Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
ec33515
Add EventListeners a SapDelegate when Sappy is present
lisandroct Aug 3, 2026
152232a
Improve collision handling when Sappy is not present
lisandroct Aug 3, 2026
eb1ddb1
Clean
lisandroct Aug 11, 2026
4944b9c
Fix Unity package .asmdef
lisandroct Aug 11, 2026
6925b8a
Start SappyIntegration
lisandroct Aug 12, 2026
f5c14b9
Implement DI
lisandroct Aug 12, 2026
03a9deb
Fix compilation errors
lisandroct Aug 12, 2026
2f0f26f
Allow overriding CustomFactory
lisandroct Aug 12, 2026
cd0c64b
Improve collisions check
lisandroct Aug 12, 2026
45c0b88
Add .meta files
lisandroct Aug 13, 2026
c8116ec
Add Extensions
lisandroct Aug 13, 2026
4af9607
Performance improvements suggested by Codex
lisandroct Aug 14, 2026
10fdd98
Cache method delegates
lisandroct Aug 14, 2026
9532a90
Improve performance
lisandroct Aug 14, 2026
a8bb0a3
Remove DelegateIndex
lisandroct Aug 14, 2026
d1869cb
Squeeze more performance
lisandroct Aug 14, 2026
d47dafd
Use native events by default
lisandroct Aug 17, 2026
e16564f
Clean
lisandroct Aug 17, 2026
c73a1b6
Fix bug where no custom factory could be used
lisandroct Aug 17, 2026
de628c9
Benchmark by Codex
lisandroct Aug 18, 2026
c7cc3d1
Add missing meta files
lisandroct Aug 19, 2026
4640ec4
Allow duplicates and simplify code
lisandroct Aug 24, 2026
0d25339
Add documentation
lisandroct Sep 3, 2026
335b1f9
Improve documentation
lisandroct Sep 3, 2026
3381193
Make documentation more explicit
lisandroct Sep 3, 2026
8608ec0
Move benchmark under tests~
lisandroct Sep 3, 2026
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
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ Before diving into the reference, you may want to review:
| [`ErrorContext` type](#type-errorcontext) | Implements [`IDbContext`](#interface-idbcontext) for subscription error callbacks. |
| [Query Builder API](#query-builder-api) | Type-safe query builder for typed subscription queries. |
| [Access the client cache](#access-the-client-cache) | Access to your local view of the database. |
| [Configure event dispatch](#configure-event-dispatch) | Choose native C# events or a custom event listener backend. |
| [Observe and invoke reducers](#observe-and-invoke-reducers) | Send requests to the database to run reducers, and register callbacks to run when notified of reducers. |
| [Identify a client](#identify-a-client) | Types for identifying users and client connections. |

Expand Down Expand Up @@ -1035,6 +1036,55 @@ The `OnUpdate` callback runs whenever an already-resident row in the client cach

See [the quickstart](../../00100-intro/00200-quickstarts/00600-c-sharp.md) for examples of registering and unregistering row callbacks.

### Configure event dispatch

By default, the C# SDK stores table row callbacks as regular native C# events. For most applications, no extra setup is required:

```csharp
conn.Db.User.OnInsert += OnUserInsert;
conn.Db.User.OnInsert -= OnUserInsert;
```

Native events are simple, idiomatic, and should be your default choice unless profiling shows that event subscription management is a problem in your application.

If your client frequently adds and removes many row callbacks, the cost of native multicast delegate updates can become noticeable. For those cases, the SDK can use a custom event listener backend instead:

```csharp
using SpacetimeDB;

SpacetimeDB.EventHandling.Backend.UseCustomListeners();

var conn = DbConnection.Builder()
.WithUri("http://localhost:3000")
.WithDatabaseName("my-database")
.Build();
```

Call `Backend.UseCustomListeners()` before creating the generated `DbConnection`. Table handles capture the selected backend when they are constructed, so changing the backend later does not update existing handles.

The default custom backend keeps listeners in an indexed collection. It is useful when you have many listener removals, duplicate subscriptions, or integration code that attaches and detaches callbacks aggressively. Registering and unregistering callbacks still uses the same generated `OnInsert`, `OnDelete`, and `OnUpdate` event APIs.

Reducer result events, such as `conn.Reducers.OnSendMessage`, are always regular C# events.

If your project includes [Sappy](https://github.com/clockworklabs/SappyEvents/), the SDK can use Sappy-backed listener storage:

```csharp
using SpacetimeDB.SappyIntegration;

SpacetimeDB.EventHandling.Backend.UseCustomListeners(new SappyEventListenersFactory());
```

Use the Sappy backend only in projects that already reference Sappy. It is intended for applications that have standardized on Sappy's event/listener model; it is not required for normal C# or Unity clients.

For Sappy-backed table callbacks, register and unregister generated Sappy targets through the listener accessors instead of using normal C# event syntax:

```csharp
conn.Db.User.OnInsertListeners.AddSapTarget(Sappy.OnUserInsert);
conn.Db.User.OnInsertListeners.RemoveSapTarget(Sappy.OnUserInsert);
```

Use the matching listener accessor for each row callback: `OnInsertListeners`, `OnDeleteListeners`, and `OnUpdateListeners`. This lets Sappy manage the callback target directly, which is required for the Sappy backend to behave correctly and avoid unnecessary delegate-management overhead.

### Unique constraint index access

For each unique constraint on a table, its table handle has a property which is a unique index handle and whose name is the unique column name. This unique index handle has a method `.Find(Column value)`. If a `Row` with `value` in the unique column is resident in the client cache, `.Find` returns it. Otherwise it returns null.
Expand Down
3 changes: 3 additions & 0 deletions sdks/csharp/src/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("com.clockworklabs.spacetimedbsdk.sappyintegration")]
11 changes: 11 additions & 0 deletions sdks/csharp/src/AssemblyInfo.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

100 changes: 0 additions & 100 deletions sdks/csharp/src/EventHandling/AbstractEventHandler.cs

This file was deleted.

24 changes: 24 additions & 0 deletions sdks/csharp/src/EventHandling/Backend.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System;

namespace SpacetimeDB.EventHandling
{
public static class Backend
{
internal static bool UseNativeDispatch { get; private set; } = true;
private static IEventListenersFactory? CustomFactory { get; set; }

public static void UseNativeEvents()
{
UseNativeDispatch = true;
CustomFactory = null;
}

public static void UseCustomListeners(IEventListenersFactory? factory = null)
{
UseNativeDispatch = false;
CustomFactory = factory;
}

internal static IEventListeners<T> Create<T>() where T : Delegate => CustomFactory?.Create<T>() ?? new EventListeners<T>();
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading