From 0a4336b7a60631cf8df57fb168eae72f5a14a110 Mon Sep 17 00:00:00 2001 From: James Gilles Date: Tue, 15 Jul 2025 15:39:40 -0400 Subject: [PATCH 1/2] Add internal docs for C# bindings packages --- .../bindings-csharp/BSATN.Codegen/README.md | 69 +++++++++++++++++++ .../BSATN.Runtime.Tests/Tests.cs | 9 ++- .../bindings-csharp/BSATN.Runtime/README.md | 19 +++++ crates/bindings-csharp/Codegen.Tests/Tests.cs | 10 +++ crates/bindings-csharp/Codegen/README.md | 35 +++------- crates/bindings-csharp/README.md | 24 ++++++- crates/bindings-csharp/Runtime/README.md | 10 ++- 7 files changed, 144 insertions(+), 32 deletions(-) create mode 100644 crates/bindings-csharp/BSATN.Codegen/README.md create mode 100644 crates/bindings-csharp/BSATN.Runtime/README.md diff --git a/crates/bindings-csharp/BSATN.Codegen/README.md b/crates/bindings-csharp/BSATN.Codegen/README.md new file mode 100644 index 00000000000..ddcb2881867 --- /dev/null +++ b/crates/bindings-csharp/BSATN.Codegen/README.md @@ -0,0 +1,69 @@ +> ⚠️ **Internal Project** ⚠️ +> +> This project is intended for internal use only. It is **not** stable and may change without notice. + +## Internal documentation + +This project contains Roslyn [incremental source generators](https://github.com/dotnet/roslyn/blob/main/docs/features/incremental-generators.md) that augment types with methods for self-describing and serialization. It relies on the [BSATN.Runtime](../BSATN.Runtime/) library in the generated code. + +This project provides `[SpacetimeDB.Type]`. This attribute makes types self-describing, allowing them to automatically register their structure with SpacetimeDB. It also generates serialization code to the [BSATN format](https://spacetimedb.com/docs/bsatn). Any C# type annotated with `[SpacetimeDB.Type]` can be used as a table column or reducer argument. + +Any `[SpacetimeDB.Type]` must be marked `partial` to allow the generated code to add new functionality. + +`[SpacetimeDB.Type]` also supports emulation of tagged enums in C#. For that, the struct needs to inherit a marker interface `SpacetimeDB.TaggedEnum` where `Variants` is a named tuple of all possible variants, e.g.: + +```csharp +[SpacetimeDB.Type] +partial record Option : SpacetimeDB.TaggedEnum<(T Some, Unit None)>; +``` + +will generate inherited records `Option.Some(T Some_)` and `Option.None(Unit None_)`. It allows +you to use tagged enums in C# in a similar way to Rust enums by leveraging C# pattern-matching +on any instance of `Option`. + +## What is generated + +See [`../Codegen.Tests/fixtures/client/snapshots`](../Codegen.Tests/fixtures/client/snapshots/) for examples of the generated code. +[`../Codegen.Tests/fixtures/server/snapshots`](../Codegen.Tests/fixtures/server/snapshots/) also has examples, those filenames starting with `Type#`. +In addition, in any project using this library, you can set `true` in the `` of your `.csproj` to see exactly what code is geing generated for your project. + +`[SpacetimeDB.Type]` automatically generates correct `Equals`, `GetHashCode`, and `ToString` methods for the type. It also generates serialization code. + +Any `[SpacetimeDB.Type]` will have an auto-generated member struct named `BSATN`. This struct is zero-sized and implements the interface `SpacetimeDB.BSATN.IReadWrite` interface. This is used to serialize and deserialize elements of the struct. + +```csharp +[SpacetimeDB.Type] +partial struct Banana { + public int Freshness; + public int LengthMeters; +} + +void Example(System.IO.BinaryReader reader, System.IO.BinaryWriter writer) { + Banana.BSATN serializer = new(); + Banana banana1 = serializer.Read(reader); // read a BSATN-encoded Banana from the reader. + Banana banana2 = serializer.Read(reader); + Console.Log($"bananas: {banana1} {banana2}"); + Console.Log($"equal?: {banana1.Equals(banana2)}"); + serializer.write(writer, banana2); // write a BSATN-encoded Banana to the writer. + serializer.write(writer, banana1); +} +``` + +Since `Banana.BSATN` takes up no space in memory, allocating one is free. We use this pattern because the C# versions we target don't support static interface methods. + +`[SpacetimeDB.Type]`s that do not inherit from `SpacetimeDB.TaggedEnum` implement an additional interface, `IStructuralReadWrite`. This allows them to be read without using a serializer. (This is not possible for `TaggedEnum`s because their concrete type is not known before deserialization.) + +```csharp +void Example(System.IO.BinaryReader reader, System.IO.BinaryWriter writer) { + Banana banana = new(); // has default field values. + banana.ReadFields(reader); // now it is initialized. + banana.WriteFields(writer); // and we can write it out directly as well. +} +``` + +The `IReadWrite` interface has an additional method, `AlgebraicType GetAlgebraicType()`. This returns a description of the type that is used during module initialization; see [`../Runtime`](../Runtime/) for more information. + +## Testing +The testing for this project lives in two places. +- [`../Codegen.Tests`](../Codegen.Tests/) contains snapshot-based tests. These verify that the generated code looks as expected and allow it to be reviewed more easily. +- Randomized runtime tests live in [`../BSATN.Runtime.Tests`](../BSATN.Runtime.Tests/). These tests randomly fuzz the generated serializers for a variety of types. \ No newline at end of file diff --git a/crates/bindings-csharp/BSATN.Runtime.Tests/Tests.cs b/crates/bindings-csharp/BSATN.Runtime.Tests/Tests.cs index c22879a596c..726070e3494 100644 --- a/crates/bindings-csharp/BSATN.Runtime.Tests/Tests.cs +++ b/crates/bindings-csharp/BSATN.Runtime.Tests/Tests.cs @@ -5,6 +5,12 @@ namespace SpacetimeDB; using SpacetimeDB.BSATN; using Xunit; +/// +/// Unit and randomized tests for the BSATN.Runtime library. Some tests here also test BSATN.Codegen. +/// +/// Randomized tests use the CsCheck library to generate large numbers of sample inputs and check that these libraries +/// maintain certain invariants for all of them. +/// public static partial class BSATNRuntimeTests { [Fact] @@ -444,7 +450,8 @@ public partial record BasicEnum BasicDataClass U, BasicDataStruct V, BasicDataRecord W - )> { } + )> + { } static readonly Gen GenBasicEnum = Gen.SelectMany( Gen.Int[0, 7], diff --git a/crates/bindings-csharp/BSATN.Runtime/README.md b/crates/bindings-csharp/BSATN.Runtime/README.md new file mode 100644 index 00000000000..ee26c0e264d --- /dev/null +++ b/crates/bindings-csharp/BSATN.Runtime/README.md @@ -0,0 +1,19 @@ +> ⚠️ **Internal Project** ⚠️ +> +> This project is intended for internal use only. It is **not** stable and may change without notice. + +## Internal documentation + +This project contains interfaces and runtime support code for [BSATN serialization](https://spacetimedb.com/docs/bsatn) in C#. It is the companion of the [BSATN.Codegen](../BSATN.Codegen/) project. + +See [`../BSATN.Runtime.Tests/`](../BSATN.Runtime.Tests/) for tests. + +### User-facing types + +This project contains implementations of a number of wide integer types for compatibility with SpacetimeDB. It also has implementations of a number of "special" SpacetimeDB types, including `Identity`, `ConnectionId`, `Timestamp`, `TimeDuration`, and `ScheduleAt`. These live in [`./Builtins.cs`](./Builtins.cs). It also contains the `AlgebraicType` type, which is not really user-facing but is important internally. + +There are also two of important interfaces: `IStructuralReadWrite` and `IReadWrite`. See their documentation in [`./BSATN/Runtime.cs`](./BSATN/Runtime.cs) for more information. + +### Internal types + +This project contains the base implementation of serializers for various primitive BSATN types. These live in [`./BSATN/Runtime.cs`](./BSATN/Runtime.cs). These serializers are mainly used in code generated by `BSATN.Codegen`. \ No newline at end of file diff --git a/crates/bindings-csharp/Codegen.Tests/Tests.cs b/crates/bindings-csharp/Codegen.Tests/Tests.cs index d79533ba1d1..e4f7ed9cd76 100644 --- a/crates/bindings-csharp/Codegen.Tests/Tests.cs +++ b/crates/bindings-csharp/Codegen.Tests/Tests.cs @@ -7,6 +7,16 @@ namespace SpacetimeDB.Codegen.Tests; using Microsoft.CodeAnalysis.MSBuild; using Microsoft.CodeAnalysis.Text; +/// +/// Snapshot tests for the SpacetimeDB.Codegen library. +/// +/// These run code generation for the sample projects in fixtures. We compare the generated code +/// to known-good examples of generated code using the Verify library: https://github.com/VerifyTests/Verify +/// +/// If you need to update the generated code, you probably want to install the Verify.Terminal tool: https://github.com/VerifyTests/Verify.Terminal +/// Run dotnet tool restore; dotnet verify accept after changing the code generation to compare the old and new generated code and approve it. +/// You'll need to check the updated snapshots into Git with your PR; the .gitignores in this project are set up to add the right files. +/// public static class GeneratorSnapshotTests { // Note that we can't use assembly path here because it will be put in some deep nested folder. diff --git a/crates/bindings-csharp/Codegen/README.md b/crates/bindings-csharp/Codegen/README.md index 1224669fb10..5e6b952d08c 100644 --- a/crates/bindings-csharp/Codegen/README.md +++ b/crates/bindings-csharp/Codegen/README.md @@ -1,36 +1,17 @@ -# SpacetimeDB.Codegen +> ⚠️ **Internal Project** ⚠️ +> +> This project is intended for internal use only. It is **not** stable and may change without notice. -This project contains Roslyn [incremental source generators](https://github.com/dotnet/roslyn/blob/main/docs/features/incremental-generators.md) that augment types and tables with static methods for self-describing and registration. They look for different attributes to know which types to augment: +See the [C# module library reference](https://spacetimedb.com/docs/modules/c-sharp) for stable, user-facing documentation. -- `[SpacetimeDB.Type]` - generates a `GetSatsTypeInfo()` static method that registers this type with the runtime and returns a `TypeInfo` object. It supports only `struct`s for now to explicitly forbid infinitely recursive types and to make the implementation simpler, as it doesn't need to deal with type references - each table is registered as an entirely self-contained type together with its nested structs if any. This is unlikely to be a problem in common scenarios, but it will be optimised in the future. +## Internal documentation - All the nested fields will be added to the product type. Because it's not possible to implement static extension methods on 3rd-party types (including built-ins) in C#, the codegen is responsible for manually routing different types to their `TypeInfo` descriptors. See various static `TypeInfo` properties and helper methods on `SpacetimeDB.BSATN.AlgebraicType` (`Runtime/AlgebraicType.cs`) and routing logic in `Utils.GetTypeInfo` (`Codegen/Utils.cs`) for more details. +This project contains Roslyn [incremental source generators](https://github.com/dotnet/roslyn/blob/main/docs/features/incremental-generators.md) that augment tables and reducers with static methods for self-describing and registration. - Also, for the same reason - absence of static extension methods in C# - the codegen expects that your struct, as well as any of its parents, is `partial` so methods can be added from extra source files generated by the codegen. +SpacetimeDB modules are compiled to WebAssembly modules that expose a specific interface; see the [module ABI reference](https://spacetimedb.com/docs/webassembly-abi). This interface is implemented in the generated `FFI` class; see [`../Codegen.Tests/fixtures/server/snapshots/Module#FFI.verified.cs`](../Codegen.Tests/fixtures/server/snapshots/Module#FFI.verified.cs) for an example of what this generated code looks like. -- `[SpacetimeDB.Type]` - also supports emulation of tagged enums in C#. For that, the struct needs to inherit a marker interface `SpacetimeDB.TaggedEnum` where `Variants` is a named tuple of all possible variants, e.g.: - - ```csharp - [SpacetimeDB.Type] - partial record Option : SpacetimeDB.TaggedEnum<(T Some, Unit None)>; - ``` - - will generate inherited records `Option.Some(T Some_)` and `Option.None(Unit None_)`. It allows - you to use tagged enums in C# in a similar way to Rust enums by leveraging C# pattern-matching - on any instance of `Option`. +The source generators are implemented via several attributes usable in module code: - `[SpacetimeDB.Table]` - generates code to register this table in the `FFI` upon startup so that they can be enumerated by the `__describe_module__` FFI API. It implies `[SpacetimeDB.Type]`, so you must not specify both attributes on the same struct. - The fields can be marked with `[SpacetimeDB.ColumnAttrs]` and those will be detected by the codegen and passed on to the runtime as well. Example: - - ```csharp - [SpacetimeDB.Table] - public partial struct Person - { - [SpacetimeDB.Column(ColumnAttrs.Identity)] - public int Id; - public string Name; - } - ``` - - `[SpacetimeDB.Reducer]` - generates code to register a static function as a SpacetimeDB reducer in the `FFI` upon startup and creates a wrapper that will parse SATS binary blob into individual arguments and invoke the underlying function for the `__call_reducer__` FFI API. diff --git a/crates/bindings-csharp/README.md b/crates/bindings-csharp/README.md index 21789b2f649..449d6948d17 100644 --- a/crates/bindings-csharp/README.md +++ b/crates/bindings-csharp/README.md @@ -1,5 +1,23 @@ -# SpacetimeDB +> ⚠️ **Unstable Project** ⚠️ +> +> The interface of this project is **not** stable and may change without notice. -These projects contain the SpacetimeDB SATS typesystem, codegen and runtime bindings for SpacetimeDB WebAssembly modules. +See the [C# module library reference](https://spacetimedb.com/docs/modules/c-sharp) and the [C# client SDK reference](https://spacetimedb.com/docs/sdks/c-sharp) for stable, user-facing documentation. + +## Internal documentation + +These projects contain the SpacetimeDB SATS typesystem, codegen and runtime bindings for SpacetimeDB WebAssembly modules. It also contains serialization code for SpacetimeDB C# clients. + +See the + +The [`BSATN.Codegen`](./BSATN.Codegen/) and [`BSATN.Runtime`](./BSATN.Runtime/) libraries are used by: +- C# Modules +- and C# Client applications. + +Together they provide serialization and deserialization to the BSATN format. See their READMEs for more information. + +The [`Codegen`](./Codegen/) and [`Runtime`](./Runtime/) libraries are used: +- only by C# Modules. + +They provide all of the functionality needed to write SpacetimeDB modules in C#. See their READMEs for more information. -Please refer to documentation inside `Codegen` and `Runtime` folders for more details. diff --git a/crates/bindings-csharp/Runtime/README.md b/crates/bindings-csharp/Runtime/README.md index 336cce40169..baea0bee302 100644 --- a/crates/bindings-csharp/Runtime/README.md +++ b/crates/bindings-csharp/Runtime/README.md @@ -1,6 +1,12 @@ +> ⚠️ **Internal Project** ⚠️ +> +> This project is intended for internal use only. It is **not** stable and may change without notice. + # SpacetimeDB.Runtime -This project contains the core SpacetimeDB SATS typesystem, attributes for the codegen as well as runtime bindings for SpacetimeDB WebAssembly modules. +This project contains the runtime bindings for SpacetimeDB WebAssembly modules. See the [C# module library reference](https://spacetimedb.com/docs/modules/c-sharp) for stable, user-facing documentation. + +SpacetimeDB modules are compiled to WebAssembly modules that expose a specific interface; see the [module ABI reference](https://spacetimedb.com/docs/webassembly-abi). The runtime bindings are currently implementing via `Wasi.Sdk` package, which is a .NET implementation of the [WASI](https://wasi.dev/) standard. This is likely to change in the future. @@ -21,3 +27,5 @@ To regenenerate the `Autogen` folder, run: ```sh cargo run -p spacetimedb-codegen --example regen-csharp-moduledef ``` + +This folder contains the type definitions used to serialize the `RawModuleDef` that is returned by `__describe_module__`. \ No newline at end of file From ba4ded61ca4ab2bc47684ad80fe8439763ff6436 Mon Sep 17 00:00:00 2001 From: James Gilles Date: Tue, 15 Jul 2025 15:41:54 -0400 Subject: [PATCH 2/2] Csharpier --- crates/bindings-csharp/BSATN.Runtime.Tests/Tests.cs | 5 ++--- crates/bindings-csharp/Codegen.Tests/Tests.cs | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/crates/bindings-csharp/BSATN.Runtime.Tests/Tests.cs b/crates/bindings-csharp/BSATN.Runtime.Tests/Tests.cs index 726070e3494..b3aad13fc5a 100644 --- a/crates/bindings-csharp/BSATN.Runtime.Tests/Tests.cs +++ b/crates/bindings-csharp/BSATN.Runtime.Tests/Tests.cs @@ -7,7 +7,7 @@ namespace SpacetimeDB; /// /// Unit and randomized tests for the BSATN.Runtime library. Some tests here also test BSATN.Codegen. -/// +/// /// Randomized tests use the CsCheck library to generate large numbers of sample inputs and check that these libraries /// maintain certain invariants for all of them. /// @@ -450,8 +450,7 @@ public partial record BasicEnum BasicDataClass U, BasicDataStruct V, BasicDataRecord W - )> - { } + )> { } static readonly Gen GenBasicEnum = Gen.SelectMany( Gen.Int[0, 7], diff --git a/crates/bindings-csharp/Codegen.Tests/Tests.cs b/crates/bindings-csharp/Codegen.Tests/Tests.cs index e4f7ed9cd76..9a8ecaf1968 100644 --- a/crates/bindings-csharp/Codegen.Tests/Tests.cs +++ b/crates/bindings-csharp/Codegen.Tests/Tests.cs @@ -9,10 +9,10 @@ namespace SpacetimeDB.Codegen.Tests; /// /// Snapshot tests for the SpacetimeDB.Codegen library. -/// +/// /// These run code generation for the sample projects in fixtures. We compare the generated code /// to known-good examples of generated code using the Verify library: https://github.com/VerifyTests/Verify -/// +/// /// If you need to update the generated code, you probably want to install the Verify.Terminal tool: https://github.com/VerifyTests/Verify.Terminal /// Run dotnet tool restore; dotnet verify accept after changing the code generation to compare the old and new generated code and approve it. /// You'll need to check the updated snapshots into Git with your PR; the .gitignores in this project are set up to add the right files.