Timers in CloudBehaviours
Schedule, cancel, and understand the limits of a server-authoritative CloudBehaviour timer.
Read time 5 minutesLast updated 12 days ago
Timers are server-authoritative. Cloud Code schedules a call to one of your CloudBehaviour's own endpoints, so a player who closes the game can't avoid a turn clock.
Schedule a timer
To schedule a timer, use the same generated interface that you use for cross-behaviour calls. Inject the interface, call the endpoint method with its real arguments, and run the call with . The following example schedules a timer from an RPC:
I{Behaviour}ScheduleAsync(delay)[StateScope(Scope.MultiplayerSession)]public class TurnClock : CloudBehaviour{ public string CurrentTimerId; public int TurnNumber; [CloudCodeAccess(Access.SessionHost)] public async Task StartTurn([CloudCodeService] ITurnClock self, [CloudCodeService] ITimerService timers) { if (CurrentTimerId != null) { var previous = await timers.GetTimerAsync(CurrentTimerId); // Cancel the previous timer so that it doesn't hold a timer slot. previous.Cancel(); } CurrentTimerId = await self.EndTurn(TurnNumber).ScheduleAsync(TimeSpan.FromSeconds(30)); } [CloudCodeAccess(Access.Service)] public void EndTurn(int turnNumber) { // Ignore a stale timer that fires against an old turn. if (turnNumber != TurnNumber) return; CurrentTimerId = null; TurnNumber++; }}
Arguments are ordinary typed parameters on the endpoint. registers a timer against the deployed endpoint and returns the timer's ID.
ScheduleAsyncOnly the session host can call . Without , any session member can call repeatedly and fill the session's timer slots. also cancels the previous timer before it schedules a new one, so no orphaned timer keeps running.
StartTurn[CloudCodeAccess]StartTurnStartTurnEndTurnCurrentTimerIdGetTimerAsyncTo inject , register the interface in your module's with . Refer to Register the generated interface.
ITurnClockICloudCodeSetupconfig.AddTurnClockClient()Cancel a timer
ICall[CloudCodeService] ITimerServiceYou need only to cancel or query a timer, not to schedule one. uses the timer service for you. Don't call directly in a CloudBehaviour. If you do, the build fails with .
ITimerServiceScheduleAsyncITimerService.RegisterTimerAsyncCCSG137GetTimerAsyncEndTurnThe following example cancels the current turn's timer:
[CloudCodeAccess(Access.SessionHost)]public async Task CancelTurn([CloudCodeService] ITimerService timers){ if (CurrentTimerId == null) return; var timer = await timers.GetTimerAsync(CurrentTimerId); timer.Cancel(); CurrentTimerId = null;}
Persist the timer ID
The timer service returns a timer's ID only once, when you schedule the timer. The service can't list your timers later. If you need to cancel a timer later, store its ID in a persisted member.
Cloud Code doesn't save an ID that you keep only in memory. The ID can survive between calls, but you lose the ID whenever Cloud Code reloads your CloudBehaviour, for example after a period of inactivity or a redeploy.
In the preceding examples, is a public field, so it persists by default. The field also synchronizes to every client in the session. To persist the ID without sending it to clients, make the field private and mark it :
CurrentTimerId[CloudCodeSerializeProperty]// This field persists, but Cloud Code never sends it to a client.[CloudCodeSerializeProperty]private string _turnTimerId;[CloudCodeAccess(Access.SessionHost)]public async Task StartTurn([CloudCodeService] ITurnClock self){ _turnTimerId = await self.EndTurn(TurnNumber).ScheduleAsync(TimeSpan.FromSeconds(30));}[CloudCodeAccess(Access.SessionHost)]public async Task CancelTurn([CloudCodeService] ITimerService timers){ if (_turnTimerId == null) return; (await timers.GetTimerAsync(_turnTimerId)).Cancel(); _turnTimerId = null;}
Restrict access to the scheduled endpoint
A scheduled endpoint is still an endpoint, so a client can call it directly. Mark the endpoint so that the timer can still call it but clients can't.
[CloudCodeAccess(Access.Service)]A timer can target only a public instance endpoint on the CloudBehaviour. A private or static method isn't on , so scheduling one doesn't compile.
I{Behaviour}What a timer callback pushes
A timer-triggered invocation pushes synchronized state and declared events to the scope's clients. A -scoped CloudBehaviour pushes to its player, and a -scoped CloudBehaviour pushes to every session member. If the CloudBehaviour has per-player state views, each client receives its own view.
PlayerMultiplayerSessionTimer limits
Timers have the following limits:
- The delay must be greater than zero and no more than 24 hours. The 24-hour limit is shared. The combined delays of all pending timers on the same player or session, plus the new timer, can't exceed 24 hours.
- A player or session can hold at most 10 pending timers at once. Every module and CloudBehaviour that uses that scope shares the 10 slots. A timer frees its slot when it fires or when you cancel it.
- A timer callback must not await a call into another module in its own scope instance. Calls into a scope instance run one at a time, so the call waits for the callback and the callback waits for the call. Refer to Call the current scope instance.
- There's no recurring timer. For a repeating clock, make the scheduled endpoint schedule the next timer.
- A timer can call only endpoints on the CloudBehaviour that scheduled it. If you call on another CloudBehaviour's
ScheduleAsync, the build fails withI{Behaviour}.CCSG132