Documentation

Cloud Code

Version: 3.0.0  
Unity 6.7 (6000.7)+
Open Unity Dashboard

Cloud Code

Version: 3.0.0  
Unity 6.7 (6000.7)+

Timers in CloudBehaviours

Schedule, cancel, and understand the limits of a server-authoritative CloudBehaviour timer.
Read time 5 minutesLast updated 12 days ago

Important
This page describes an experimental feature that might change significantly before release. It's not recommended to use or rely on experimental features in production environments due to potential instability.
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
I{Behaviour}
interface that you use for cross-behaviour calls. Inject the interface, call the endpoint method with its real arguments, and run the call with
ScheduleAsync(delay)
. The following example schedules a timer from an RPC:
[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.
ScheduleAsync
registers a timer against the deployed endpoint and returns the timer's ID.
Only the session host can call
StartTurn
. Without
[CloudCodeAccess]
, any session member can call
StartTurn
repeatedly and fill the session's timer slots.
StartTurn
also cancels the previous timer before it schedules a new one, so no orphaned timer keeps running.
EndTurn
clears
CurrentTimerId
when the timer fires, because a fired timer's ID no longer resolves. Calling
GetTimerAsync
with that ID throws an exception. For other ways to restrict who calls an endpoint, refer to Multiplayer game security in CloudBehaviours.
To inject
ITurnClock
, register the interface in your module's
ICloudCodeSetup
with
config.AddTurnClockClient()
. Refer to Register the generated interface.

Cancel a timer

ICall
has no cancel method. To fetch and cancel a timer by ID, inject
[CloudCodeService] ITimerService
.
You need
ITimerService
only to cancel or query a timer, not to schedule one.
ScheduleAsync
uses the timer service for you. Don't call
ITimerService.RegisterTimerAsync
directly in a CloudBehaviour. If you do, the build fails with
CCSG137
.
GetTimerAsync
throws an exception for a timer that already fired or that you canceled. Clear a stored timer ID in the endpoint that the timer calls, as
EndTurn
does in the preceding example.
The 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,
CurrentTimerId
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
[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
[CloudCodeAccess(Access.Service)]
so that the timer can still call it but clients can't.
Important
In a timer-triggered invocation,
Context.PlayerId
is
null
and no player access token exists. Use
Context.ServiceToken
for outbound service calls. Pass what the endpoint needs as arguments instead of reading the calling player.
A timer can target only a public instance endpoint on the CloudBehaviour. A private or static method isn't on
I{Behaviour}
, so scheduling one doesn't compile.

What a timer callback pushes

A timer-triggered invocation pushes synchronized state and declared events to the scope's clients. A
Player
-scoped CloudBehaviour pushes to its player, and a
MultiplayerSession
-scoped CloudBehaviour pushes to every session member. If the CloudBehaviour has per-player state views, each client receives its own view.

Timer 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
    ScheduleAsync
    on another CloudBehaviour's
    I{Behaviour}
    , the build fails with
    CCSG132
    .

Additional resources