Skip to content

Add internal docs for C# bindings packages #2938

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions crates/bindings-csharp/BSATN.Codegen/README.md
Original file line number Diff line number Diff line change
@@ -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<Variants>` where `Variants` is a named tuple of all possible variants, e.g.:

```csharp
[SpacetimeDB.Type]
partial record Option<T> : 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<T>`.

## 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 `<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>` in the `<PropertyGroup>` 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<T>` 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.
6 changes: 6 additions & 0 deletions crates/bindings-csharp/BSATN.Runtime.Tests/Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ namespace SpacetimeDB;
using SpacetimeDB.BSATN;
using Xunit;

/// <summary>
/// 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.
/// </summary>
public static partial class BSATNRuntimeTests
{
[Fact]
Expand Down
19 changes: 19 additions & 0 deletions crates/bindings-csharp/BSATN.Runtime/README.md
Original file line number Diff line number Diff line change
@@ -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<T>`. 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`.
10 changes: 10 additions & 0 deletions crates/bindings-csharp/Codegen.Tests/Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ namespace SpacetimeDB.Codegen.Tests;
using Microsoft.CodeAnalysis.MSBuild;
using Microsoft.CodeAnalysis.Text;

/// <summary>
/// Snapshot tests for the <c>SpacetimeDB.Codegen</c> library.
///
/// These run code generation for the sample projects in <c>fixtures</c>. 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 <c>dotnet tool restore; dotnet verify accept</c> 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.
/// </summary>
public static class GeneratorSnapshotTests
{
// Note that we can't use assembly path here because it will be put in some deep nested folder.
Expand Down
35 changes: 8 additions & 27 deletions crates/bindings-csharp/Codegen/README.md
Original file line number Diff line number Diff line change
@@ -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<Variants>` where `Variants` is a named tuple of all possible variants, e.g.:

```csharp
[SpacetimeDB.Type]
partial record Option<T> : 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<T>`.
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.
24 changes: 21 additions & 3 deletions crates/bindings-csharp/README.md
Original file line number Diff line number Diff line change
@@ -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

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to be a fragment. Is the reader being instructed to see the following libraries? If so, this may just require a colon to be added to the end of the line.

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.
10 changes: 9 additions & 1 deletion crates/bindings-csharp/Runtime/README.md
Original file line number Diff line number Diff line change
@@ -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.

Expand All @@ -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__`.
Loading