SyncVar hook not called with every change

I have a basic finite-state machine for controlling turn-based play, but I am finding that SyncVar is practically worthless to me in syncing the state because the hook isn’t called with every change.

Here’s what I’m trying:

class GameController : NetworkBehaviour // instantiated by the client
{
    enum GameState
    {
        TurnStart,
        PlayerTurn,
        TurnEnd
        // etc.
    };

    [SyncVar(hook = "OnGameStateChanged")]
    GameState state;

    private void OnGameStateChanged(GameState newState)
    {
        state = newState;
        // do some work
    }

    private void Update()
    {
        if (!isServer)
        {
            return;
        }
        switch (state)
        {
            case GameState.TurnStart:
                // do some work
                state = GameState.PlayerTurn;
                break;
            case GameState.PlayerTurn:
                // updated by player Command elsewhere in script
                break;
            case GameState.TurnEnd:
                // do some work
                state = GameState.TurnStart; // start the next player's turn
            default:
                break;
        }
    }

    // example of player Command located in other script
    [Command]
    private void CmdEndTurn()
    {
        state = GameState.TurnEnd;
        // work is done in Update
    }
}

I am testing this on a connection between one host (server+client) and one other client. I would expect the OnGameStateChanged method to be invoked on the client every time that state is changed. What I’m actually experiencing on the client is it going straight from GameState.PlayerTurn back to GameState.TurnStart, which is to say that the hook is never being invoked with GameState.TurnEnd. This means that my client is missing vital synchronizations.

What I’ve had to do instead is remove the SyncVar altogether and just use an RPC:

class GameController : NetworkBehaviour // instantiated by the client
{
    enum GameState
    {
        TurnStart,
        PlayerTurn,
        TurnEnd
        // etc.
    }

    GameState state;

    [ClientRpc]
    private void RpcOnGameStateChanged(GameState newState)
    {
        state = newState;
        // do some work
    }

    private void Update()
    {
        if (!isServer)
        {
            return;
        }
        GameState oldState = state;
        switch (state)
        {
            case GameState.TurnStart:
                // do some work
                state = GameState.PlayerTurn;
                break;
            case GameState.PlayerTurn:
                // updated by player Command elsewhere in script
                break;
            case GameState.TurnEnd:
                // do some work
                state = GameState.TurnStart; // start the next player's turn
            default:
                break;
        }
        if (state != oldState)
        {
            RpcOnGameStateChanged(state);
        }
    }

    // example of player Command located in other script
    [Command]
    private void CmdEndTurn()
    {
        state = GameState.TurnEnd;
        // work is done in Update
    }
}

This workaround works the way I need it to, but is there any way to make a SyncVar call its hook on the clients with every change?

Synvars has a max send rate. It says at the bottom of the network behaviour in the editor. Default is 0.1. To follow every state. Use command and rpcs.

1 Like

I did end up using an RPC. I had tried looking for clarification on this behavior before posting, but came up short in my searches. Thanks, now I know understand better when to and not to use SyncVars.