# Synchronized state in CloudBehaviours

> Declare synchronized state, read it in your game, and control when Cloud Code pushes updates.

> **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.

A public field or property on a CloudBehaviour is synchronized state. Its value lives in the cloud, and the client bindings keep a copy of it up to date. In a `MultiplayerSession` scope, every member of the session receives updates. Private members never reach a client, so secrets such as a card deck or a dice seed stay in the cloud.

## Declare synchronized state

A public, non-static field synchronizes, and so does a public, non-static property with a public setter:

```csharp title="Valid and invalid synchronized state"
[StateScope(Scope.MultiplayerSession)]
public class MatchState : CloudBehaviour
{
    public int Round;                        // Synchronizes
    public List<int> Scores;                 // Synchronizes
    public int TurnNumber { get; set; }      // Synchronizes

    public int Seed { get; private set; }    // Doesn't synchronize: no public setter

    private List<string> _deck;              // Stays in the cloud
}
```

A read-only member, such as a `readonly` field or a property without a public setter, doesn't synchronize, and the build reports a warning for it. Synchronization and persistence follow different rules. For more information about persistence, refer to [Player and match persistence in CloudBehaviours](/cloud-code/cloudbehaviours/persist-player-and-match-progress.md).

Values serialize as JSON. Classes and enums that you define in the cloud assembly are automatically available to the client bindings, carrying only their public fields and properties.

## Read synchronized state in your game

The generated client bindings expose each synchronized field as a read-only property. Subscribe to the `Synchronized` event to react to updates:

```csharp title="Reacting to state updates"
protected virtual void OnEnable()
{
    EnableClient();
    Synchronized.AddListener(() => turnLabel.text = $"Round {matchStateClient.Round}");
}
```

`Synchronized` raises after each applied update: an RPC return, an incoming push, or the hydrate that runs when the client's scope resolves. A client that connects and does nothing else still holds current server state, because hydration runs automatically.

## Control synchronization manually

`AutoSynchronize` is enabled by default. After each RPC returns, Cloud Code pushes updated synchronized state to the other clients in the scope. To batch several changes into a single push, disable `AutoSynchronize` and call `SynchronizeAsync()` when you're ready:

```csharp title="Manual synchronization"
public async Task ResolveRound()
{
    AutoSynchronize = false;
    Round++;
    Scores[0] += 10;
    await SynchronizeAsync();
}
```

`AutoSynchronize` is saved with the rest of the CloudBehaviour's state. Once you set it to `false`, it stays off for every later call in that scope until you set it back to `true`.

The calling client always receives current state in the RPC return, regardless of `AutoSynchronize`.

> **Note:**
>
> A `Player`-scoped CloudBehaviour has no other clients to push to. State reaches that one client through RPC returns and hydration. A timer is the exception: a timer-triggered invocation has no RPC return, so Cloud Code pushes the updated state to the player. Refer to [What a timer callback pushes](/cloud-code/cloudbehaviours/timers.md#what-a-timer-callback-pushes). For scope-specific persistence guidance, refer to [Player and match persistence in CloudBehaviours](/cloud-code/cloudbehaviours/persist-player-and-match-progress.md).

## Model types crossing the client boundary

When a synchronized field, an RPC, or an event uses a class, struct, or enum that you define in your cloud assembly, Cloud Code generates a type with the same name in the client bindings, so your game can work with the same data. The generated type is a data-only copy: it keeps the public fields and properties, and drops everything else.

For example, an inventory CloudBehaviour stores a list of items:

```csharp title="Cloud script"
public class Item
{
    public string Name;
    public int Quantity = 1;

    private string _dropTable;                  // Server-only data

    public bool IsStackable() => Quantity > 1;  // Server-only logic
}

[StateScope(Scope.Player)]
public class Inventory : CloudBehaviour
{
    public List<Item> Items = new();

    public void Add(Item item) => Items.Add(item);
}
```

In your game, `Items` on the client bindings is a list of this generated `Item` type:

```csharp title="Generated in the client bindings"
public class Item
{
    public string Name { get; set; }
    public int Quantity { get; set; }
}
```

In practice, this means:

* Your game reads `Name` and `Quantity` directly, for example to display the inventory.
* `IsStackable()` doesn't exist in your game. To use the same rule on both sides, put it in a [shared rules library](/cloud-code/cloudbehaviours/sharing-game-rules/shared-rules-library.md), or work out the answer on the server and synchronize it.
* `_dropTable` never reaches your game.
* Default values such as `= 1` aren't copied. An `Item` that your game creates, for example to pass to `Add`, starts with `Quantity` at `0` unless you set it.

A public property that can be read but not publicly assigned, such as a computed property or one with a private setter, causes a compile-time error, and so does a public `readonly` field. For where to put real game logic instead, refer to [Game organization in CloudBehaviours](/cloud-code/cloudbehaviours/organize-your-game-into-behaviours.md).

Use `List<T>`, an array, or `Dictionary<TKey, TValue>` for a collection field. Don't use a custom collection type: Cloud Code can't serialize it to JSON and deserialize it back. As an RPC parameter or return value, or when it derives from a .NET collection type, it causes a compile-time error.

To share a single value differently per player, refer to [Per-player state views in CloudBehaviours](/cloud-code/cloudbehaviours/per-player-state-views.md).

## Additional resources

* [Get started with CloudBehaviours](/cloud-code/cloudbehaviours/get-started.md)
* [Player and match persistence in CloudBehaviours](/cloud-code/cloudbehaviours/persist-player-and-match-progress.md)
* [Per-player state views in CloudBehaviours](/cloud-code/cloudbehaviours/per-player-state-views.md)
