diff --git a/crates/cli/src/subcommands/generate/csharp.rs b/crates/cli/src/subcommands/generate/csharp.rs index 1d50eb085b1..52cba3151f4 100644 --- a/crates/cli/src/subcommands/generate/csharp.rs +++ b/crates/cli/src/subcommands/generate/csharp.rs @@ -4,7 +4,7 @@ use convert_case::{Case, Casing}; use spacetimedb_lib::sats::{ AlgebraicType, AlgebraicType::Builtin, AlgebraicTypeRef, ArrayType, BuiltinType, MapType, ProductType, SumType, }; -use spacetimedb_lib::{ColumnIndexAttribute, ReducerDef, TableDef}; +use spacetimedb_lib::{ColumnIndexAttribute, ProductTypeElement, ReducerDef, TableDef}; use super::code_indenter::CodeIndenter; use super::{GenCtx, GenItem, INDENT}; @@ -579,6 +579,7 @@ fn autogen_csharp_product_table_common( writeln!(output).unwrap(); writeln!(output, "using System;").unwrap(); + writeln!(output, "using System.Collections.Generic;").unwrap(); if namespace != "SpacetimeDB" { writeln!(output, "using SpacetimeDB;").unwrap(); } @@ -636,6 +637,64 @@ fn autogen_csharp_product_table_common( writeln!(output).unwrap(); + // If this is a table, we want to generate indexes + if let Some(column_attrs) = column_attrs { + let indexed_fields: Vec<(&ProductTypeElement, String)> = column_attrs + .iter() + .enumerate() + .filter(|a| a.1.is_unique() || a.1.is_primary()) + .map(|a| &product_type.elements[a.0]) + .map(|f| (f, f.name.as_ref().unwrap().replace("r#", "").to_case(Case::Pascal))) + .collect(); + // Declare custom index dictionaries + for (field, field_name) in &indexed_fields { + let type_name = ty_fmt(ctx, &field.algebraic_type, namespace); + let comparer = if format!("{}", type_name) == "byte[]" { + ", new SpacetimeDB.ByteArrayComparer()" + } else { + "" + }; + writeln!( + output, + "private static Dictionary<{type_name}, {name}> {field_name}_Index = new Dictionary<{type_name}, {name}>(16{comparer});" + ) + .unwrap(); + } + writeln!(output).unwrap(); + // OnInsert method for updating indexes + writeln!( + output, + "private static void InternalOnValueInserted(object insertedValue)" + ) + .unwrap(); + writeln!(output, "{{").unwrap(); + { + indent_scope!(output); + writeln!(output, "var val = ({name})insertedValue;").unwrap(); + for (_, field_name) in &indexed_fields { + writeln!(output, "{field_name}_Index[val.{field_name}] = val;").unwrap(); + } + } + writeln!(output, "}}").unwrap(); + writeln!(output).unwrap(); + // OnDelete method for updating indexes + writeln!( + output, + "private static void InternalOnValueDeleted(object deletedValue)" + ) + .unwrap(); + writeln!(output, "{{").unwrap(); + { + indent_scope!(output); + writeln!(output, "var val = ({name})deletedValue;").unwrap(); + for (_, field_name) in &indexed_fields { + writeln!(output, "{field_name}_Index.Remove(val.{field_name});").unwrap(); + } + } + writeln!(output, "}}").unwrap(); + writeln!(output).unwrap(); + } // End indexes + writeln!( output, "public static SpacetimeDB.SATS.AlgebraicType GetAlgebraicType()" @@ -937,67 +996,76 @@ fn autogen_csharp_access_funcs_for_struct( writeln!(output, "{{").unwrap(); { indent_scope!(output); - writeln!( - output, - "foreach(var entry in NetworkManager.clientDB.GetEntries(\"{}\"))", - table_name - ) - .unwrap(); - writeln!(output, "{{").unwrap(); - { - indent_scope!(output); - writeln!(output, "var productValue = entry.Item1.AsProductValue();").unwrap(); + if is_unique || is_primary { writeln!( output, - "var compareValue = ({})productValue.elements[{}].As{}();", - csharp_field_type, col_i, field_type + "{csharp_field_name_pascal}_Index.TryGetValue(value, out var r);" ) .unwrap(); - if csharp_field_type == "byte[]" { + writeln!(output, "return r;").unwrap(); + } else { + writeln!( + output, + "foreach(var entry in NetworkManager.clientDB.GetEntries(\"{}\"))", + table_name + ) + .unwrap(); + writeln!(output, "{{").unwrap(); + { + indent_scope!(output); + writeln!(output, "var productValue = entry.Item1.AsProductValue();").unwrap(); writeln!( output, - "static bool ByteArrayCompare(byte[] a1, byte[] a2) -{{ - if (a1.Length != a2.Length) - return false; - - for (int i=0; i Vec<(String "public ClientApi.Event.Types.Status Status {{ get; private set; }}" ) .unwrap(); - writeln!(output, "private ReducerArgs Args;").unwrap(); + writeln!(output, "private object Args;").unwrap(); writeln!(output).unwrap(); - writeln!(output, "public ReducerEvent(ReducerType reducer, string reducerName, ulong timestamp, SpacetimeDB.Identity identity, string errMessage, ClientApi.Event.Types.Status status, ReducerArgs args)").unwrap(); + writeln!(output, "public ReducerEvent(ReducerType reducer, string reducerName, ulong timestamp, SpacetimeDB.Identity identity, string errMessage, ClientApi.Event.Types.Status status, object args)").unwrap(); writeln!(output, "{{").unwrap(); { indent_scope!(output); @@ -1432,7 +1498,7 @@ pub fn autogen_csharp_globals(items: &[GenItem], namespace: &str) -> Vec<(String { indent_scope!(output); writeln!(output, "if (Reducer != ReducerType.{reducer_name}) throw new SpacetimeDB.ReducerMismatchException(Reducer.ToString(), \"{reducer_name}\");").unwrap(); - writeln!(output, "return Args.{reducer_name}Args;").unwrap(); + writeln!(output, "return ({reducer_name}ArgsStruct)Args;").unwrap(); } // Closing brace for struct ReducerArgs writeln!(output, "}}").unwrap(); @@ -1484,25 +1550,6 @@ pub fn autogen_csharp_globals(items: &[GenItem], namespace: &str) -> Vec<(String // Closing brace for ReducerEvent writeln!(output, "}}").unwrap(); - writeln!( - output, - "[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Explicit)]" - ) - .unwrap(); - writeln!(output, "public partial struct ReducerArgs").unwrap(); - writeln!(output, "{{").unwrap(); - { - indent_scope!(output); - for reducer in &reducers { - let reducer_name = reducer.name.to_case(Case::Pascal); - writeln!(output, "[System.Runtime.InteropServices.FieldOffset(0)]").unwrap(); - writeln!(output, "public {reducer_name}ArgsStruct {reducer_name}Args;").unwrap(); - } - } - // Closing brace for struct ReducerArgs - writeln!(output, "}}").unwrap(); - writeln!(output).unwrap(); - if use_namespace { output.dedent(1); writeln!(output, "}}").unwrap(); diff --git a/crates/cli/tests/snapshots/codegen__codegen_output.snap b/crates/cli/tests/snapshots/codegen__codegen_output.snap index 9c8b27b0aa3..b3993b96ef5 100644 --- a/crates/cli/tests/snapshots/codegen__codegen_output.snap +++ b/crates/cli/tests/snapshots/codegen__codegen_output.snap @@ -58,13 +58,11 @@ namespace SpacetimeDB using var reader = new System.IO.BinaryReader(ms); var args_0_value = SpacetimeDB.SATS.AlgebraicValue.Deserialize(SpacetimeDB.SATS.AlgebraicType.CreatePrimitiveType(SpacetimeDB.SATS.BuiltinType.Type.String), reader); args.Name = args_0_value.AsString(); - var argsGeneric = new ReducerArgs(); - argsGeneric.AddPlayerArgs = args; - dbEvent.FunctionCall.CallInfo = new ReducerEvent(ReducerType.AddPlayer, "add_player", dbEvent.Timestamp, Identity.From(dbEvent.CallerIdentity.ToByteArray()), dbEvent.Message, dbEvent.Status, argsGeneric); + dbEvent.FunctionCall.CallInfo = new ReducerEvent(ReducerType.AddPlayer, "add_player", dbEvent.Timestamp, Identity.From(dbEvent.CallerIdentity.ToByteArray()), dbEvent.Message, dbEvent.Status, args); } } - public struct AddPlayerArgsStruct + public class AddPlayerArgsStruct { public string Name; } @@ -143,9 +141,9 @@ namespace SpacetimeDB public SpacetimeDB.Identity Identity { get; private set; } public string ErrMessage { get; private set; } public ClientApi.Event.Types.Status Status { get; private set; } - private ReducerArgs Args; + private object Args; - public ReducerEvent(ReducerType reducer, string reducerName, ulong timestamp, SpacetimeDB.Identity identity, string errMessage, ClientApi.Event.Types.Status status, ReducerArgs args) + public ReducerEvent(ReducerType reducer, string reducerName, ulong timestamp, SpacetimeDB.Identity identity, string errMessage, ClientApi.Event.Types.Status status, object args) { Reducer = reducer; ReducerName = reducerName; @@ -161,7 +159,7 @@ namespace SpacetimeDB get { if (Reducer != ReducerType.Update) throw new SpacetimeDB.ReducerMismatchException(Reducer.ToString(), "Update"); - return Args.UpdateArgs; + return (UpdateArgsStruct)Args; } } public AddPlayerArgsStruct AddPlayerArgs @@ -169,7 +167,7 @@ namespace SpacetimeDB get { if (Reducer != ReducerType.AddPlayer) throw new SpacetimeDB.ReducerMismatchException(Reducer.ToString(), "AddPlayer"); - return Args.AddPlayerArgs; + return (AddPlayerArgsStruct)Args; } } public RepeatingTestArgsStruct RepeatingTestArgs @@ -177,7 +175,7 @@ namespace SpacetimeDB get { if (Reducer != ReducerType.RepeatingTest) throw new SpacetimeDB.ReducerMismatchException(Reducer.ToString(), "RepeatingTest"); - return Args.RepeatingTestArgs; + return (RepeatingTestArgsStruct)Args; } } public TestArgsStruct TestArgs @@ -185,7 +183,7 @@ namespace SpacetimeDB get { if (Reducer != ReducerType.Test) throw new SpacetimeDB.ReducerMismatchException(Reducer.ToString(), "Test"); - return Args.TestArgs; + return (TestArgsStruct)Args; } } @@ -226,18 +224,6 @@ namespace SpacetimeDB } } } - [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Explicit)] - public partial struct ReducerArgs - { - [System.Runtime.InteropServices.FieldOffset(0)] - public UpdateArgsStruct UpdateArgs; - [System.Runtime.InteropServices.FieldOffset(0)] - public AddPlayerArgsStruct AddPlayerArgs; - [System.Runtime.InteropServices.FieldOffset(0)] - public RepeatingTestArgsStruct RepeatingTestArgs; - [System.Runtime.InteropServices.FieldOffset(0)] - public TestArgsStruct TestArgs; - } } ''' "RepeatingTestReducer.cs" = ''' @@ -296,13 +282,11 @@ namespace SpacetimeDB using var reader = new System.IO.BinaryReader(ms); var args_0_value = SpacetimeDB.SATS.AlgebraicValue.Deserialize(SpacetimeDB.SATS.AlgebraicType.CreatePrimitiveType(SpacetimeDB.SATS.BuiltinType.Type.U64), reader); args.PrevTime = args_0_value.AsU64(); - var argsGeneric = new ReducerArgs(); - argsGeneric.RepeatingTestArgs = args; - dbEvent.FunctionCall.CallInfo = new ReducerEvent(ReducerType.RepeatingTest, "repeating_test", dbEvent.Timestamp, Identity.From(dbEvent.CallerIdentity.ToByteArray()), dbEvent.Message, dbEvent.Status, argsGeneric); + dbEvent.FunctionCall.CallInfo = new ReducerEvent(ReducerType.RepeatingTest, "repeating_test", dbEvent.Timestamp, Identity.From(dbEvent.CallerIdentity.ToByteArray()), dbEvent.Message, dbEvent.Status, args); } } - public struct RepeatingTestArgsStruct + public class RepeatingTestArgsStruct { public ulong PrevTime; } @@ -313,6 +297,7 @@ namespace SpacetimeDB // WILL NOT BE SAVED. MODIFY TABLES IN RUST INSTEAD. using System; +using System.Collections.Generic; namespace SpacetimeDB { @@ -443,6 +428,7 @@ namespace SpacetimeDB // WILL NOT BE SAVED. MODIFY TABLES IN RUST INSTEAD. using System; +using System.Collections.Generic; namespace SpacetimeDB { @@ -479,6 +465,7 @@ namespace SpacetimeDB // WILL NOT BE SAVED. MODIFY TABLES IN RUST INSTEAD. using System; +using System.Collections.Generic; namespace SpacetimeDB { @@ -574,6 +561,7 @@ namespace SpacetimeDB using System; namespace SpacetimeDB +using System.Collections.Generic; { public partial class TestE : IDatabaseTable { @@ -746,13 +734,11 @@ namespace SpacetimeDB args.Arg2 = (SpacetimeDB.TestB)(args_1_value); var args_2_value = SpacetimeDB.SATS.AlgebraicValue.Deserialize(SpacetimeDB.Namespace.GetAlgebraicTypeForTestC(), reader); args.Arg3 = SpacetimeDB.Namespace.IntoTestC(args_2_value); - var argsGeneric = new ReducerArgs(); - argsGeneric.TestArgs = args; - dbEvent.FunctionCall.CallInfo = new ReducerEvent(ReducerType.Test, "test", dbEvent.Timestamp, Identity.From(dbEvent.CallerIdentity.ToByteArray()), dbEvent.Message, dbEvent.Status, argsGeneric); + dbEvent.FunctionCall.CallInfo = new ReducerEvent(ReducerType.Test, "test", dbEvent.Timestamp, Identity.From(dbEvent.CallerIdentity.ToByteArray()), dbEvent.Message, dbEvent.Status, args); } } - public struct TestArgsStruct + public class TestArgsStruct { public SpacetimeDB.TestA Arg; public SpacetimeDB.TestB Arg2; @@ -813,13 +799,11 @@ namespace SpacetimeDB bsatnBytes.CopyTo(ms.GetBuffer(), 0); ms.Position = 0; using var reader = new System.IO.BinaryReader(ms); - var argsGeneric = new ReducerArgs(); - argsGeneric.UpdateArgs = args; - dbEvent.FunctionCall.CallInfo = new ReducerEvent(ReducerType.Update, "update", dbEvent.Timestamp, Identity.From(dbEvent.CallerIdentity.ToByteArray()), dbEvent.Message, dbEvent.Status, argsGeneric); + dbEvent.FunctionCall.CallInfo = new ReducerEvent(ReducerType.Update, "update", dbEvent.Timestamp, Identity.From(dbEvent.CallerIdentity.ToByteArray()), dbEvent.Message, dbEvent.Status, args); } } - public struct UpdateArgsStruct + public class UpdateArgsStruct { } }