Skip to content
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

Dylan/refactor/csharp module scheduling repeating #39

Open
wants to merge 2 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
51 changes: 33 additions & 18 deletions docs/modules/c-sharp/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,9 @@ These attributes are bitflags and can be combined together, but you can also use

Attribute `[SpacetimeDB.Reducer]` can be used on any `static void` method to register it as a SpacetimeDB reducer. The method must accept only supported types as arguments. If it throws an exception, those will be caught and reported back to the database runtime.

> [!WARNING]
> Currently, `Exceptions` do not log in C# server modules: For an intentional `throw`, be sure to log your error message before throwing. We are actively working to resolve this issue!

```csharp
[SpacetimeDB.Reducer]
public static void Add(string name, int age)
Expand All @@ -254,38 +257,50 @@ If a reducer has an argument with a type `DbEventArgs` (`SpacetimeDB.Runtime.DbE

```csharp
[SpacetimeDB.Reducer]
public static void PrintInfo(DbEventArgs e)
public static void PrintInfo(DbEventArgs dbEvent)
{
Log($"Sender identity: {e.Sender}");
Log($"Sender address: {e.Address}");
Log($"Time: {e.Time}");
Log($"Sender identity: {dbEvent.Sender}");
Log($"Sender address: {dbEvent.Address}");
Log($"Time: {dbEvent.Time}");
}
```

`[SpacetimeDB.Reducer]` also generates a function to schedule the given reducer in the future.

Since it's not possible to generate extension methods on existing methods, the codegen will instead add a `Schedule`-prefixed method colocated in the same namespace as the original method instead. The generated method will accept `DateTimeOffset` argument for the time when the reducer should be invoked, followed by all the arguments of the reducer itself, except those that have type `DbEventArgs`.
#### Reducer Scheduling

`[SpacetimeDB.Reducer]` also generates a function to schedule the given reducer to fire **once** in the future.

Although schedulers only fire once, you may nest this inside of the function it calls to **repeatedly** invoke the reducer.

Since it's not possible to generate extension methods on existing methods, the codegen will instead add a `Schedule`-prefixed method colocated in the same namespace as the original method. The generated method will accept `DateTimeOffset` argument for the time when the reducer should be invoked, followed by all the arguments of the reducer itself, except those that have type `DbEventArgs`.

If we created a Reducer named `SpawnResource(uint resourceId)`, the auto-codegen would create:

```csharp
// Example reducer:
[SpacetimeDB.Reducer]
public static void Add(string name, int age) { ... }
`ScheduleSpawnResource(DateTimeOffset time, int age)`
```

Let's consider an **repeating** resource scheduler example:

// Auto-generated by the codegen:
public static void ScheduleAdd(DateTimeOffset time, string name, int age) { ... }
```csharp
/// Cancel this anytime with `.Cancel()`
static ScheduleToken _resourceSpawnerScheduleToken;

// Usage from another reducer:
/// Schedule to call itself every 5m, repeating until cancelled
[SpacetimeDB.Reducer]
public static void AddIn5Minutes(DbEventArgs e, string name, int age)
public static void ResourceSpawnerAgent(DbEventArgs dbEvent, string name, int age)
{
// Note that we're using `e.Time` instead of `DateTimeOffset.Now` which is not allowed in modules.
var scheduleToken = ScheduleAdd(e.Time.AddMinutes(5), name, age);

// We can cancel the scheduled reducer by calling `Cancel()` on the returned token.
scheduleToken.Cancel();
// Note that we're using the sender's `dbEvent.Time` instead of `DateTimeOffset.Now`
DateTimeOffset time = dbEvent.Time.AddMinutes(5);
// [Re]Schedule to call itself again in 5m
_resourceSpawnerScheduleToken = ScheduleSpawnResource(time, resourceId: 0);
}
```

> [!WARNING]
> When scheduling a Reducer, always use original send time from `dbEvent.time` instead of `DateTimeOffset.Now` to schedule a reducer.

#### Special reducers

These are two special kinds of reducers that can be used to respond to module lifecycle events. They're stored in the `SpacetimeDB.Module.ReducerKind` class and can be used as an argument to the `[SpacetimeDB.Reducer]` attribute:
Expand Down
16 changes: 8 additions & 8 deletions docs/modules/c-sharp/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,17 +207,17 @@ In `server/Lib.cs`, add the definition of the connect reducer to the `Module` cl

```csharp
[SpacetimeDB.Reducer(ReducerKind.Connect)]
public static void OnConnect(DbEventArgs dbEventArgs)
public static void OnConnect(DbEventArgs dbEvent)
{
Log($"Connect {dbEventArgs.Sender}");
var user = User.FindByIdentity(dbEventArgs.Sender);
Log($"Connect {dbEvent.Sender}");
var user = User.FindByIdentity(dbEvent.Sender);

if (user is not null)
{
// If this is a returning user, i.e., we already have a `User` with this `Identity`,
// set `Online: true`, but leave `Name` and `Identity` unchanged.
user.Online = true;
User.UpdateByIdentity(dbEventArgs.Sender, user);
User.UpdateByIdentity(dbEvent.Sender, user);
}
else
{
Expand All @@ -226,7 +226,7 @@ public static void OnConnect(DbEventArgs dbEventArgs)
new User
{
Name = null,
Identity = dbEventArgs.Sender,
Identity = dbEvent.Sender,
Online = true,
}.Insert();
}
Expand All @@ -239,15 +239,15 @@ Add the following code after the `OnConnect` lambda:

```csharp
[SpacetimeDB.Reducer(ReducerKind.Disconnect)]
public static void OnDisconnect(DbEventArgs dbEventArgs)
public static void OnDisconnect(DbEventArgs dbEvent)
{
var user = User.FindByIdentity(dbEventArgs.Sender);
var user = User.FindByIdentity(dbEvent.Sender);

if (user is not null)
{
// This user should exist, so set `Online: false`.
user.Online = false;
User.UpdateByIdentity(dbEventArgs.Sender, user);
User.UpdateByIdentity(dbEvent.Sender, user);
}
else
{
Expand Down