|
| 1 | +> ⚠️ **Internal Project** ⚠️ |
| 2 | +> |
| 3 | +> This project is intended for internal use only. It is **not** stable and may change without notice. |
| 4 | +
|
| 5 | +## Internal documentation |
| 6 | + |
| 7 | +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. |
| 8 | + |
| 9 | +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. |
| 10 | + |
| 11 | +Any `[SpacetimeDB.Type]` must be marked `partial` to allow the generated code to add new functionality. |
| 12 | + |
| 13 | +`[SpacetimeDB.Type]` also supports emulation of tagged enums in C#. For that, the struct needs to inherit a marker interface `SpacetimeDB.TaggedEnum<Variants>` where `Variants` is a named tuple of all possible variants, e.g.: |
| 14 | + |
| 15 | +```csharp |
| 16 | +[SpacetimeDB.Type] |
| 17 | +partial record Option<T> : SpacetimeDB.TaggedEnum<(T Some, Unit None)>; |
| 18 | +``` |
| 19 | + |
| 20 | +will generate inherited records `Option.Some(T Some_)` and `Option.None(Unit None_)`. It allows |
| 21 | +you to use tagged enums in C# in a similar way to Rust enums by leveraging C# pattern-matching |
| 22 | +on any instance of `Option<T>`. |
| 23 | + |
| 24 | +## What is generated |
| 25 | + |
| 26 | +See [`../Codegen.Tests/fixtures/client/snapshots`](../Codegen.Tests/fixtures/client/snapshots/) for examples of the generated code. |
| 27 | +[`../Codegen.Tests/fixtures/server/snapshots`](../Codegen.Tests/fixtures/server/snapshots/) also has examples, those filenames starting with `Type#`. |
| 28 | +In addition, in any project using this library, you can set `<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>` in the `<PropertyGroup>` of your `.csproj` to see exactly what code is geing generated for your project. |
| 29 | + |
| 30 | +`[SpacetimeDB.Type]` automatically generates correct `Equals`, `GetHashCode`, and `ToString` methods for the type. It also generates serialization code. |
| 31 | + |
| 32 | +Any `[SpacetimeDB.Type]` will have an auto-generated member struct named `BSATN`. This struct is zero-sized and implements the interface `SpacetimeDB.BSATN.IReadWrite<T>` interface. This is used to serialize and deserialize elements of the struct. |
| 33 | + |
| 34 | +```csharp |
| 35 | +[SpacetimeDB.Type] |
| 36 | +partial struct Banana { |
| 37 | + public int Freshness; |
| 38 | + public int LengthMeters; |
| 39 | +} |
| 40 | + |
| 41 | +void Example(System.IO.BinaryReader reader, System.IO.BinaryWriter writer) { |
| 42 | + Banana.BSATN serializer = new(); |
| 43 | + Banana banana1 = serializer.Read(reader); // read a BSATN-encoded Banana from the reader. |
| 44 | + Banana banana2 = serializer.Read(reader); |
| 45 | + Console.Log($"bananas: {banana1} {banana2}"); |
| 46 | + Console.Log($"equal?: {banana1.Equals(banana2)}"); |
| 47 | + serializer.write(writer, banana2); // write a BSATN-encoded Banana to the writer. |
| 48 | + serializer.write(writer, banana1); |
| 49 | +} |
| 50 | +``` |
| 51 | + |
| 52 | +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. |
| 53 | + |
| 54 | +`[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.) |
| 55 | + |
| 56 | +```csharp |
| 57 | +void Example(System.IO.BinaryReader reader, System.IO.BinaryWriter writer) { |
| 58 | + Banana banana = new(); // has default field values. |
| 59 | + banana.ReadFields(reader); // now it is initialized. |
| 60 | + banana.WriteFields(writer); // and we can write it out directly as well. |
| 61 | +} |
| 62 | +``` |
| 63 | + |
| 64 | +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. |
| 65 | + |
| 66 | +## Testing |
| 67 | +The testing for this project lives in two places. |
| 68 | +- [`../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. |
| 69 | +- Randomized runtime tests live in [`../BSATN.Runtime.Tests`](../BSATN.Runtime.Tests/). These tests randomly fuzz the generated serializers for a variety of types. |
0 commit comments