Documentation

Support

Cloud Code

Timers in Stateful Cloud Code

Use timers in Stateful Cloud Code modules to schedule delayed function calls.
Read time 3 minutesLast updated a day 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.
Use timers to schedule time-based game mechanics in your Stateful Cloud Code modules without managing infrastructure or maintaining persistent connections from game clients. A timer registers a one-off callback that executes a specified Cloud Code function after a defined time span elapses. Timers are server authoritative and persist within the scope of your module. When a timer elapses, the Cloud Code runtime invokes the target function with the arguments you specified at registration. Common use cases for timers include the following:
  • Turn timers: Enforce time limits for player turns in multiplayer games.
  • Cooldowns: Restrict how frequently players can perform certain actions.
  • Delayed events: Trigger in-game events after a specified duration.
  • Idle mechanics: Process offline progression or resource generation at intervals.
Note
Timer management in Stateful Cloud Code requires you to reference the following NuGet package versions in your modules:
  • com.unity.services.cloudcode.apis
    v0.0.26 or later
  • com.unity.services.cloudcode.core
    v0.0.4 or later

Timer registration

To use timers, add
ITimerService
as a parameter to your Cloud Code function. The runtime injects the timer service automatically.
Note
The Cloud Code function must be of the same Cloud Code module as the function registering the timer.
using Unity.Services.CloudCode.Core;[StateScope(Scope.MultiplayerSession)]public class RegisterTimerExample{ [CloudCodeFunction("PerformDelayedAction")] public async Task<bool> PerformDelayedAction(IExecutionContext context, ITimerService timerService) { // Register a timer that calls "DelayedAction" after 30 seconds await timerService.Register( TimeSpan.FromSeconds(30), "DelayedAction", new Dictionary<string, object> { { "action", "NOTHING" } } ); return true; } [CloudCodeFunction("DelayedAction")] public async Task DelayedAction(string action) { // Perform your action here }}
In this example, calling
PerformDelayedAction
registers a timer that automatically calls
DelayedAction
after 30 seconds.
PerformDelayedAction
also passes in the relevant argument (in this case,
action
) to the callback function.

Timer parameters

The
Register
method accepts the following parameters:

Parameter

Type

Description

delay
TimeSpan
The duration to wait before the timer elapses.
functionName
string
The name of the Cloud Code function to call when the timer elapses.
arguments
Dictionary<string, object>
An optional dictionary containing arguments to pass to the target function.
Additionally, note the following parameters:
  • Register timers with a delay between 1 second and 24 hours.
  • A module can run up to 10 timers at the same time.

Turn timer implementation

You can implement recurring turn timers by having the function called by the timer register its own timer. This pattern is useful for turn-based games where each player has a fixed amount of time to act.
using Unity.Services.CloudCode.Core;[StateScope(Scope.MultiplayerSession)]public class TurnBasedGameExample{ public List<string> _players = new List<string>(); public int _currentPlayerIndex; public bool _gameActive; public string? _timerId; [CloudCodeFunction("StartGame")] public async Task<string> StartGame(ITimerService timerService, List<string> playerIds) { _players = playerIds; _currentPlayerIndex = 0; _gameActive = true; // Start the first turn timer _timerId = await timerService.Register( TimeSpan.FromSeconds(60), "OnTurnTimeout", new Dictionary<string, object> { { "playerId", _players[_currentPlayerIndex] } } ); return $"Game started. {_players[_currentPlayerIndex]}'s turn."; } [CloudCodeFunction("MakeMove")] public async Task<string> MakeMove(IExecutionContext context, ITimerService timerService, string move) { if (_players[_currentPlayerIndex] != context.PlayerId) { return "Not your turn."; } if (_timerId != null) { var timer = await timerService.Fetch(_timerId); timer.Cancel(); } // Process the move here // Advance to the next player and register a new turn timer _currentPlayerIndex = (_currentPlayerIndex + 1) % _players.Count; await timerService.Register( TimeSpan.FromSeconds(60), "OnTurnTimeout", new Dictionary<string, object> { { "playerId", _players[_currentPlayerIndex] } } ); return $"Move '{move}' processed. Next player's turn."; } [CloudCodeFunction("OnTurnTimeout")] public async Task<string> OnTurnTimeout(IExecutionContext context, ITimerService timerService) { if (!_gameActive || _players[_currentPlayerIndex] != context.PlayerId) { return "Timer no longer valid."; } // Handle timeout (skip turn, apply penalty, etc.) var skippedPlayer = _players[_currentPlayerIndex]; // Advance to the next player and register a new turn timer _currentPlayerIndex = (_currentPlayerIndex + 1) % _players.Count; await timerService.Register( TimeSpan.FromSeconds(60), "OnTurnTimeout", new Dictionary<string, object> { { "playerId", _players[_currentPlayerIndex] } } ); return $"Player {skippedPlayer} timed out. Next player's turn."; }}
In this pattern, each call to
OnTurnTimeout
or
MakeMove
advances to the next player and registers a new timer. The timer chain continues until the game ends.
Tip
In turn-based flows, because a module can run up to 10 timers at the same time, only keep the active turn timer and cancel timers that are no longer needed.

Acting on timers

Sometimes, you might need to cancel a timer before it elapses, for example, when a player completes their turn before the timeout. You might also need to make checks based on a timer's remaining time. To implement either of these scenarios, fetch the running timer by ID with
timerService.Fetch()
, then call methods on the returned timer. Save the timer ID when you register the timer.
[StateScope(Scope.MultiplayerSession)]public class TimerActionExample{ private string _activeTimerId; [CloudCodeFunction("StartAction")] public async Task<string> StartAction(ITimerService timerService) { // Register returns a timer ID _activeTimerId = await timerService.Register( TimeSpan.FromSeconds(30), "DoSomething" // Callback function not included below ); return "Action started. Complete within 30 seconds."; } [CloudCodeFunction("UserCompleteAction")] public async Task<string> UserCompleteAction(ITimerService timerService) { // Cancel the timer since the action completed in time if (!string.IsNullOrEmpty(_activeTimerId)) { var timer = await timerService.Fetch(_activeTimerId); timer.Cancel(); _activeTimerId = null; } return "Action completed successfully."; } [CloudCodeFunction("RemainingTime")] public async Task<TimeSpan> RemainingTime(ITimerService timerService) { // Return how much time is left on the active timer if (string.IsNullOrEmpty(_activeTimerId)) { throw new InvalidOperationException("Cannot return remaining time because no timer is currently active."); } var timer = await timerService.Fetch(_activeTimerId); return timer.RemainingTime(); }}
Tip
Since a module can only run up to 10 timers at the same time, cancel completed or obsolete timers to free timer slots.