# State scopes

> Retain state for individual players or multiplayer sessions using Cloud Code scopes.

By default, Cloud Code is stateless, meaning that each time you run a script or module, Cloud Code doesn't remember the data from the previous run. However, for games that require persistent states, such as card, puzzle, and idle games, you can use scopes to preserve information during your scope's lifetime in Cloud Code.

Scopes can retain state for individual players, or a group of players in a multiplayer session.

Implementing Stateful Cloud Code ensures your game scales while operating at minimal cost.

## Prerequisites

Before you use Stateful Cloud Code, make sure you meet the following requirements:

* Use Unity 6.7 or later.
* Reference the following NuGet package versions in your module. Modules you create in the Unity Editor reference these packages automatically.
  * `com.unity.services.cloudcode.apis` v0.0.27 or later
  * `com.unity.services.cloudcode.core` v0.0.9 or later
* If your module uses the `MultiplayerSession` scope, install the [Multiplayer Services SDK](/mps-sdk/install-and-upgrade.md) (`com.unity.services.multiplayer`) in your game client to create and join sessions.

## Configuration

To implement state persistence, specify the scope type to determine how to retain your module's class variables within the lifecycle of the selected scope. There are two primary scope types available for configuration:

* `Player`: State is accessible only to a specific player. Use the `Player` scope for data that belongs to an individual player, such as progression, currency balances, inventory, or preferences. `Player` state persists until its time to live (TTL) expires, which defaults to two years.
* `MultiplayerSession`: State is accessible to all players in a multiplayer session. Use the `MultiplayerSession` scope for data shared across a session, such as a game board, turn order, or a shared score. `MultiplayerSession` state persists until its time to live (TTL) expires, which defaults to seven days.

To configure scoped module functions, annotate your class with `StateScope` and a desired scope type, as demonstrated in the following code examples:

```csharp title="Example of Player scope"
using Unity.Services.CloudCode.Core;

// Example module setup for a HelloPlayer module with a Player scope
[StateScope(Scope.Player)]
public class HelloPlayer
{
    // Public members are serialized and persisted by default.
    public int score;

    // Private members aren't persisted unless you opt them in.
    [CloudCodeSerializeProperty]
    private int _counter;

    // Opt a public member out of persistence, for example, a transient or cached value.
    [CloudCodeIgnoreProperty]
    public int sessionCacheHits;

    [CloudCodeFunction("IncrementPlayer")]
    public int IncrementPlayer()
    {
        return ++_counter;
    }
}
```

```csharp title="Example of MultiplayerSession scope"
using Unity.Services.CloudCode.Core;

// Example module setup for a HelloSession module with a MultiplayerSession scope
[StateScope(Scope.MultiplayerSession)]
public class HelloSession
{
    // Public members are serialized and persisted by default.
    public int sharedScore;

    // Private members aren't persisted unless you opt them in.
    [CloudCodeSerializeProperty]
    private int _turnCount;

    // Opt a public member out of persistence, for example, a transient or cached value.
    [CloudCodeIgnoreProperty]
    public int sessionCacheHits;

    [CloudCodeFunction("IncrementTurn")]
    public int IncrementTurn()
    {
        return ++_turnCount;
    }
}
```

> **Important:**
>
> By default, the scope's state serializes and persists only the `public` members of your module. To persist a `private` member, annotate it with `CloudCodeSerializeProperty`, and to exclude a `public` member, annotate it with `CloudCodeIgnoreProperty`, as shown in the previous examples. For more information, refer to [State serialization](/cloud-code/3.0.0/modules/stateful-cloud-code/state-serialization.md).

Then, from your game, sign in to get a player ID, and reference that ID for future `Player`-scoped server module function calls:

```csharp title="Referencing player ID for future Player-scoped calls"
using UnityEngine;
using Unity.Services.Authentication;
using Unity.Services.Core;

// Example of logging into an anonymous account and referencing the
// playerId to be invoked with for future Player-scoped calls.
public class CallPlayerCloudCode : MonoBehaviour
{
    private string m_PlayerID;

    private async void Start()
    {
        await UnityServices.InitializeAsync();
        await AuthenticationService.Instance.SignInAnonymouslyAsync();

        // Access the currently logged-in player's ID
        m_PlayerID = AuthenticationService.Instance.PlayerId;
    }
}
```

Likewise, for `MultiplayerSession` scopes, [create a session](/mps-sdk/create-session.md) with `MultiplayerService`, and save a session ID for future scoped server module function calls:

```csharp title="Referencing session ID for future MultiplayerSession-scoped calls"
using UnityEngine;
using Unity.Services.Authentication;
using Unity.Services.Core;
using Unity.Services.Multiplayer;

// Example of creating a client-hosted session and referencing the
// sessionId to be invoked with for future MultiplayerSession-scoped calls.
public class CallMultiplayerSessionCloudCode : MonoBehaviour
{
    private string m_SessionID;

    private async void Start()
    {
        await UnityServices.InitializeAsync();
        await AuthenticationService.Instance.SignInAnonymouslyAsync();

        var options = new SessionOptions
        {
            MaxPlayers = 2
        };

        var session = await MultiplayerService.Instance.CreateSessionAsync(options);
        Debug.Log($"Session {session.Id} created! Join code: {session.Code}");

        // Access the session's ID
        m_SessionID = session.Id;
    }
}
```

## Scoped calls using generated modules

The following diagram demonstrates how a function call reaches `Player`-scoped or `MultiplayerSession`-scoped state, including how every player in the same session shares a single `MultiplayerSession` scope instance.

```mermaid
flowchart TB
    PA["<b>Player A</b><br/>Sign in: Player ID A"] --> CPA["Call Player scoped module"]
    PA --> JA["Join session"]
    JA --> CSA["Call Session scoped module"]
    CPA --> PSA["<b>Player state scope (Player&nbsp;A)</b><br/>Keyed on Player ID A · 2-year&nbsp;TTL"]
    CSA --> SHARED["<b>Multiplayer session scope</b><br/>Keyed on Multiplayer Session ID · 7-day&nbsp;TTL<br/>Same instance for every player"]

    PB["<b>Player B</b><br/>Sign in: Player ID B"] --> CPB["Call Player scoped module"]
    PB --> JB["Join session"]
    JB --> CSB["Call Session scoped module"]
    CPB --> PSB["<b>Player state scope (Player&nbsp;B)</b><br/>Keyed on Player ID B · 2-year&nbsp;TTL"]
    CSB --> SHARED
```

To target scoped module functions from your game client, initialize the module with the unique identifier corresponding to the desired player or multiplayer session. This initialization associates the module instance with that ID, ensuring that all subsequent function calls interact with the correct persistent state.

> **Note:**
>
> You need to re-generate your modules after adding `StateScope` annotations before you can implement them and call them from within your game.

The following code is an example of a Cloud Code function call incrementing a `Player`-scoped counter:

```csharp title="Incrementing player counter"
// Example of an in-game function call to the HelloPlayer module
public async Task OnButtonClickedHelloPlayer()
{
    try
    {
        var module = new HelloPlayerBindings(m_PlayerID, CloudCodeService.Instance);
        var result = await module.IncrementPlayer();

        Debug.Log("Player " + "[" + m_PlayerID + $"] Counter: {result}");
    }
    catch (CloudCodeException exception)
    {
        Debug.LogException(exception);
    }
}
```

## Scoped calls directly from CloudCodeService

You can call server endpoints directly, without bindings, using `CallModuleEndpointAsync()` on the `CloudCodeService` instance. To make these calls, configure a `CloudCodeScope` with the scope type you want and a unique string ID that targets the player or group within that scope.

```csharp title="Implementing player counter from CloudCodeService"
public async Task OnButtonClickedHelloPlayerDirect()
{
    try
    {
        var scope = new CloudCodeScope(ScopeType.Player, m_PlayerID);
        var result = await CloudCodeService.Instance.CallModuleEndpointAsync<int>(
            "HelloPlayer",
            "IncrementPlayer",
            scope: scope);


        Debug.Log("Player " + "[" + m_PlayerID + $"] Counter: {result}");
    }
    catch (CloudCodeException exception)
    {
        Debug.LogException(exception);
    }
}
```

## Avoid calls into your own scope

Calls into the same scope type and scope ID run one at a time, whichever module they target. Two modules scoped to the same session, or to the same player, count as the same scope.

If a function awaits a call into another module in the same scope, the call waits for the running function to finish, and the running function waits for the call. The wait continues until the gateway times out the request, and the caller fails with a `Gateway Timeout` error. The queued call then runs, even though the caller already received the error. Timer callbacks behave the same way.

| Caller scope                     | Target scope                             | Await the call?                |
| -------------------------------- | ---------------------------------------- | ------------------------------ |
| `MultiplayerSession` with ID `X` | `Player` with any player ID              | Yes. The scopes are different. |
| `Player` with ID `P`             | `MultiplayerSession` with any session ID | Yes. The scopes are different. |
| `MultiplayerSession` with ID `X` | `MultiplayerSession` with ID `X`         | No. Same scope.                |
| `Player` with ID `P`             | `Player` with ID `P`                     | No. Same scope.                |
| Any scope                        | A stateless module (no `StateScope`)     | Yes.                           |

The following function fails with `Gateway Timeout`, because both modules use the session scope and the call targets the caller's own session:

```csharp title="Don't await a call into your own scope"
[StateScope(Scope.MultiplayerSession)]
public class Lobby
{
    readonly IGameApiClient m_Api;

    public Lobby(IGameApiClient api) { m_Api = api; }

    [CloudCodeFunction("StartGame")]
    public async Task StartGame([CloudCodeService] IExecutionContext context)
    {
        await m_Api.CloudCode.RunModuleAsync(
            context, context.ServiceToken, context.ProjectId, "Turns", "Start",
            new RunModuleArguments(
                new Dictionary<string, object>(),
                new ModuleScope(ModuleScopeType.MultiplayerSession, context.ScopeId)));
    }
}
```

To avoid the wait, use one of the following approaches:

* Keep the logic that both modules need in a plain class, and call its methods directly from each module.
* Put the functions that must run together in the same module, and call them directly as methods instead of through a module call.
* Don't await the cross-module call from inside the running function. Return first, then make the game client call the other module.

A timer callback can't use the last approach. A timer's target function must be in the same module that registers the timer, so a timer callback that awaits a call into the other module waits in the same way.

## ScopeType mismatch

If you try to make client calls with a specified `ScopeType` other than the one deployed with the module, you will receive a `ScopeType` mismatch error.

This error can occur when your game client makes the following type of Cloud Code module function call:

* With a scope, but the deployed module has no scope attribute defined.
* Without a scope, but the deployed module has a defined scope attribute.
* With scope, but the deployed module's scope attribute doesn't match.

The following example of a `ScopeType` mismatch deploys the module with a `Player` session scope, but then calls from the perspective of a `MultiplayerSession` scope in the game.

```csharp title="Example of ScopeType mismatch"
using System.Threading.Tasks;
using Unity.Services.CloudCode.Core;

// Example setup for a HelloPlayer module with a Player scope
[StateScope(Scope.Player)]
public class HelloPlayer
{
    public int _counter;
    [CloudCodeFunction("IncrementPlayer")]
    public int IncrementPlayer()
    {
        return ++_counter;
    }
}

// In Game - Note the misconfigured MultiplayerSession Scope - ScopeType mismatch
public async Task OnButtonClickedHelloPlayerDirect()
{
    var scope = new CloudCodeScope(ScopeType.MultiplayerSession, m_SessionID);
    var result = await CloudCodeService.Instance.CallModuleEndpointAsync<int>(
        "HelloPlayer",
        "IncrementPlayer",
        scope: scope); // <--- Will have Error
}
```
