Skip to content

Commit c475984

Browse files
authored
scheduler table doc update (#77)
1 parent efcc979 commit c475984

File tree

3 files changed

+133
-41
lines changed

3 files changed

+133
-41
lines changed

docs/docs/modules/c-sharp/index.md

Lines changed: 62 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -295,30 +295,77 @@ public static void PrintInfo(ReducerContext e)
295295
}
296296
```
297297

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

300-
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 `ReducerContext`.
299+
### Scheduler Tables
300+
Tables can be used to schedule a reducer calls either at a specific timestamp or at regular intervals.
301301

302302
```csharp
303-
// Example reducer:
304-
[SpacetimeDB.Reducer]
305-
public static void Add(string name, int age) { ... }
303+
public static partial class Timers
304+
{
305+
306+
// The `Scheduled` attribute links this table to a reducer.
307+
[SpacetimeDB.Table(Scheduled = nameof(SendScheduledMessage))]
308+
public partial struct SendMessageTimer
309+
{
310+
public string Text;
311+
}
306312

307-
// Auto-generated by the codegen:
308-
public static void ScheduleAdd(DateTimeOffset time, string name, int age) { ... }
313+
314+
// Define the reducer that will be invoked by the scheduler table.
315+
// The first parameter is always `ReducerContext`, and the second parameter is an instance of the linked table struct.
316+
[SpacetimeDB.Reducer]
317+
public static void SendScheduledMessage(ReducerContext ctx, SendMessageTimer arg)
318+
{
319+
// ...
320+
}
309321

310-
// Usage from another reducer:
311-
[SpacetimeDB.Reducer]
312-
public static void AddIn5Minutes(ReducerContext e, string name, int age)
313-
{
314-
// Note that we're using `e.Time` instead of `DateTimeOffset.Now` which is not allowed in modules.
315-
var scheduleToken = ScheduleAdd(e.Time.AddMinutes(5), name, age);
316322

317-
// We can cancel the scheduled reducer by calling `Cancel()` on the returned token.
318-
scheduleToken.Cancel();
323+
// Scheduling reducers inside `init` reducer.
324+
[SpacetimeDB.Reducer(ReducerKind.Init)]
325+
public static void Init(ReducerContext ctx)
326+
{
327+
328+
// Schedule a one-time reducer call by inserting a row.
329+
new SendMessageTimer
330+
{
331+
Text = "bot sending a message",
332+
ScheduledAt = ctx.Time.AddSeconds(10),
333+
ScheduledId = 1,
334+
}.Insert();
335+
336+
337+
// Schedule a recurring reducer.
338+
new SendMessageTimer
339+
{
340+
Text = "bot sending a message",
341+
ScheduledAt = new TimeStamp(10),
342+
ScheduledId = 2,
343+
}.Insert();
344+
}
319345
}
320346
```
321347

348+
Annotating a struct with `Scheduled` automatically adds fields to support scheduling, It can be expanded as:
349+
350+
```csharp
351+
public static partial class Timers
352+
{
353+
[SpacetimeDB.Table]
354+
public partial struct SendMessageTimer
355+
{
356+
public string Text; // fields of original struct
357+
358+
[SpacetimeDB.Column(ColumnAttrs.PrimaryKeyAuto)]
359+
public ulong ScheduledId; // unique identifier to be used internally
360+
361+
public SpacetimeDB.ScheduleAt ScheduleAt; // Scheduling details (Time or Inteval)
362+
}
363+
}
364+
365+
// `ScheduledAt` definition
366+
public abstract partial record ScheduleAt: SpacetimeDB.TaggedEnum<(DateTimeOffset Time, TimeSpan Interval)>
367+
```
368+
322369
#### Special reducers
323370

324371
These are four 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:

docs/docs/modules/rust/index.md

Lines changed: 55 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -167,8 +167,6 @@ struct Person {
167167

168168
### Defining reducers
169169

170-
`#[spacetimedb(reducer)]` optionally takes a single argument, which is a frequency at which the reducer will be automatically called by the database.
171-
172170
`#[spacetimedb(reducer)]` is always applied to top level Rust functions. They can take arguments of types known to SpacetimeDB (just like fields of structs must be known to SpacetimeDB), and either return nothing, or return a `Result<(), E: Debug>`.
173171

174172
```rust
@@ -192,39 +190,75 @@ struct Item {
192190

193191
Note that reducers can call non-reducer functions, including standard library functions.
194192

195-
Reducers that are called periodically take an additional macro argument specifying the frequency at which they will be invoked. Durations are parsed according to https://docs.rs/humantime/latest/humantime/fn.parse_duration.html and will usually be a number of milliseconds or seconds.
196193

197-
Both of these examples are invoked every second.
194+
There are several macros which modify the semantics of a column, which are applied to the members of the table struct. `#[unique]` and `#[autoinc]` are covered below, describing how those attributes affect the semantics of inserting, filtering, and so on.
198195

199-
```rust
200-
#[spacetimedb(reducer, repeat = 1s)]
201-
fn every_second() {}
196+
#[SpacetimeType]
202197

203-
#[spacetimedb(reducer, repeat = 1000ms)]
204-
fn every_thousand_milliseconds() {}
205-
```
198+
#[sats]
206199

207-
Finally, reducers can also receive a ReducerContext object, or the Timestamp at which they are invoked, just by taking parameters of those types first.
200+
### Defining Scheduler Tables
201+
Tables can be used to schedule a reducer calls either at a specific timestamp or at regular intervals.
208202

209203
```rust
210-
#[spacetimedb(reducer, repeat = 1s)]
211-
fn tick_timestamp(time: Timestamp) {
212-
println!("tick at {time}");
204+
// The `scheduled` attribute links this table to a reducer.
205+
#[spacetimedb(table, scheduled(send_message))]
206+
struct SendMessageTimer {
207+
text: String,
213208
}
209+
```
214210

215-
#[spacetimedb(reducer, repeat = 500ms)]
216-
fn tick_ctx(ctx: ReducerContext) {
217-
println!("tick at {}", ctx.timestamp)
211+
The `scheduled` attribute adds a couple of default fields and expands as follows:
212+
```rust
213+
#[spacetimedb(table)]
214+
struct SendMessageTimer {
215+
text: String, // original field
216+
#[primary]
217+
#[autoinc]
218+
scheduled_id: u64, // identifier for internal purpose
219+
scheduled_at: ScheduleAt, //schedule details
220+
}
221+
222+
pub enum ScheduleAt {
223+
/// A specific time at which the reducer is scheduled.
224+
/// Value is a UNIX timestamp in microseconds.
225+
Time(u64),
226+
/// A regular interval at which the repeated reducer is scheduled.
227+
/// Value is a duration in microseconds.
228+
Interval(u64),
218229
}
219230
```
220231

221-
Note that each distinct time a repeating reducer is invoked, a seperate schedule is created for that reducer. So invoking `every_second` three times from the spacetimedb cli will result in the reducer being called times times each second.
232+
Managing timers with scheduled table is as simple as inserting or deleting rows from table.
233+
```rust
234+
#[spacetimedb(reducer)]
222235

223-
There are several macros which modify the semantics of a column, which are applied to the members of the table struct. `#[unique]` and `#[autoinc]` are covered below, describing how those attributes affect the semantics of inserting, filtering, and so on.
236+
// Reducers linked to the scheduler table should have their first argument as `ReducerContext`
237+
// and the second as an instance of the table struct it is linked to.
238+
fn send_message(ctx: ReducerContext, arg: SendMessageTimer) -> Result<(), String> {
239+
// ...
240+
}
224241

225-
#[SpacetimeType]
242+
// Scheduling reducers inside `init` reducer
243+
fn init() {
244+
// Scheduling a reducer for a specific Timestamp
245+
SendMessageTimer::insert(SendMessageTimer {
246+
scheduled_id: 1,
247+
text:"bot sending a message".to_string(),
248+
//`spacetimedb::Timestamp` implements `From` trait to `ScheduleAt::Time`.
249+
scheduled_at: ctx.timestamp.plus(Duration::from_secs(10)).into()
250+
});
251+
252+
// Scheduling a reducer to be called at fixed interval of 100 milliseconds.
253+
SendMessageTimer::insert(SendMessageTimer {
254+
scheduled_id: 0,
255+
text:"bot sending a message".to_string(),
256+
//`std::time::Duration` implements `From` trait to `ScheduleAt::Duration`.
257+
scheduled_at: duration!(100ms).into(),
258+
});
259+
}
260+
```
226261

227-
#[sats]
228262

229263
## Client API
230264

docs/docs/unity/part-4.md

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -98,11 +98,16 @@ pub struct Config {
9898

9999
### Step 2: Write our Resource Spawner Repeating Reducer
100100

101-
1. Add the following code to lib.rs. We are using a special attribute argument called repeat which will automatically schedule the reducer to run every 1000ms.
101+
1. Add the following code to lib.rs. As we want to schedule `resource_spawn_agent` to run later, It will require to implement a scheduler table.
102102

103103
```rust
104-
#[spacetimedb(reducer, repeat = 1000ms)]
105-
pub fn resource_spawner_agent(_ctx: ReducerContext, _prev_time: Timestamp) -> Result<(), String> {
104+
#[spacetimedb(table, scheduled(resource_spawner_agent))]
105+
struct ResouceSpawnAgentSchedueler {
106+
_prev_time: Timestamp,
107+
}
108+
109+
#[spacetimedb(reducer)
110+
pub fn resource_spawner_agent(_ctx: ReducerContext, _arg: ResourceSpawnAgentScheduler) -> Result<(), String> {
106111
let config = Config::find_by_version(&0).unwrap();
107112

108113
// Retrieve the maximum number of nodes we want to spawn from the Config table
@@ -157,18 +162,24 @@ pub fn resource_spawner_agent(_ctx: ReducerContext, _prev_time: Timestamp) -> Re
157162
}
158163
```
159164

165+
160166
2. Since this reducer uses `rand::Rng` we need add include it. Add this `use` statement to the top of lib.rs.
161167

162168
```rust
163169
use rand::Rng;
164170
```
165171

166-
3. Even though our reducer is set to repeat, we still need to schedule it the first time. Add the following code to the end of the `init` reducer. You can use this `schedule!` macro to schedule any reducer to run in the future after a certain amount of time.
172+
3. Add the following code to the end of the `init` reducer to set the reducer to repeat at every regular interval.
167173

168174
```rust
169175
// Start our resource spawner repeating reducer
170-
spacetimedb::schedule!("1000ms", resource_spawner_agent(_, Timestamp::now()));
176+
ResouceSpawnAgentSchedueler::insert(ResouceSpawnAgentSchedueler {
177+
_prev_time: TimeStamp::now(),
178+
scheduled_id: 1,
179+
scheduled_at: duration!(1000ms).into()
180+
}).expect();
171181
```
182+
struct ResouceSpawnAgentSchedueler {
172183

173184
4. Next we need to generate our client code and publish the module. Since we changed the schema we need to make sure we include the `--clear-database` flag. Run the following commands from your Server directory:
174185

0 commit comments

Comments
 (0)