diff --git a/crates/bindings-csharp/BSATN.Runtime/BSATN/Runtime.cs b/crates/bindings-csharp/BSATN.Runtime/BSATN/Runtime.cs index bce87b273bc..9a7a617a2ab 100644 --- a/crates/bindings-csharp/BSATN.Runtime/BSATN/Runtime.cs +++ b/crates/bindings-csharp/BSATN.Runtime/BSATN/Runtime.cs @@ -228,6 +228,12 @@ public void Write(BinaryWriter writer, Inner? value) public AlgebraicType GetAlgebraicType(ITypeRegistrar registrar) => AlgebraicType.MakeOption(innerRW.GetAlgebraicType(registrar)); + + // Return a List BSATN serializer that can serialize this option as an array + public static List GetListSerializer() + { + return new List(); + } } public readonly struct Bool : IReadWrite diff --git a/crates/bindings-csharp/Codegen.Tests/fixtures/diag/snapshots/ExtraCompilationErrors.verified.txt b/crates/bindings-csharp/Codegen.Tests/fixtures/diag/snapshots/ExtraCompilationErrors.verified.txt index b369e66f498..df6c2725686 100644 --- a/crates/bindings-csharp/Codegen.Tests/fixtures/diag/snapshots/ExtraCompilationErrors.verified.txt +++ b/crates/bindings-csharp/Codegen.Tests/fixtures/diag/snapshots/ExtraCompilationErrors.verified.txt @@ -233,7 +233,7 @@ public partial struct TestDefaultFieldValues var returnValue = Module.ViewDefWrongContext((SpacetimeDB.ViewContext)ctx); ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - using var output = new System.IO.MemoryStream(); + SpacetimeDB.BSATN.List returnRW = new(); */ Message: Argument 1: cannot convert from 'SpacetimeDB.ViewContext' to 'SpacetimeDB.ReducerContext', Severity: Error, @@ -279,7 +279,7 @@ SpacetimeDB.Internal.Module.RegisterClientVisibilityFilter(global::Module.MY_THI var returnValue = Module.ViewDefNoContext((SpacetimeDB.ViewContext)ctx); ^^^^^^^^^^^^^^^^ - using var output = new System.IO.MemoryStream(); + SpacetimeDB.BSATN.List returnRW = new(); */ Message: No overload for method 'ViewDefNoContext' takes 1 arguments, Severity: Error, @@ -299,10 +299,10 @@ SpacetimeDB.Internal.Module.RegisterClientVisibilityFilter(global::Module.MY_THI } }, {/* - - private static readonly SpacetimeDB.BSATN.ValueOption returnRW = new(); - ^^^^^ - + Params: [], + ReturnType: new SpacetimeDB.BSATN.ValueOption().GetAlgebraicType(registrar) + ^^^^^ +); */ Message: The type name 'BSATN' does not exist in the type 'NotSpacetimeType', Severity: Error, @@ -322,10 +322,10 @@ SpacetimeDB.Internal.Module.RegisterClientVisibilityFilter(global::Module.MY_THI } }, {/* - Params: [], - ReturnType: new SpacetimeDB.BSATN.ValueOption().GetAlgebraicType(registrar) - ^^^^^ -); + var returnValue = Module.ViewDefReturnsNotASpacetimeType((SpacetimeDB.AnonymousViewContext)ctx); + var listSerializer = SpacetimeDB.BSATN.ValueOption.GetListSerializer(); + ^^^^^ + var listValue = ModuleRegistration.ToListOrEmpty(returnValue); */ Message: The type name 'BSATN' does not exist in the type 'NotSpacetimeType', Severity: Error, diff --git a/crates/bindings-csharp/Codegen.Tests/fixtures/diag/snapshots/Module#FFI.verified.cs b/crates/bindings-csharp/Codegen.Tests/fixtures/diag/snapshots/Module#FFI.verified.cs index f682dc6c61b..a2916cd3f00 100644 --- a/crates/bindings-csharp/Codegen.Tests/fixtures/diag/snapshots/Module#FFI.verified.cs +++ b/crates/bindings-csharp/Codegen.Tests/fixtures/diag/snapshots/Module#FFI.verified.cs @@ -1030,32 +1030,31 @@ public sealed class Local } } -sealed class ViewDefIndexNoMutationViewDispatcher : global::SpacetimeDB.Internal.IAnonymousView +sealed class ViewDefNoContextViewDispatcher : global::SpacetimeDB.Internal.IView { - private static readonly SpacetimeDB.BSATN.ValueOption returnRW = new(); - - public SpacetimeDB.Internal.RawViewDefV9 MakeAnonymousViewDef( + public SpacetimeDB.Internal.RawViewDefV9 MakeViewDef( SpacetimeDB.BSATN.ITypeRegistrar registrar ) => new global::SpacetimeDB.Internal.RawViewDefV9( - Name: "ViewDefIndexNoMutation", - Index: 1, + Name: "ViewDefNoContext", + Index: 0, IsPublic: true, - IsAnonymous: true, + IsAnonymous: false, Params: [], - ReturnType: new SpacetimeDB.BSATN.ValueOption().GetAlgebraicType( + ReturnType: new SpacetimeDB.BSATN.List().GetAlgebraicType( registrar ) ); public byte[] Invoke( System.IO.BinaryReader reader, - global::SpacetimeDB.Internal.IAnonymousViewContext ctx + global::SpacetimeDB.Internal.IViewContext ctx ) { try { - var returnValue = Module.ViewDefIndexNoMutation((SpacetimeDB.AnonymousViewContext)ctx); + var returnValue = Module.ViewDefNoContext((SpacetimeDB.ViewContext)ctx); + SpacetimeDB.BSATN.List returnRW = new(); using var output = new System.IO.MemoryStream(); using var writer = new System.IO.BinaryWriter(output); returnRW.Write(writer, returnValue); @@ -1063,38 +1062,37 @@ public byte[] Invoke( } catch (System.Exception e) { - global::SpacetimeDB.Log.Error("Error in view 'ViewDefIndexNoMutation': " + e); + global::SpacetimeDB.Log.Error("Error in view 'ViewDefNoContext': " + e); throw; } } } -sealed class ViewDefNoAnonIdentityViewDispatcher : global::SpacetimeDB.Internal.IAnonymousView +sealed class ViewDefNoPublicViewDispatcher : global::SpacetimeDB.Internal.IView { - private static readonly SpacetimeDB.BSATN.ValueOption returnRW = new(); - - public SpacetimeDB.Internal.RawViewDefV9 MakeAnonymousViewDef( + public SpacetimeDB.Internal.RawViewDefV9 MakeViewDef( SpacetimeDB.BSATN.ITypeRegistrar registrar ) => new global::SpacetimeDB.Internal.RawViewDefV9( - Name: "ViewDefNoAnonIdentity", - Index: 2, - IsPublic: true, - IsAnonymous: true, + Name: "ViewDefNoPublic", + Index: 1, + IsPublic: false, + IsAnonymous: false, Params: [], - ReturnType: new SpacetimeDB.BSATN.ValueOption().GetAlgebraicType( + ReturnType: new SpacetimeDB.BSATN.List().GetAlgebraicType( registrar ) ); public byte[] Invoke( System.IO.BinaryReader reader, - global::SpacetimeDB.Internal.IAnonymousViewContext ctx + global::SpacetimeDB.Internal.IViewContext ctx ) { try { - var returnValue = Module.ViewDefNoAnonIdentity((SpacetimeDB.AnonymousViewContext)ctx); + var returnValue = Module.ViewDefNoPublic((SpacetimeDB.ViewContext)ctx); + SpacetimeDB.BSATN.List returnRW = new(); using var output = new System.IO.MemoryStream(); using var writer = new System.IO.BinaryWriter(output); returnRW.Write(writer, returnValue); @@ -1102,22 +1100,20 @@ public byte[] Invoke( } catch (System.Exception e) { - global::SpacetimeDB.Log.Error("Error in view 'ViewDefNoAnonIdentity': " + e); + global::SpacetimeDB.Log.Error("Error in view 'ViewDefNoPublic': " + e); throw; } } } -sealed class ViewDefNoContextViewDispatcher : global::SpacetimeDB.Internal.IView +sealed class ViewDefWrongContextViewDispatcher : global::SpacetimeDB.Internal.IView { - private static readonly SpacetimeDB.BSATN.List returnRW = new(); - public SpacetimeDB.Internal.RawViewDefV9 MakeViewDef( SpacetimeDB.BSATN.ITypeRegistrar registrar ) => new global::SpacetimeDB.Internal.RawViewDefV9( - Name: "ViewDefNoContext", - Index: 1, + Name: "ViewDefWrongContext", + Index: 2, IsPublic: true, IsAnonymous: false, Params: [], @@ -1133,7 +1129,8 @@ public byte[] Invoke( { try { - var returnValue = Module.ViewDefNoContext((SpacetimeDB.ViewContext)ctx); + var returnValue = Module.ViewDefWrongContext((SpacetimeDB.ViewContext)ctx); + SpacetimeDB.BSATN.List returnRW = new(); using var output = new System.IO.MemoryStream(); using var writer = new System.IO.BinaryWriter(output); returnRW.Write(writer, returnValue); @@ -1141,38 +1138,35 @@ public byte[] Invoke( } catch (System.Exception e) { - global::SpacetimeDB.Log.Error("Error in view 'ViewDefNoContext': " + e); + global::SpacetimeDB.Log.Error("Error in view 'ViewDefWrongContext': " + e); throw; } } } -sealed class ViewDefNoIterViewDispatcher : global::SpacetimeDB.Internal.IAnonymousView +sealed class ViewDefWrongReturnViewDispatcher : global::SpacetimeDB.Internal.IView { - private static readonly SpacetimeDB.BSATN.ValueOption returnRW = new(); - - public SpacetimeDB.Internal.RawViewDefV9 MakeAnonymousViewDef( + public SpacetimeDB.Internal.RawViewDefV9 MakeViewDef( SpacetimeDB.BSATN.ITypeRegistrar registrar ) => new global::SpacetimeDB.Internal.RawViewDefV9( - Name: "ViewDefNoIter", + Name: "ViewDefWrongReturn", Index: 3, IsPublic: true, - IsAnonymous: true, + IsAnonymous: false, Params: [], - ReturnType: new SpacetimeDB.BSATN.ValueOption().GetAlgebraicType( - registrar - ) + ReturnType: new Player.BSATN().GetAlgebraicType(registrar) ); public byte[] Invoke( System.IO.BinaryReader reader, - global::SpacetimeDB.Internal.IAnonymousViewContext ctx + global::SpacetimeDB.Internal.IViewContext ctx ) { try { - var returnValue = Module.ViewDefNoIter((SpacetimeDB.AnonymousViewContext)ctx); + var returnValue = Module.ViewDefWrongReturn((SpacetimeDB.ViewContext)ctx); + Player.BSATN returnRW = new(); using var output = new System.IO.MemoryStream(); using var writer = new System.IO.BinaryWriter(output); returnRW.Write(writer, returnValue); @@ -1180,26 +1174,24 @@ public byte[] Invoke( } catch (System.Exception e) { - global::SpacetimeDB.Log.Error("Error in view 'ViewDefNoIter': " + e); + global::SpacetimeDB.Log.Error("Error in view 'ViewDefWrongReturn': " + e); throw; } } } -sealed class ViewDefNoPublicViewDispatcher : global::SpacetimeDB.Internal.IView +sealed class ViewNoDeleteViewDispatcher : global::SpacetimeDB.Internal.IView { - private static readonly SpacetimeDB.BSATN.List returnRW = new(); - public SpacetimeDB.Internal.RawViewDefV9 MakeViewDef( SpacetimeDB.BSATN.ITypeRegistrar registrar ) => new global::SpacetimeDB.Internal.RawViewDefV9( - Name: "ViewDefNoPublic", - Index: 2, - IsPublic: false, + Name: "ViewNoDelete", + Index: 4, + IsPublic: true, IsAnonymous: false, Params: [], - ReturnType: new SpacetimeDB.BSATN.List().GetAlgebraicType( + ReturnType: new SpacetimeDB.BSATN.ValueOption().GetAlgebraicType( registrar ) ); @@ -1211,154 +1203,161 @@ public byte[] Invoke( { try { - var returnValue = Module.ViewDefNoPublic((SpacetimeDB.ViewContext)ctx); + var returnValue = Module.ViewNoDelete((SpacetimeDB.ViewContext)ctx); + var listSerializer = SpacetimeDB.BSATN.ValueOption< + Player, + Player.BSATN + >.GetListSerializer(); + var listValue = ModuleRegistration.ToListOrEmpty(returnValue); using var output = new System.IO.MemoryStream(); using var writer = new System.IO.BinaryWriter(output); - returnRW.Write(writer, returnValue); + listSerializer.Write(writer, listValue); return output.ToArray(); } catch (System.Exception e) { - global::SpacetimeDB.Log.Error("Error in view 'ViewDefNoPublic': " + e); + global::SpacetimeDB.Log.Error("Error in view 'ViewNoDelete': " + e); throw; } } } -sealed class ViewDefReturnsNotASpacetimeTypeViewDispatcher - : global::SpacetimeDB.Internal.IAnonymousView +sealed class ViewNoInsertViewDispatcher : global::SpacetimeDB.Internal.IView { - private static readonly SpacetimeDB.BSATN.ValueOption< - NotSpacetimeType, - NotSpacetimeType.BSATN - > returnRW = new(); - - public SpacetimeDB.Internal.RawViewDefV9 MakeAnonymousViewDef( + public SpacetimeDB.Internal.RawViewDefV9 MakeViewDef( SpacetimeDB.BSATN.ITypeRegistrar registrar ) => new global::SpacetimeDB.Internal.RawViewDefV9( - Name: "ViewDefReturnsNotASpacetimeType", - Index: 4, + Name: "ViewNoInsert", + Index: 5, IsPublic: true, - IsAnonymous: true, + IsAnonymous: false, Params: [], - ReturnType: new SpacetimeDB.BSATN.ValueOption< - NotSpacetimeType, - NotSpacetimeType.BSATN - >().GetAlgebraicType(registrar) + ReturnType: new SpacetimeDB.BSATN.ValueOption().GetAlgebraicType( + registrar + ) ); public byte[] Invoke( System.IO.BinaryReader reader, - global::SpacetimeDB.Internal.IAnonymousViewContext ctx + global::SpacetimeDB.Internal.IViewContext ctx ) { try { - var returnValue = Module.ViewDefReturnsNotASpacetimeType( - (SpacetimeDB.AnonymousViewContext)ctx - ); + var returnValue = Module.ViewNoInsert((SpacetimeDB.ViewContext)ctx); + var listSerializer = SpacetimeDB.BSATN.ValueOption< + Player, + Player.BSATN + >.GetListSerializer(); + var listValue = ModuleRegistration.ToListOrEmpty(returnValue); using var output = new System.IO.MemoryStream(); using var writer = new System.IO.BinaryWriter(output); - returnRW.Write(writer, returnValue); + listSerializer.Write(writer, listValue); return output.ToArray(); } catch (System.Exception e) { - global::SpacetimeDB.Log.Error("Error in view 'ViewDefReturnsNotASpacetimeType': " + e); + global::SpacetimeDB.Log.Error("Error in view 'ViewNoInsert': " + e); throw; } } } -sealed class ViewDefWrongContextViewDispatcher : global::SpacetimeDB.Internal.IView +sealed class ViewDefIndexNoMutationViewDispatcher : global::SpacetimeDB.Internal.IAnonymousView { - private static readonly SpacetimeDB.BSATN.List returnRW = new(); - - public SpacetimeDB.Internal.RawViewDefV9 MakeViewDef( + public SpacetimeDB.Internal.RawViewDefV9 MakeAnonymousViewDef( SpacetimeDB.BSATN.ITypeRegistrar registrar ) => new global::SpacetimeDB.Internal.RawViewDefV9( - Name: "ViewDefWrongContext", - Index: 3, + Name: "ViewDefIndexNoMutation", + Index: 0, IsPublic: true, - IsAnonymous: false, + IsAnonymous: true, Params: [], - ReturnType: new SpacetimeDB.BSATN.List().GetAlgebraicType( + ReturnType: new SpacetimeDB.BSATN.ValueOption().GetAlgebraicType( registrar ) ); public byte[] Invoke( System.IO.BinaryReader reader, - global::SpacetimeDB.Internal.IViewContext ctx + global::SpacetimeDB.Internal.IAnonymousViewContext ctx ) { try { - var returnValue = Module.ViewDefWrongContext((SpacetimeDB.ViewContext)ctx); + var returnValue = Module.ViewDefIndexNoMutation((SpacetimeDB.AnonymousViewContext)ctx); + var listSerializer = SpacetimeDB.BSATN.ValueOption< + Player, + Player.BSATN + >.GetListSerializer(); + var listValue = ModuleRegistration.ToListOrEmpty(returnValue); using var output = new System.IO.MemoryStream(); using var writer = new System.IO.BinaryWriter(output); - returnRW.Write(writer, returnValue); + listSerializer.Write(writer, listValue); return output.ToArray(); } catch (System.Exception e) { - global::SpacetimeDB.Log.Error("Error in view 'ViewDefWrongContext': " + e); + global::SpacetimeDB.Log.Error("Error in view 'ViewDefIndexNoMutation': " + e); throw; } } } -sealed class ViewDefWrongReturnViewDispatcher : global::SpacetimeDB.Internal.IView +sealed class ViewDefNoAnonIdentityViewDispatcher : global::SpacetimeDB.Internal.IAnonymousView { - private static readonly Player.BSATN returnRW = new(); - - public SpacetimeDB.Internal.RawViewDefV9 MakeViewDef( + public SpacetimeDB.Internal.RawViewDefV9 MakeAnonymousViewDef( SpacetimeDB.BSATN.ITypeRegistrar registrar ) => new global::SpacetimeDB.Internal.RawViewDefV9( - Name: "ViewDefWrongReturn", - Index: 4, + Name: "ViewDefNoAnonIdentity", + Index: 1, IsPublic: true, - IsAnonymous: false, + IsAnonymous: true, Params: [], - ReturnType: new Player.BSATN().GetAlgebraicType(registrar) + ReturnType: new SpacetimeDB.BSATN.ValueOption().GetAlgebraicType( + registrar + ) ); public byte[] Invoke( System.IO.BinaryReader reader, - global::SpacetimeDB.Internal.IViewContext ctx + global::SpacetimeDB.Internal.IAnonymousViewContext ctx ) { try { - var returnValue = Module.ViewDefWrongReturn((SpacetimeDB.ViewContext)ctx); + var returnValue = Module.ViewDefNoAnonIdentity((SpacetimeDB.AnonymousViewContext)ctx); + var listSerializer = SpacetimeDB.BSATN.ValueOption< + Player, + Player.BSATN + >.GetListSerializer(); + var listValue = ModuleRegistration.ToListOrEmpty(returnValue); using var output = new System.IO.MemoryStream(); using var writer = new System.IO.BinaryWriter(output); - returnRW.Write(writer, returnValue); + listSerializer.Write(writer, listValue); return output.ToArray(); } catch (System.Exception e) { - global::SpacetimeDB.Log.Error("Error in view 'ViewDefWrongReturn': " + e); + global::SpacetimeDB.Log.Error("Error in view 'ViewDefNoAnonIdentity': " + e); throw; } } } -sealed class ViewNoDeleteViewDispatcher : global::SpacetimeDB.Internal.IView +sealed class ViewDefNoIterViewDispatcher : global::SpacetimeDB.Internal.IAnonymousView { - private static readonly SpacetimeDB.BSATN.ValueOption returnRW = new(); - - public SpacetimeDB.Internal.RawViewDefV9 MakeViewDef( + public SpacetimeDB.Internal.RawViewDefV9 MakeAnonymousViewDef( SpacetimeDB.BSATN.ITypeRegistrar registrar ) => new global::SpacetimeDB.Internal.RawViewDefV9( - Name: "ViewNoDelete", - Index: 5, + Name: "ViewDefNoIter", + Index: 2, IsPublic: true, - IsAnonymous: false, + IsAnonymous: true, Params: [], ReturnType: new SpacetimeDB.BSATN.ValueOption().GetAlgebraicType( registrar @@ -1367,59 +1366,71 @@ SpacetimeDB.BSATN.ITypeRegistrar registrar public byte[] Invoke( System.IO.BinaryReader reader, - global::SpacetimeDB.Internal.IViewContext ctx + global::SpacetimeDB.Internal.IAnonymousViewContext ctx ) { try { - var returnValue = Module.ViewNoDelete((SpacetimeDB.ViewContext)ctx); + var returnValue = Module.ViewDefNoIter((SpacetimeDB.AnonymousViewContext)ctx); + var listSerializer = SpacetimeDB.BSATN.ValueOption< + Player, + Player.BSATN + >.GetListSerializer(); + var listValue = ModuleRegistration.ToListOrEmpty(returnValue); using var output = new System.IO.MemoryStream(); using var writer = new System.IO.BinaryWriter(output); - returnRW.Write(writer, returnValue); + listSerializer.Write(writer, listValue); return output.ToArray(); } catch (System.Exception e) { - global::SpacetimeDB.Log.Error("Error in view 'ViewNoDelete': " + e); + global::SpacetimeDB.Log.Error("Error in view 'ViewDefNoIter': " + e); throw; } } } -sealed class ViewNoInsertViewDispatcher : global::SpacetimeDB.Internal.IView +sealed class ViewDefReturnsNotASpacetimeTypeViewDispatcher + : global::SpacetimeDB.Internal.IAnonymousView { - private static readonly SpacetimeDB.BSATN.ValueOption returnRW = new(); - - public SpacetimeDB.Internal.RawViewDefV9 MakeViewDef( + public SpacetimeDB.Internal.RawViewDefV9 MakeAnonymousViewDef( SpacetimeDB.BSATN.ITypeRegistrar registrar ) => new global::SpacetimeDB.Internal.RawViewDefV9( - Name: "ViewNoInsert", - Index: 6, + Name: "ViewDefReturnsNotASpacetimeType", + Index: 3, IsPublic: true, - IsAnonymous: false, + IsAnonymous: true, Params: [], - ReturnType: new SpacetimeDB.BSATN.ValueOption().GetAlgebraicType( - registrar - ) + ReturnType: new SpacetimeDB.BSATN.ValueOption< + NotSpacetimeType, + NotSpacetimeType.BSATN + >().GetAlgebraicType(registrar) ); public byte[] Invoke( System.IO.BinaryReader reader, - global::SpacetimeDB.Internal.IViewContext ctx + global::SpacetimeDB.Internal.IAnonymousViewContext ctx ) { try { - var returnValue = Module.ViewNoInsert((SpacetimeDB.ViewContext)ctx); + var returnValue = Module.ViewDefReturnsNotASpacetimeType( + (SpacetimeDB.AnonymousViewContext)ctx + ); + var listSerializer = SpacetimeDB.BSATN.ValueOption< + NotSpacetimeType, + NotSpacetimeType.BSATN + >.GetListSerializer(); + var listValue = ModuleRegistration.ToListOrEmpty(returnValue); using var output = new System.IO.MemoryStream(); using var writer = new System.IO.BinaryWriter(output); - returnRW.Write(writer, returnValue); + listSerializer.Write(writer, listValue); return output.ToArray(); } catch (System.Exception e) { - global::SpacetimeDB.Log.Error("Error in view 'ViewNoInsert': " + e); + global::SpacetimeDB.Log.Error("Error in view 'ViewDefReturnsNotASpacetimeType': " + e); throw; } } @@ -1812,6 +1823,9 @@ public void Invoke(BinaryReader reader, SpacetimeDB.Internal.IReducerContext ctx } } + public static List ToListOrEmpty(T? value) + where T : struct => value is null ? new List() : new List { value.Value }; + #if EXPERIMENTAL_WASM_AOT // In AOT mode we're building a library. // Main method won't be called automatically, so we need to export it as a preinit function. @@ -1849,16 +1863,21 @@ public static void Main() SpacetimeDB.Internal.Module.RegisterReducer(); SpacetimeDB.Internal.Module.RegisterReducer(); SpacetimeDB.Internal.Module.RegisterReducer(); - SpacetimeDB.Internal.Module.RegisterAnonymousView(); - SpacetimeDB.Internal.Module.RegisterAnonymousView(); + + // IMPORTANT: The order in which we register views matters. + // It must correspond to the order in which we call `GenerateDispatcherClass`. + // See the comment on `GenerateDispatcherClass` for more explanation. SpacetimeDB.Internal.Module.RegisterView(); - SpacetimeDB.Internal.Module.RegisterAnonymousView(); SpacetimeDB.Internal.Module.RegisterView(); - SpacetimeDB.Internal.Module.RegisterAnonymousView(); SpacetimeDB.Internal.Module.RegisterView(); SpacetimeDB.Internal.Module.RegisterView(); SpacetimeDB.Internal.Module.RegisterView(); SpacetimeDB.Internal.Module.RegisterView(); + SpacetimeDB.Internal.Module.RegisterAnonymousView(); + SpacetimeDB.Internal.Module.RegisterAnonymousView(); + SpacetimeDB.Internal.Module.RegisterAnonymousView(); + SpacetimeDB.Internal.Module.RegisterAnonymousView(); + SpacetimeDB.Internal.Module.RegisterTable< global::Player, SpacetimeDB.Internal.TableHandles.Player @@ -2147,6 +2166,33 @@ SpacetimeDB.Internal.BytesSink error args, error ); + + [UnmanagedCallersOnly(EntryPoint = "__call_view__")] + public static SpacetimeDB.Internal.Errno __call_view__( + uint id, + ulong sender_0, + ulong sender_1, + ulong sender_2, + ulong sender_3, + SpacetimeDB.Internal.BytesSource args, + SpacetimeDB.Internal.BytesSink sink + ) => + SpacetimeDB.Internal.Module.__call_view__( + id, + sender_0, + sender_1, + sender_2, + sender_3, + args, + sink + ); + + [UnmanagedCallersOnly(EntryPoint = "__call_view_anon__")] + public static SpacetimeDB.Internal.Errno __call_view_anon__( + uint id, + SpacetimeDB.Internal.BytesSource args, + SpacetimeDB.Internal.BytesSink sink + ) => SpacetimeDB.Internal.Module.__call_view_anon__(id, args, sink); #endif } diff --git a/crates/bindings-csharp/Codegen.Tests/fixtures/server/snapshots/Module#FFI.verified.cs b/crates/bindings-csharp/Codegen.Tests/fixtures/server/snapshots/Module#FFI.verified.cs index 2ec9fd783d4..f0d41da28d4 100644 --- a/crates/bindings-csharp/Codegen.Tests/fixtures/server/snapshots/Module#FFI.verified.cs +++ b/crates/bindings-csharp/Codegen.Tests/fixtures/server/snapshots/Module#FFI.verified.cs @@ -1055,19 +1055,16 @@ public sealed class Local } } -sealed class FindPublicTableByIdentityViewDispatcher : global::SpacetimeDB.Internal.IAnonymousView +sealed class PublicTableByIdentityViewDispatcher : global::SpacetimeDB.Internal.IView { - private static readonly SpacetimeDB.BSATN.ValueOption returnRW = - new(); - - public SpacetimeDB.Internal.RawViewDefV9 MakeAnonymousViewDef( + public SpacetimeDB.Internal.RawViewDefV9 MakeViewDef( SpacetimeDB.BSATN.ITypeRegistrar registrar ) => new global::SpacetimeDB.Internal.RawViewDefV9( - Name: "FindPublicTableByIdentity", + Name: "PublicTableByIdentity", Index: 0, IsPublic: true, - IsAnonymous: true, + IsAnonymous: false, Params: [], ReturnType: new SpacetimeDB.BSATN.ValueOption< PublicTable, @@ -1077,40 +1074,40 @@ SpacetimeDB.BSATN.ITypeRegistrar registrar public byte[] Invoke( System.IO.BinaryReader reader, - global::SpacetimeDB.Internal.IAnonymousViewContext ctx + global::SpacetimeDB.Internal.IViewContext ctx ) { try { - var returnValue = Module.FindPublicTableByIdentity( - (SpacetimeDB.AnonymousViewContext)ctx - ); + var returnValue = Module.PublicTableByIdentity((SpacetimeDB.ViewContext)ctx); + var listSerializer = SpacetimeDB.BSATN.ValueOption< + PublicTable, + PublicTable.BSATN + >.GetListSerializer(); + var listValue = ModuleRegistration.ToListOrEmpty(returnValue); using var output = new System.IO.MemoryStream(); using var writer = new System.IO.BinaryWriter(output); - returnRW.Write(writer, returnValue); + listSerializer.Write(writer, listValue); return output.ToArray(); } catch (System.Exception e) { - global::SpacetimeDB.Log.Error("Error in view 'FindPublicTableByIdentity': " + e); + global::SpacetimeDB.Log.Error("Error in view 'PublicTableByIdentity': " + e); throw; } } } -sealed class PublicTableByIdentityViewDispatcher : global::SpacetimeDB.Internal.IView +sealed class FindPublicTableByIdentityViewDispatcher : global::SpacetimeDB.Internal.IAnonymousView { - private static readonly SpacetimeDB.BSATN.ValueOption returnRW = - new(); - - public SpacetimeDB.Internal.RawViewDefV9 MakeViewDef( + public SpacetimeDB.Internal.RawViewDefV9 MakeAnonymousViewDef( SpacetimeDB.BSATN.ITypeRegistrar registrar ) => new global::SpacetimeDB.Internal.RawViewDefV9( - Name: "PublicTableByIdentity", + Name: "FindPublicTableByIdentity", Index: 0, IsPublic: true, - IsAnonymous: false, + IsAnonymous: true, Params: [], ReturnType: new SpacetimeDB.BSATN.ValueOption< PublicTable, @@ -1120,20 +1117,27 @@ SpacetimeDB.BSATN.ITypeRegistrar registrar public byte[] Invoke( System.IO.BinaryReader reader, - global::SpacetimeDB.Internal.IViewContext ctx + global::SpacetimeDB.Internal.IAnonymousViewContext ctx ) { try { - var returnValue = Module.PublicTableByIdentity((SpacetimeDB.ViewContext)ctx); + var returnValue = Module.FindPublicTableByIdentity( + (SpacetimeDB.AnonymousViewContext)ctx + ); + var listSerializer = SpacetimeDB.BSATN.ValueOption< + PublicTable, + PublicTable.BSATN + >.GetListSerializer(); + var listValue = ModuleRegistration.ToListOrEmpty(returnValue); using var output = new System.IO.MemoryStream(); using var writer = new System.IO.BinaryWriter(output); - returnRW.Write(writer, returnValue); + listSerializer.Write(writer, listValue); return output.ToArray(); } catch (System.Exception e) { - global::SpacetimeDB.Log.Error("Error in view 'PublicTableByIdentity': " + e); + global::SpacetimeDB.Log.Error("Error in view 'FindPublicTableByIdentity': " + e); throw; } } @@ -1613,6 +1617,9 @@ public void Invoke(BinaryReader reader, SpacetimeDB.Internal.IReducerContext ctx } } + public static List ToListOrEmpty(T? value) + where T : struct => value is null ? new List() : new List { value.Value }; + #if EXPERIMENTAL_WASM_AOT // In AOT mode we're building a library. // Main method won't be called automatically, so we need to export it as a preinit function. @@ -1648,8 +1655,13 @@ public static void Main() SpacetimeDB.Internal.Module.RegisterReducer(); SpacetimeDB.Internal.Module.RegisterReducer(); SpacetimeDB.Internal.Module.RegisterReducer(); - SpacetimeDB.Internal.Module.RegisterAnonymousView(); + + // IMPORTANT: The order in which we register views matters. + // It must correspond to the order in which we call `GenerateDispatcherClass`. + // See the comment on `GenerateDispatcherClass` for more explanation. SpacetimeDB.Internal.Module.RegisterView(); + SpacetimeDB.Internal.Module.RegisterAnonymousView(); + SpacetimeDB.Internal.Module.RegisterTable< global::BTreeMultiColumn, SpacetimeDB.Internal.TableHandles.BTreeMultiColumn @@ -1718,6 +1730,33 @@ SpacetimeDB.Internal.BytesSink error args, error ); + + [UnmanagedCallersOnly(EntryPoint = "__call_view__")] + public static SpacetimeDB.Internal.Errno __call_view__( + uint id, + ulong sender_0, + ulong sender_1, + ulong sender_2, + ulong sender_3, + SpacetimeDB.Internal.BytesSource args, + SpacetimeDB.Internal.BytesSink sink + ) => + SpacetimeDB.Internal.Module.__call_view__( + id, + sender_0, + sender_1, + sender_2, + sender_3, + args, + sink + ); + + [UnmanagedCallersOnly(EntryPoint = "__call_view_anon__")] + public static SpacetimeDB.Internal.Errno __call_view_anon__( + uint id, + SpacetimeDB.Internal.BytesSource args, + SpacetimeDB.Internal.BytesSink sink + ) => SpacetimeDB.Internal.Module.__call_view_anon__(id, args, sink); #endif } diff --git a/crates/bindings-csharp/Codegen/Module.cs b/crates/bindings-csharp/Codegen/Module.cs index 04b4ce876bb..23669c6023b 100644 --- a/crates/bindings-csharp/Codegen/Module.cs +++ b/crates/bindings-csharp/Codegen/Module.cs @@ -921,14 +921,6 @@ record ViewDeclaration public readonly EquatableArray Parameters; public readonly Scope Scope; - public static uint ViewIndexCounter = 0; - - public static uint GetNextViewIndex() => ViewIndexCounter++; - - public static uint AnonViewIndexCounter = 0; - - public static uint GetNextAnonViewIndex() => AnonViewIndexCounter++; - public ViewDeclaration(GeneratorAttributeSyntaxContext context, DiagReporter diag) { var methodSyntax = (MethodDeclarationSyntax)context.TargetNode; @@ -1001,7 +993,12 @@ public string GenerateViewDef(uint Index) => ); """; - public string GenerateDispatcherClass() + /// + /// Generates the class responsible for evaluating a view. + /// If this is an anonymous view, the index corresponds to the position of this dispatcher in the `viewDispatchers` list of `RegisterView`. + /// Otherwise it corresponds to the position of this dispatcher in the `anonymousViewDispatchers` list of `RegisterAnonymousView`. + /// + public string GenerateDispatcherClass(uint index) { var paramReads = string.Join( "\n ", @@ -1022,15 +1019,30 @@ public string GenerateDispatcherClass() ? "SpacetimeDB.AnonymousViewContext" : "SpacetimeDB.ViewContext"; + var isValueOption = ReturnType.BSATNName.Contains("SpacetimeDB.BSATN.ValueOption"); + var writeOutput = isValueOption + ? $$$""" + var listSerializer = {{{ReturnType.BSATNName}}}.GetListSerializer(); + var listValue = ModuleRegistration.ToListOrEmpty(returnValue); + using var output = new System.IO.MemoryStream(); + using var writer = new System.IO.BinaryWriter(output); + listSerializer.Write(writer, listValue); + return output.ToArray(); + """ + : $$$""" + {{{ReturnType.BSATNName}}} returnRW = new(); + using var output = new System.IO.MemoryStream(); + using var writer = new System.IO.BinaryWriter(output); + returnRW.Write(writer, returnValue); + return output.ToArray(); + """; + var invocationArgs = Parameters.Length == 0 ? "" : ", " + string.Join(", ", Parameters.Select(p => p.Name)); - var index = IsAnonymous ? GetNextAnonViewIndex() : GetNextViewIndex(); return $$$""" sealed class {{{Name}}}ViewDispatcher : {{{interfaceName}}} { {{{MemberDeclaration.GenerateBsatnFields(Accessibility.Private, Parameters)}}} - private static readonly {{{ReturnType.BSATNName}}} returnRW = new(); - public SpacetimeDB.Internal.RawViewDefV9 {{{makeViewDefMethod}}}(SpacetimeDB.BSATN.ITypeRegistrar registrar) => {{{GenerateViewDef(index)}}} @@ -1041,10 +1053,7 @@ public byte[] Invoke( try { {{{paramReads}}} var returnValue = {{{FullName}}}(({{{concreteContext}}})ctx{{{invocationArgs}}}); - using var output = new System.IO.MemoryStream(); - using var writer = new System.IO.BinaryWriter(output); - returnRW.Write(writer, returnValue); - return output.ToArray(); + {{{writeOutput}}} } catch (System.Exception e) { global::SpacetimeDB.Log.Error("Error in view '{{{Name}}}': " + e); throw; @@ -1053,13 +1062,6 @@ public byte[] Invoke( } """; } - - public string GenerateClass() - { - var builder = new Scope.Extensions(Scope, FullName); - builder.Contents.Append(GenerateDispatcherClass()); - return builder.ToString(); - } } /// @@ -1489,8 +1491,15 @@ public sealed class Local { } } - {{string.Join("\n", views.Array.Select(v => v.GenerateDispatcherClass()))}} - + {{string.Join("\n", + views.Array.Where(v => !v.IsAnonymous) + .Select((v, i) => v.GenerateDispatcherClass((uint)i)) + .Concat( + views.Array.Where(v => v.IsAnonymous) + .Select((v, i) => v.GenerateDispatcherClass((uint)i)) + ) + )}} + namespace SpacetimeDB.Internal.ViewHandles { {{string.Join("\n", readOnlyAccessors.Array.Select(v => v.readOnlyAccessor))}} } @@ -1504,6 +1513,9 @@ public sealed partial class LocalReadOnly { static class ModuleRegistration { {{string.Join("\n", addReducers.Select(r => r.Class))}} + public static List ToListOrEmpty(T? value) where T : struct + => value is null ? new List() : new List { value.Value }; + #if EXPERIMENTAL_WASM_AOT // In AOT mode we're building a library. // Main method won't be called automatically, so we need to export it as a preinit function. @@ -1525,14 +1537,19 @@ public static void Main() { $"SpacetimeDB.Internal.Module.RegisterReducer<{r.Name}>();" ) )}} - {{string.Join( - "\n", - views.Array.Select(v => - v.IsAnonymous - ? $"SpacetimeDB.Internal.Module.RegisterAnonymousView<{v.Name}ViewDispatcher>();" - : $"SpacetimeDB.Internal.Module.RegisterView<{v.Name}ViewDispatcher>();" - ) - )}} + + // IMPORTANT: The order in which we register views matters. + // It must correspond to the order in which we call `GenerateDispatcherClass`. + // See the comment on `GenerateDispatcherClass` for more explanation. + {{string.Join("\n", + views.Array.Where(v => !v.IsAnonymous) + .Select(v => $"SpacetimeDB.Internal.Module.RegisterView<{v.Name}ViewDispatcher>();") + .Concat( + views.Array.Where(v => v.IsAnonymous) + .Select(v => $"SpacetimeDB.Internal.Module.RegisterAnonymousView<{v.Name}ViewDispatcher>();") + ) + )}} + {{string.Join( "\n", tableAccessors.Select(t => $"SpacetimeDB.Internal.Module.RegisterTable<{t.tableName}, SpacetimeDB.Internal.TableHandles.{t.tableAccessorName}>();") @@ -1584,6 +1601,36 @@ SpacetimeDB.Internal.BytesSink error args, error ); + + [UnmanagedCallersOnly(EntryPoint = "__call_view__")] + public static SpacetimeDB.Internal.Errno __call_view__( + uint id, + ulong sender_0, + ulong sender_1, + ulong sender_2, + ulong sender_3, + SpacetimeDB.Internal.BytesSource args, + SpacetimeDB.Internal.BytesSink sink + ) => SpacetimeDB.Internal.Module.__call_view__( + id, + sender_0, + sender_1, + sender_2, + sender_3, + args, + sink + ); + + [UnmanagedCallersOnly(EntryPoint = "__call_view_anon__")] + public static SpacetimeDB.Internal.Errno __call_view_anon__( + uint id, + SpacetimeDB.Internal.BytesSource args, + SpacetimeDB.Internal.BytesSink sink + ) => SpacetimeDB.Internal.Module.__call_view_anon__( + id, + args, + sink + ); #endif } diff --git a/crates/bindings-csharp/Runtime/Internal/Module.cs b/crates/bindings-csharp/Runtime/Internal/Module.cs index f0afea9f0df..849b2510fff 100644 --- a/crates/bindings-csharp/Runtime/Internal/Module.cs +++ b/crates/bindings-csharp/Runtime/Internal/Module.cs @@ -326,7 +326,7 @@ BytesSink rows } } - public static Errno __call_anonymous_view__(uint id, BytesSource args, BytesSink rows) + public static Errno __call_view_anon__(uint id, BytesSource args, BytesSink rows) { try { diff --git a/crates/bindings-csharp/Runtime/bindings.c b/crates/bindings-csharp/Runtime/bindings.c index 1756325dfdc..f256c03bd2e 100644 --- a/crates/bindings-csharp/Runtime/bindings.c +++ b/crates/bindings-csharp/Runtime/bindings.c @@ -159,6 +159,18 @@ EXPORT(int16_t, __call_reducer__, &sender_0, &sender_1, &sender_2, &sender_3, &conn_id_0, &conn_id_1, ×tamp, &args, &error); + +EXPORT(int16_t, __call_view__, + (uint32_t id, + uint64_t sender_0, uint64_t sender_1, uint64_t sender_2, uint64_t sender_3, + BytesSource args, BytesSink rows), + &id, + &sender_0, &sender_1, &sender_2, &sender_3, + &args, &rows); + +EXPORT(int16_t, __call_view_anon__, + (uint32_t id, BytesSource args, BytesSink rows), + &id, &args, &rows); #endif // Shims to avoid dependency on WASI in the generated Wasm file. diff --git a/sdks/csharp/examples~/quickstart-chat/client/module_bindings/SpacetimeDBClient.g.cs b/sdks/csharp/examples~/quickstart-chat/client/module_bindings/SpacetimeDBClient.g.cs index 977372c0e05..87d5432b30f 100644 --- a/sdks/csharp/examples~/quickstart-chat/client/module_bindings/SpacetimeDBClient.g.cs +++ b/sdks/csharp/examples~/quickstart-chat/client/module_bindings/SpacetimeDBClient.g.cs @@ -1,7 +1,7 @@ // THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE // WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. -// This was generated using spacetimedb cli version 1.8.0 (commit 31a8d5f1540fb11aef7db32f62e19b238fdaa797). +// This was generated using spacetimedb cli version 1.8.0 (commit cd36a957862ff4efc6086c28d04773f461406e6f). #nullable enable diff --git a/sdks/csharp/examples~/regression-tests/client/EqualityOperations.cs b/sdks/csharp/examples~/regression-tests/client/EqualityOperations.cs new file mode 100644 index 00000000000..924d0d81b34 --- /dev/null +++ b/sdks/csharp/examples~/regression-tests/client/EqualityOperations.cs @@ -0,0 +1,25 @@ +using SpacetimeDB.Types; + +public static class EqualityOperations +{ + public static bool Equals(this Player lh, Player rh) + { + return lh.Id == rh.Id && + lh.Identity == rh.Identity && + lh.Name == rh.Name; + } + + public static bool Equals(this PlayerLevel lh, PlayerLevel rh) + { + return lh.Level == rh.Level && + lh.PlayerId == rh.PlayerId; + } + + public static bool Equals(this PlayerAndLevel lh, PlayerAndLevel rh) + { + return lh.Id == rh.Id && + lh.Identity == rh.Identity && + lh.Name == rh.Name && + lh.Level == rh.Level; + } +} \ No newline at end of file diff --git a/sdks/csharp/examples~/regression-tests/client/Program.cs b/sdks/csharp/examples~/regression-tests/client/Program.cs index 6db727e65cf..9b9f6d15b19 100644 --- a/sdks/csharp/examples~/regression-tests/client/Program.cs +++ b/sdks/csharp/examples~/regression-tests/client/Program.cs @@ -38,7 +38,7 @@ DbConnection ConnectToDB() } uint waiting = 0; -bool applied = false; +var applied = false; SubscriptionHandle? handle = null; void OnConnected(DbConnection conn, Identity identity, string authToken) @@ -50,7 +50,7 @@ void OnConnected(DbConnection conn, Identity identity, string authToken) { throw err; }) - .Subscribe(["SELECT * FROM ExampleData"]); + .Subscribe(["SELECT * FROM ExampleData", "SELECT * FROM MyPlayer", "SELECT * FROM PlayersForLevel"]); conn.Reducers.OnAdd += (ReducerEventContext ctx, uint id, uint indexed) => { @@ -133,6 +133,52 @@ void OnSubscriptionApplied(SubscriptionEventContext context) ValidateBTreeIndexes(ctx); waiting--; }); + + + // Views test + + Log.Debug("Checking Views are populated"); + Debug.Assert(context.Db.MyPlayer != null, "context.Db.MyPlayer != null"); + Debug.Assert(context.Db.PlayersForLevel != null, "context.Db.PlayersForLevel != null"); + Debug.Assert(context.Db.MyPlayer.Count > 0, $"context.Db.MyPlayer.Count = {context.Db.MyPlayer.Count}"); + Debug.Assert(context.Db.PlayersForLevel.Count > 0, $"context.Db.PlayersForLevel.Count = {context.Db.PlayersForLevel.Count}"); + + Log.Debug("Calling Iter on View"); + var viewIterRows = context.Db.MyPlayer.Iter(); + var expectedPlayer = new Player { Id = 1, Identity = context.Identity!.Value, Name = "NewPlayer" }; + Log.Debug("MyPlayer Iter count: " + (viewIterRows != null ? viewIterRows.Count().ToString() : "null")); + Debug.Assert(viewIterRows != null && viewIterRows.Any()); + Log.Debug("Validating View row data " + + $"Id={expectedPlayer.Id}, Identity={expectedPlayer.Identity}, Name={expectedPlayer.Name} => " + + $"Id={viewIterRows.First().Id}, Identity={viewIterRows.First().Identity}, Name={viewIterRows.First().Name}"); + Debug.Assert(viewIterRows.First().Equals(expectedPlayer)); + + Log.Debug("Calling RemoteQuery on View"); + var viewRemoteQueryRows = context.Db.MyPlayer.RemoteQuery("WHERE Id > 0"); + Debug.Assert(viewRemoteQueryRows != null && viewRemoteQueryRows.Result.Length > 0); + Debug.Assert(viewRemoteQueryRows.Result.First().Equals(expectedPlayer)); + + Log.Debug("Calling Iter on Anonymous View"); + var anonViewIterRows = context.Db.PlayersForLevel.Iter(); + var expectedPlayerAndLevel = new PlayerAndLevel + { + Id = 1, + Identity = context.Identity!.Value, + Name = "NewPlayer", + Level = 1 + }; + Log.Debug("PlayersForLevel Iter count: " + (anonViewIterRows != null ? anonViewIterRows.Count().ToString() : "null")); + Debug.Assert(anonViewIterRows != null && anonViewIterRows.Any()); + Log.Debug("Validating Anonymous View row data " + + $"Id={expectedPlayerAndLevel.Id}, Identity={expectedPlayerAndLevel.Identity}, Name={expectedPlayerAndLevel.Name}, Level={expectedPlayerAndLevel.Level} => " + + $"Id={anonViewIterRows.First().Id}, Identity={anonViewIterRows.First().Identity}, Name={anonViewIterRows.First().Name}, Level={anonViewIterRows.First().Level}"); + Debug.Assert(anonViewIterRows.First().Equals(expectedPlayerAndLevel)); + + Log.Debug("Calling RemoteQuery on Anonymous View"); + var anonViewRemoteQueryRows = context.Db.PlayersForLevel.RemoteQuery("WHERE Level = 1"); + Log.Debug("PlayersForLevel RemoteQuery count: " + (anonViewRemoteQueryRows != null ? anonViewRemoteQueryRows.Result.Length.ToString() : "null")); + Debug.Assert(anonViewRemoteQueryRows != null && anonViewRemoteQueryRows.Result.Length > 0); + Debug.Assert(anonViewRemoteQueryRows.Result.First().Equals(expectedPlayerAndLevel)); } System.AppDomain.CurrentDomain.UnhandledException += (sender, args) => diff --git a/sdks/csharp/examples~/regression-tests/client/module_bindings/Reducers/ClientConnected.g.cs b/sdks/csharp/examples~/regression-tests/client/module_bindings/Reducers/ClientConnected.g.cs new file mode 100644 index 00000000000..a23c796730b --- /dev/null +++ b/sdks/csharp/examples~/regression-tests/client/module_bindings/Reducers/ClientConnected.g.cs @@ -0,0 +1,48 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#nullable enable + +using System; +using SpacetimeDB.ClientApi; +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace SpacetimeDB.Types +{ + public sealed partial class RemoteReducers : RemoteBase + { + public delegate void ClientConnectedHandler(ReducerEventContext ctx); + public event ClientConnectedHandler? OnClientConnected; + + public bool InvokeClientConnected(ReducerEventContext ctx, Reducer.ClientConnected args) + { + if (OnClientConnected == null) + { + if (InternalOnUnhandledReducerError != null) + { + switch (ctx.Event.Status) + { + case Status.Failed(var reason): InternalOnUnhandledReducerError(ctx, new Exception(reason)); break; + case Status.OutOfEnergy(var _): InternalOnUnhandledReducerError(ctx, new Exception("out of energy")); break; + } + } + return false; + } + OnClientConnected( + ctx + ); + return true; + } + } + + public abstract partial class Reducer + { + [SpacetimeDB.Type] + [DataContract] + public sealed partial class ClientConnected : Reducer, IReducerArgs + { + string IReducerArgs.ReducerName => "ClientConnected"; + } + } +} diff --git a/sdks/csharp/examples~/regression-tests/client/module_bindings/SpacetimeDBClient.g.cs b/sdks/csharp/examples~/regression-tests/client/module_bindings/SpacetimeDBClient.g.cs index fedbbce0396..ab2eb58021f 100644 --- a/sdks/csharp/examples~/regression-tests/client/module_bindings/SpacetimeDBClient.g.cs +++ b/sdks/csharp/examples~/regression-tests/client/module_bindings/SpacetimeDBClient.g.cs @@ -1,7 +1,7 @@ // THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE // WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. -// This was generated using spacetimedb cli version 1.8.0 (commit 798b1c7909306e832723f507f7a3c97d6abc610d). +// This was generated using spacetimedb cli version 1.8.0 (commit cd36a957862ff4efc6086c28d04773f461406e6f). #nullable enable @@ -24,8 +24,10 @@ public sealed partial class RemoteTables : RemoteTablesBase public RemoteTables(DbConnection conn) { AddTable(ExampleData = new(conn)); - AddTable(GetAnonymousExampleDataById = new(conn)); - AddTable(GetExampleDataById = new(conn)); + AddTable(MyPlayer = new(conn)); + AddTable(Player = new(conn)); + AddTable(PlayerLevel = new(conn)); + AddTable(PlayersForLevel = new(conn)); } } @@ -470,6 +472,7 @@ protected override Reducer ToReducer(TransactionUpdate update) return update.ReducerCall.ReducerName switch { "Add" => BSATNHelpers.Decode(encodedArgs), + "ClientConnected" => BSATNHelpers.Decode(encodedArgs), "Delete" => BSATNHelpers.Decode(encodedArgs), "ThrowError" => BSATNHelpers.Decode(encodedArgs), "" => throw new SpacetimeDBEmptyReducerNameException("Reducer name is empty"), @@ -495,6 +498,7 @@ protected override bool Dispatch(IReducerEventContext context, Reducer reducer) return reducer switch { Reducer.Add args => Reducers.InvokeAdd(eventContext, args), + Reducer.ClientConnected args => Reducers.InvokeClientConnected(eventContext, args), Reducer.Delete args => Reducers.InvokeDelete(eventContext, args), Reducer.ThrowError args => Reducers.InvokeThrowError(eventContext, args), _ => throw new ArgumentOutOfRangeException("Reducer", $"Unknown reducer {reducer}") diff --git a/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/GetExampleDataById.g.cs b/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/MyPlayer.g.cs similarity index 57% rename from sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/GetExampleDataById.g.cs rename to sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/MyPlayer.g.cs index 7832fe7b6d7..86292847f92 100644 --- a/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/GetExampleDataById.g.cs +++ b/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/MyPlayer.g.cs @@ -13,15 +13,15 @@ namespace SpacetimeDB.Types { public sealed partial class RemoteTables { - public sealed class GetExampleDataByIdHandle : RemoteTableHandle + public sealed class MyPlayerHandle : RemoteTableHandle { - protected override string RemoteTableName => "GetExampleDataById"; + protected override string RemoteTableName => "MyPlayer"; - internal GetExampleDataByIdHandle(DbConnection conn) : base(conn) + internal MyPlayerHandle(DbConnection conn) : base(conn) { } } - public readonly GetExampleDataByIdHandle GetExampleDataById; + public readonly MyPlayerHandle MyPlayer; } } diff --git a/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/Player.g.cs b/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/Player.g.cs new file mode 100644 index 00000000000..7cc2b9b3316 --- /dev/null +++ b/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/Player.g.cs @@ -0,0 +1,49 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#nullable enable + +using System; +using SpacetimeDB.BSATN; +using SpacetimeDB.ClientApi; +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace SpacetimeDB.Types +{ + public sealed partial class RemoteTables + { + public sealed class PlayerHandle : RemoteTableHandle + { + protected override string RemoteTableName => "Player"; + + public sealed class IdUniqueIndex : UniqueIndexBase + { + protected override ulong GetKey(Player row) => row.Id; + + public IdUniqueIndex(PlayerHandle table) : base(table) { } + } + + public readonly IdUniqueIndex Id; + + public sealed class IdentityUniqueIndex : UniqueIndexBase + { + protected override SpacetimeDB.Identity GetKey(Player row) => row.Identity; + + public IdentityUniqueIndex(PlayerHandle table) : base(table) { } + } + + public readonly IdentityUniqueIndex Identity; + + internal PlayerHandle(DbConnection conn) : base(conn) + { + Id = new(this); + Identity = new(this); + } + + protected override object GetPrimaryKey(Player row) => row.Id; + } + + public readonly PlayerHandle Player; + } +} diff --git a/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/PlayerLevel.g.cs b/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/PlayerLevel.g.cs new file mode 100644 index 00000000000..01e5169593a --- /dev/null +++ b/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/PlayerLevel.g.cs @@ -0,0 +1,47 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#nullable enable + +using System; +using SpacetimeDB.BSATN; +using SpacetimeDB.ClientApi; +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace SpacetimeDB.Types +{ + public sealed partial class RemoteTables + { + public sealed class PlayerLevelHandle : RemoteTableHandle + { + protected override string RemoteTableName => "PlayerLevel"; + + public sealed class LevelIndex : BTreeIndexBase + { + protected override ulong GetKey(PlayerLevel row) => row.Level; + + public LevelIndex(PlayerLevelHandle table) : base(table) { } + } + + public readonly LevelIndex Level; + + public sealed class PlayerIdUniqueIndex : UniqueIndexBase + { + protected override ulong GetKey(PlayerLevel row) => row.PlayerId; + + public PlayerIdUniqueIndex(PlayerLevelHandle table) : base(table) { } + } + + public readonly PlayerIdUniqueIndex PlayerId; + + internal PlayerLevelHandle(DbConnection conn) : base(conn) + { + Level = new(this); + PlayerId = new(this); + } + } + + public readonly PlayerLevelHandle PlayerLevel; + } +} diff --git a/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/GetAnonymousExampleDataById.g.cs b/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/PlayersForLevel.g.cs similarity index 54% rename from sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/GetAnonymousExampleDataById.g.cs rename to sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/PlayersForLevel.g.cs index 0d9910a0fa8..5758613ad31 100644 --- a/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/GetAnonymousExampleDataById.g.cs +++ b/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/PlayersForLevel.g.cs @@ -13,15 +13,15 @@ namespace SpacetimeDB.Types { public sealed partial class RemoteTables { - public sealed class GetAnonymousExampleDataByIdHandle : RemoteTableHandle + public sealed class PlayersForLevelHandle : RemoteTableHandle { - protected override string RemoteTableName => "GetAnonymousExampleDataById"; + protected override string RemoteTableName => "PlayersForLevel"; - internal GetAnonymousExampleDataByIdHandle(DbConnection conn) : base(conn) + internal PlayersForLevelHandle(DbConnection conn) : base(conn) { } } - public readonly GetAnonymousExampleDataByIdHandle GetAnonymousExampleDataById; + public readonly PlayersForLevelHandle PlayersForLevel; } } diff --git a/sdks/csharp/examples~/regression-tests/client/module_bindings/Types/Player.g.cs b/sdks/csharp/examples~/regression-tests/client/module_bindings/Types/Player.g.cs new file mode 100644 index 00000000000..443f51cf68f --- /dev/null +++ b/sdks/csharp/examples~/regression-tests/client/module_bindings/Types/Player.g.cs @@ -0,0 +1,39 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace SpacetimeDB.Types +{ + [SpacetimeDB.Type] + [DataContract] + public sealed partial class Player + { + [DataMember(Name = "Id")] + public ulong Id; + [DataMember(Name = "Identity")] + public SpacetimeDB.Identity Identity; + [DataMember(Name = "Name")] + public string Name; + + public Player( + ulong Id, + SpacetimeDB.Identity Identity, + string Name + ) + { + this.Id = Id; + this.Identity = Identity; + this.Name = Name; + } + + public Player() + { + this.Name = ""; + } + } +} diff --git a/sdks/csharp/examples~/regression-tests/client/module_bindings/Types/PlayerAndLevel.g.cs b/sdks/csharp/examples~/regression-tests/client/module_bindings/Types/PlayerAndLevel.g.cs new file mode 100644 index 00000000000..f87a401f421 --- /dev/null +++ b/sdks/csharp/examples~/regression-tests/client/module_bindings/Types/PlayerAndLevel.g.cs @@ -0,0 +1,43 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace SpacetimeDB.Types +{ + [SpacetimeDB.Type] + [DataContract] + public sealed partial class PlayerAndLevel + { + [DataMember(Name = "Id")] + public ulong Id; + [DataMember(Name = "Identity")] + public SpacetimeDB.Identity Identity; + [DataMember(Name = "Name")] + public string Name; + [DataMember(Name = "Level")] + public ulong Level; + + public PlayerAndLevel( + ulong Id, + SpacetimeDB.Identity Identity, + string Name, + ulong Level + ) + { + this.Id = Id; + this.Identity = Identity; + this.Name = Name; + this.Level = Level; + } + + public PlayerAndLevel() + { + this.Name = ""; + } + } +} diff --git a/sdks/csharp/examples~/regression-tests/client/module_bindings/Types/PlayerLevel.g.cs b/sdks/csharp/examples~/regression-tests/client/module_bindings/Types/PlayerLevel.g.cs new file mode 100644 index 00000000000..8f9fb43e7f3 --- /dev/null +++ b/sdks/csharp/examples~/regression-tests/client/module_bindings/Types/PlayerLevel.g.cs @@ -0,0 +1,34 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace SpacetimeDB.Types +{ + [SpacetimeDB.Type] + [DataContract] + public sealed partial class PlayerLevel + { + [DataMember(Name = "PlayerId")] + public ulong PlayerId; + [DataMember(Name = "Level")] + public ulong Level; + + public PlayerLevel( + ulong PlayerId, + ulong Level + ) + { + this.PlayerId = PlayerId; + this.Level = Level; + } + + public PlayerLevel() + { + } + } +} diff --git a/sdks/csharp/examples~/regression-tests/republishing/client/module_bindings/SpacetimeDBClient.g.cs b/sdks/csharp/examples~/regression-tests/republishing/client/module_bindings/SpacetimeDBClient.g.cs index 7267aa9e29a..be596846a87 100644 --- a/sdks/csharp/examples~/regression-tests/republishing/client/module_bindings/SpacetimeDBClient.g.cs +++ b/sdks/csharp/examples~/regression-tests/republishing/client/module_bindings/SpacetimeDBClient.g.cs @@ -1,7 +1,7 @@ // THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE // WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. -// This was generated using spacetimedb cli version 1.8.0 (commit 31a8d5f1540fb11aef7db32f62e19b238fdaa797). +// This was generated using spacetimedb cli version 1.8.0 (commit cd36a957862ff4efc6086c28d04773f461406e6f). #nullable enable diff --git a/sdks/csharp/examples~/regression-tests/server/Lib.cs b/sdks/csharp/examples~/regression-tests/server/Lib.cs index 0cf7f3cfef0..303f293d82c 100644 --- a/sdks/csharp/examples~/regression-tests/server/Lib.cs +++ b/sdks/csharp/examples~/regression-tests/server/Lib.cs @@ -15,16 +15,65 @@ public partial struct ExampleData public uint Indexed; } - [SpacetimeDB.View(Name = "GetExampleDataById", Public = true)] - public static ExampleData? GetExampleDataById(ViewContext ctx)//, uint id) + [SpacetimeDB.Table(Name = "Player", Public = true)] + public partial struct Player { - return ctx.Db.ExampleData.Id.Find(0); + [SpacetimeDB.PrimaryKey] + [SpacetimeDB.AutoInc] + public ulong Id; + + [SpacetimeDB.Unique] + public Identity Identity; + + public string Name; + } + + [SpacetimeDB.Table(Name = "PlayerLevel", Public = true)] + public partial struct PlayerLevel + { + [SpacetimeDB.Unique] + public ulong PlayerId; + + [SpacetimeDB.Index.BTree] + public ulong Level; + } + + [SpacetimeDB.Type] + public partial struct PlayerAndLevel + { + public ulong Id; + public Identity Identity; + public string Name; + public ulong Level; + } + + // At-most-one row: return T? + [SpacetimeDB.View(Name = "MyPlayer", Public = true)] + public static Player? MyPlayer(ViewContext ctx) + { + return ctx.Db.Player.Identity.Find(ctx.Sender) as Player?; } - [SpacetimeDB.View(Name = "GetAnonymousExampleDataById", Public = true)] - public static ExampleData? GetAnonymousExampleDataById(AnonymousViewContext ctx) //, uint id) + // Multiple rows: return a list + [SpacetimeDB.View(Name = "PlayersForLevel", Public = true)] + public static List PlayersForLevel(AnonymousViewContext ctx) { - return ctx.Db.ExampleData.Id.Find(0); + var rows = new List(); + foreach (var player in ctx.Db.PlayerLevel.Level.Filter(1)) + { + if (ctx.Db.Player.Id.Find(player.PlayerId) is Player p) + { + var row = new PlayerAndLevel + { + Id = p.Id, + Identity = p.Identity, + Name = p.Name, + Level = player.Level + }; + rows.Add(row); + } + } + return rows; } [SpacetimeDB.Reducer] @@ -44,4 +93,22 @@ public static void ThrowError(ReducerContext ctx, string error) { throw new Exception(error); } + + [Reducer(ReducerKind.ClientConnected)] + public static void ClientConnected(ReducerContext ctx) + { + Log.Info($"Connect {ctx.Sender}"); + + if (ctx.Db.Player.Identity.Find(ctx.Sender) is Player player) + { + // We are not logging player login status, so do nothing + } + else + { + // Lets setup a new player with a level of 1 + ctx.Db.Player.Insert(new Player { Identity = ctx.Sender, Name = "NewPlayer" }); + var playerId = (ctx.Db.Player.Identity.Find(ctx.Sender)!).Value.Id; + ctx.Db.PlayerLevel.Insert(new PlayerLevel { PlayerId = playerId, Level = 1 }); + } + } }