Scheduler
Persistent fire-at-time callbacks that survive restarts. Server-only.
Seams you implement 1
Crossplay calls these; your game supplies them. Each ships an inert or permissive default, so register yours before services.AddCrossplayScheduler(); and it wins.
IScheduleHandler game SPI
The SPI a game implements per KIND: when an entry of your kind comes due, you get its opaque payload. "respawn" → bring the boss back; "daily-reset" → roll the shops; "auction-expiry" → close the listing. Register any number, each answering one kind.
public sealed class RespawnHandler : IScheduleHandler
{
public string Kind => "respawn";
public void OnDue(ScheduledEntry entry)
{
int bossId = BitConverter.ToInt32(entry.Payload);
_world.Spawn(bossId);
}
} -
string Kind { get; }The kind this handler answers (the routing key; must be unique across handlers).
-
void OnDue(ScheduledEntry entry)An entry of this kind came due — act on its opaque Payload.
Providers you can swap 2
Infrastructure seams. Crossplay ships a working implementation of each — replacing one changes where data lives or how it moves, never a game rule.
IScheduleStore provider
Persistence seam: pending entries must survive restarts. In-memory default; the persistence package overrides with a document-backed store.
-
void Delete(long id)Removes a persisted entry by id (it fired or was cancelled).
-
IReadOnlyList<ScheduledEntry> LoadAll()Every persisted pending entry — read once at startup to rehydrate the queue.
-
long NextId()Allocates the next monotonic entry id.
-
void Save(ScheduledEntry entry)Persists (inserts or replaces) one pending entry.
IWallClock provider
Wall-clock seam (restart-safe time, unlike the uptime-based server clock). Fakeable.
-
long UtcNowMs { get; }Milliseconds since the Unix epoch, UTC.
Services you call 1
Crossplay implements these. Resolve them from DI and call them from your own systems.
ISchedulerService
Schedules persistent timers.
-
bool Cancel(long id)Cancels a pending entry. False when it already fired or never existed.
-
int PendingCount { get; }Pending entries (diagnostics).
-
long Schedule(string kind, byte[] payload, long fireAtUtcMs)Schedules an entry; returns its id. Fires on or shortly after fireAtUtcMs (sweep granularity) — including after a restart, if the store is durable.
-
long ScheduleIn(string kind, byte[] payload, double delaySeconds)Convenience: fire delaySeconds from now.
Configuration 1
Every tunable lives in an options object — there are no magic numbers to hunt for.
SchedulerOptions
Configurable scheduler rules (defaults here, never inline).
-
int MaxFiresPerSweep { get; set; }Max entries fired per sweep (a backlog after downtime drains over several sweeps).
-
int MaxPayloadBytes { get; set; }Maximum payload bytes per entry.
-
float SweepIntervalSeconds { get; set; }Seconds between due-item sweeps (timers are coarse by design — not a frame clock).