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..b3aad13fc5a 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] 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..9a8ecaf1968 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