Skip to content
Open
42 changes: 42 additions & 0 deletions crates/codegen/tests/snapshots/codegen__codegen_csharp.snap
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
---
source: crates/codegen/tests/codegen.rs
assertion_line: 37
expression: outfiles
---
"Procedures/GetMySchemaViaHttp.g.cs" = '''
Expand Down Expand Up @@ -2222,6 +2223,47 @@ namespace SpacetimeDB
)>;
}
'''
"Types/NonrepeatingTestArg.g.cs" = '''
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.

#nullable enable

using System;
using System.Collections.Generic;
using System.Runtime.Serialization;

namespace SpacetimeDB
{
[SpacetimeDB.Type]
[DataContract]
public sealed partial class NonrepeatingTestArg
{
[DataMember(Name = "scheduled_id")]
public ulong ScheduledId;
[DataMember(Name = "scheduled_at")]
public SpacetimeDB.ScheduleAt ScheduledAt;
[DataMember(Name = "prev_time")]
public SpacetimeDB.Timestamp PrevTime;

public NonrepeatingTestArg(
ulong ScheduledId,
SpacetimeDB.ScheduleAt ScheduledAt,
SpacetimeDB.Timestamp PrevTime
)
{
this.ScheduledId = ScheduledId;
this.ScheduledAt = ScheduledAt;
this.PrevTime = PrevTime;
}

public NonrepeatingTestArg()
{
this.ScheduledAt = null!;
}
}
}
'''
"Types/Person.g.cs" = '''
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
Expand Down
71 changes: 71 additions & 0 deletions crates/codegen/tests/snapshots/codegen__codegen_rust.snap
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
---
source: crates/codegen/tests/codegen.rs
assertion_line: 37
expression: outfiles
---
"add_player_reducer.rs" = '''
Expand Down Expand Up @@ -1029,6 +1030,7 @@ use spacetimedb_sdk::__codegen::{
pub mod baz_type;
pub mod foobar_type;
pub mod has_special_stuff_type;
pub mod nonrepeating_test_arg_type;
pub mod person_type;
pub mod pk_multi_identity_type;
pub mod player_type;
Expand Down Expand Up @@ -1069,6 +1071,7 @@ pub mod with_tx_procedure;
pub use baz_type::Baz;
pub use foobar_type::Foobar;
pub use has_special_stuff_type::HasSpecialStuff;
pub use nonrepeating_test_arg_type::NonrepeatingTestArg;
pub use person_type::Person;
pub use pk_multi_identity_type::PkMultiIdentity;
pub use player_type::Player;
Expand Down Expand Up @@ -2185,6 +2188,74 @@ impl __sdk::InModule for NamespaceTestF {
type Module = super::RemoteModule;
}

'''
"nonrepeating_test_arg_type.rs" = '''
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.

#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{
self as __sdk,
__lib,
__sats,
__ws,
};


#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
pub struct NonrepeatingTestArg {
pub scheduled_id: u64,
pub scheduled_at: __sdk::ScheduleAt,
pub prev_time: __sdk::Timestamp,
}


impl __sdk::InModule for NonrepeatingTestArg {
type Module = super::RemoteModule;
}


/// Column accessor struct for the table `NonrepeatingTestArg`.
///
/// Provides typed access to columns for query building.
pub struct NonrepeatingTestArgCols {
pub scheduled_id: __sdk::__query_builder::Col<NonrepeatingTestArg, u64>,
pub scheduled_at: __sdk::__query_builder::Col<NonrepeatingTestArg, __sdk::ScheduleAt>,
pub prev_time: __sdk::__query_builder::Col<NonrepeatingTestArg, __sdk::Timestamp>,
}

impl __sdk::__query_builder::HasCols for NonrepeatingTestArg {
type Cols = NonrepeatingTestArgCols;
fn cols(table_name: &'static str) -> Self::Cols {
NonrepeatingTestArgCols {
scheduled_id: __sdk::__query_builder::Col::new(table_name, "scheduled_id"),
scheduled_at: __sdk::__query_builder::Col::new(table_name, "scheduled_at"),
prev_time: __sdk::__query_builder::Col::new(table_name, "prev_time"),

}
}
}

/// Indexed column accessor struct for the table `NonrepeatingTestArg`.
///
/// Provides typed access to indexed columns for query building.
pub struct NonrepeatingTestArgIxCols {
pub scheduled_id: __sdk::__query_builder::IxCol<NonrepeatingTestArg, u64>,
}

impl __sdk::__query_builder::HasIxCols for NonrepeatingTestArg {
type IxCols = NonrepeatingTestArgIxCols;
fn ix_cols(table_name: &'static str) -> Self::IxCols {
NonrepeatingTestArgIxCols {
scheduled_id: __sdk::__query_builder::IxCol::new(table_name, "scheduled_id"),

}
}
}

impl __sdk::__query_builder::CanBeLookupTable for NonrepeatingTestArg {}

'''
"person_table.rs" = '''
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
---
source: crates/codegen/tests/codegen.rs
assertion_line: 37
expression: outfiles
---
"add_player_reducer.ts" = '''
Expand Down Expand Up @@ -645,6 +646,13 @@ export const HasSpecialStuff = __t.object("HasSpecialStuff", {
});
export type HasSpecialStuff = __Infer<typeof HasSpecialStuff>;

export const NonrepeatingTestArg = __t.object("NonrepeatingTestArg", {
scheduledId: __t.u64(),
scheduledAt: __t.scheduleAt(),
prevTime: __t.timestamp(),
});
export type NonrepeatingTestArg = __Infer<typeof NonrepeatingTestArg>;

export const Person = __t.object("Person", {
id: __t.u32(),
name: __t.string(),
Expand Down
51 changes: 47 additions & 4 deletions crates/testing/tests/standalone_integration_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,21 +128,26 @@ fn test_calling_a_reducer_with_private_table() {
let logs = read_logs(&module)
.await
.into_iter()
.skip_while(|r| r.starts_with("Timestamp"))
.filter(|r| !is_scheduled_test_log(r))
.collect::<Vec<_>>();

assert_eq!(logs, ["Private, Tyrion!", "Private, World!",].map(String::from));
},
);
}

/// Returns `true` if `line` was produced by the `repeating_test` or `nonrepeating_test` scheduled reducers.
fn is_scheduled_test_log(line: &str) -> bool {
line.starts_with("Timestamp: ") || line.starts_with("This reducers runs only once")
}

async fn read_log_skip_repeating(module: &ModuleHandle) -> String {
let logs = read_logs(module).await;
let mut logs = logs
.into_iter()
// Filter out log lines from the `repeating_test` reducer,
// which runs frequently enough to appear in our logs after we've slept a second.
.filter(|line| !line.starts_with("Timestamp: Timestamp { __timestamp_micros_since_unix_epoch__: "))
// Filter out log lines from the `repeating_test` and `nonrepeating_test` reducers,
// which run on a schedule and can appear in our logs after we've slept.
.filter(|line| !is_scheduled_test_log(line))
.collect::<Vec<_>>();

if logs.len() != 1 {
Expand All @@ -152,6 +157,44 @@ async fn read_log_skip_repeating(module: &ModuleHandle) -> String {
logs.swap_remove(0)
}

fn test_nonrepeating_scheduled_reducer_in_module(module_name: &'static str) {
init();

CompiledModule::compile(module_name, CompilationMode::Debug).with_module_async(
DEFAULT_CONFIG,
|module| async move {
// The `init` reducer schedules `nonrepeating_test` to run 1 second in the future.
// Wait long enough for it to fire.
tokio::time::sleep(std::time::Duration::from_secs(3)).await;

let logs = read_logs(&module).await;

assert!(
logs.iter().any(|line| line.starts_with("This reducers runs only once")),
"Expected nonrepeating_test reducer to have logged, but got: {logs:#?}",
);
},
);
}

#[test]
#[serial]
fn test_nonrepeating_scheduled_reducer() {
test_nonrepeating_scheduled_reducer_in_module("module-test");
}

#[test]
#[serial]
fn test_nonrepeating_scheduled_reducer_csharp() {
test_nonrepeating_scheduled_reducer_in_module("module-test-cs");
}

#[test]
#[serial]
fn test_nonrepeating_scheduled_reducer_typescript() {
test_nonrepeating_scheduled_reducer_in_module("module-test-ts");
}

fn test_calling_a_procedure_in_module(module_name: &'static str) {
init();

Expand Down
28 changes: 28 additions & 0 deletions modules/module-test-cs/Lib.cs
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,16 @@ public partial struct RepeatingTestArg
public Timestamp prev_time;
}

[Table(Accessor = "nonrepeating_test_arg", Scheduled = nameof(Module.nonrepeating_test), ScheduledAt = nameof(scheduled_at))]
public partial struct NonrepeatingTestArg
{
[PrimaryKey]
[AutoInc]
public ulong scheduled_id;
public ScheduleAt scheduled_at;
public Timestamp prev_time;
}

[Table(Accessor = "has_special_stuff")]
public partial struct HasSpecialStuff
{
Expand Down Expand Up @@ -233,6 +243,17 @@ public static void init(ReducerContext ctx)
scheduled_id = 0,
scheduled_at = new TimeDuration(1000000)
});

var currentTime = ctx.Timestamp;
var oneSeconds = new TimeDuration { Microseconds = 1_000_000 };
var futureTimestamp = currentTime + oneSeconds;

ctx.Db.nonrepeating_test_arg.Insert(new NonrepeatingTestArg
{
prev_time = ctx.Timestamp,
scheduled_id = 0,
scheduled_at = new ScheduleAt.Time(futureTimestamp)
});
}

[Reducer]
Expand All @@ -241,6 +262,13 @@ public static void repeating_test(ReducerContext ctx, RepeatingTestArg arg)
var deltaTime = ctx.Timestamp.TimeDurationSince(arg.prev_time);
Log.Trace($"Timestamp: {ctx.Timestamp}, Delta time: {deltaTime}");
}

[Reducer]
public static void nonrepeating_test(ReducerContext ctx, NonrepeatingTestArg arg)
{
var deltaTime = ctx.Timestamp.TimeDurationSince(arg.prev_time);
Log.Trace($"This reducers runs only once, at Timestamp: {ctx.Timestamp}, Delta time: {deltaTime}");
}

[Reducer]
public static void add(ReducerContext ctx, string name, byte age)
Expand Down
37 changes: 37 additions & 0 deletions modules/module-test-ts/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,14 @@ const repeatingTestArg = t.row({
});
type RepeatingTestArg = Infer<typeof repeatingTestArg>;

// Rust: #[spacetimedb::table(name = nonrepeating_test_arg, scheduled(nonrepeating_test))]
const nonrepeatingTestArg = t.row({
scheduled_id: t.u64().primaryKey().autoInc(),
scheduled_at: t.scheduleAt(),
prev_time: t.timestamp(),
});
type NonrepeatingTestArg = Infer<typeof nonrepeatingTestArg>;

// Rust: #[spacetimedb::table(name = has_special_stuff)]
const hasSpecialStuffRow = {
identity: t.identity(),
Expand Down Expand Up @@ -217,6 +225,15 @@ const spacetimedb = schema({
repeatingTestArg
),

// nonrepeating_test_arg table with scheduled(nonrepeating_test)
nonrepeatingTestArg: table(
{
name: 'nonrepeating_test_arg',
scheduled: (): any => nonrepeatingTest,
},
nonrepeatingTestArg
),

// has_special_stuff with Identity and ConnectionId
hasSpecialStuff: table({ name: 'has_special_stuff' }, hasSpecialStuffRow),

Expand Down Expand Up @@ -252,6 +269,15 @@ export const init = spacetimedb.init(ctx => {
scheduled_id: 0n, // u64 autoInc placeholder (engine will assign)
scheduled_at: ScheduleAt.interval(1000000n), // 1000ms
});

const currentTimeMicros = ctx.timestamp.microsSinceUnixEpoch;
const oneSecond = 1_000_000n; // 1 second in microseconds

ctx.db.nonrepeatingTestArg.insert({
prev_time: ctx.timestamp,
scheduled_id: 1n,
scheduled_at: ScheduleAt.time(currentTimeMicros + oneSecond),
});
});

// repeating_test
Expand All @@ -263,6 +289,17 @@ export const repeatingTest = spacetimedb.reducer(
}
);

// nonrepeating_test
export const nonrepeatingTest = spacetimedb.reducer(
{ arg: nonrepeatingTestArg },
(ctx, { arg }) => {
const delta = ctx.timestamp.since(arg.prev_time);
console.trace(
`This reducers runs only once, at Timestamp: ${ctx.timestamp}, Delta time: ${delta}`
);
}
);

// add(name, age)
export const add = spacetimedb.reducer(
{ name: t.string(), age: t.u8() },
Expand Down
Loading
Loading