Do SyncVar hooks need to set their variable to the new value?

I have a SyncVar variable of type GameObject. I’d like to write a hook function for that variable which runs callbacks (via a C# event) when the value is updated. Consider this example of a node in a tree structure.

class Node : NetworkBehaviour
{
    [SyncVar(hook="ParentHook")]
    private GameObject m_parentObj;
    // Can't use variable of type "Node",
    // because [SyncVar] cannot be applied to component types.
   
    // Hook function: emit ParentChanged event when value changes.
    private void ParentHook (GameObject obj)
    {
        if (obj == m_parentObj)
            return;       
        m_parentObj = obj; // Is this line required?
        if (ParentChanged)
            ParentChanged();           
    }
   
    // Access m_parentObj as a Node rather than a GameObject.
    public Node Parent {
        get {
            return m_parentObj != null ? m_parentObj.GetComponent<Node>() : null;
        }
    }
   
    // Event fired with the parent is changed.  Triggered in SyncVar hook.
    public event System.Action ParentChanged;

}

After showing this code to some colleagues, I have the following questions:

  1. Is it even legal to have a SyncVar of type GameObject?

  2. In a SyncVar hook, does the developer need to set the variable to the new value? Is line 13 required?

2.1) If the answer to 2 is “yes”, then is it a bug that SyncVar variables of type GameObject still maintain their original value until the hook function completes? That is to say, on line 14, m_parentObj still has its previous value. All other variable types are updated on assignment.

2.2) If the answer to 2 is “no”, then is the hook called before or after the variable has been set to the new value? I would say that it should be after, since callbacks (e.g. C# events) could be fired from the hook, and those callback functions would expect the new value to be in place.

2.2.1) If hooks are run before the variable is set, and the developer is not expected to set the variable manually, then I cannot run callbacks or emit events from within a SyncVar hook function. That’s fine, as I can remove the hook entirely and add a setter to the “Parent” property on line 19:

    // Access m_parentObj as a Node rather than a GameObject.
    public Node Parent {
        get {
            return m_parentObj != null ? m_parentObj.GetComponent<Node>() : null;
        }
        set {
            // 'value' is of type Node.  Get the GameObject.
            GameObject obj = value != null ? value.gameObject : null;
            if (obj == m_parentObj)
                return;
            m_parentObj = obj;
            if (ParentChanged != null)
                ParentChanged();
        }
    }

However, the SyncVar documentation states that:

Would I fall foul of this in the example above? Or does it refer to if the SyncVar variable itself has getter/setter functions?

I’m sure there was more documentation for SyncVar hooks, but it seems to have disappeared recently. There certainly used to be a page definitively listing the permissible SyncVar types, but that has disappeared.

Whenever you use a hook on a SyncVar, you need to manually set the variable. So, yes, in your example, line 13 is required.

If you want to sync references to game objects across the network with UNET, you can add NetworkIdentities to the respective game objects. Then, each object will have a NetworkInstanceId which you can pass as a reference across network and then fetch the objects with either ClientScene.FindLocalObject or NetworkServer.FindLocalObject.

With commands and rpcs you can also directly pass gameobject as arguments, but if you take a look at the source you will see that under the hood UNET just passes the NetworkInstanceId. I haven’t checked how SyncVars work for gameobjects, but I presume that it either does not work at all or just also uses the Network Id in case that a NetworkIdentity is in place. I still would not recommend to do this as it causes a lot of confusion and it would be better to directly sync the Network Id in the first place.

If you do not want the gameobjects to be networked objects with NetworkIdentities, and still need to sync the references across network, then you probably need to implement some sort of custom mechanism for the syncronization.

I’m not sure if this is the case. I’ve tried using a hook function that does nothing more than a Debug.Log, and the variable does still get updated to the new value, on the server and all clients.

It does. In the inspector, if you select an object with a component that has a [SyncVar] GameObject member variable called something like m_Whatever, and then right click the “Inspector” tab and change to Debug view, you can see that there is a generated field called something like ___m_mmWhateverNetId. So it looks like a similar thing is happening here.

I guess your quote is slightly out-dated because I had revised my comment regarding the SyncVar support of game objects. Nevertheless, from your info, it seems that the 2nd option is the case so SyncVars use the same technical solution like commands and rpcs, which is using the network Id if it is available

As it regards the hook functionality, maybe UNET functionality has been changed so that the manual step is not required any longer. However, I don’t think that is true since the implementation with the manual assignment enables a lot of control over the client-side sync var update flow. For example, you can implement checks and other stuff before the variable gets eventually updated. Are you really sure about your test results? I will have a look into this later myself.

Thanks for your help.

(Btw: All of the GameObjects that I’m passing around, storing, etc. do have NetworkIdentity on them.)