Search Unity

SyncVar hook not called when client joins game

Discussion in 'Multiplayer' started by Xerse, Jul 17, 2015.

  1. Xerse

    Xerse

    Joined:
    Jun 19, 2015
    Posts:
    1
    From the manual: "When an object is spawned, or a new player joins a game in progress, they are sent the latest state of all SyncVars on networked objects that are visible to them. " (http://docs.unity3d.com/Manual/UNetStateSync.html)

    That is great and is working correctly, but my SyncVars have hook functions, and these functions are only being called when the variable is changed on the server. It isn't being called when the client first logs in and gets the latest state of all SyncVars on the networked objects from the server.

    Is this a known bug? Is there something I can do to call those hook functions when a client joins a game in progress and its networked objects' syncvars are updated?
     
    wlwl2 and tribio like this.
  2. peterept2

    peterept2

    Joined:
    Aug 1, 2012
    Posts:
    41
    Yes the hook is not called when the object is instantiated - but is guaranteed to have syncvar at correct state by StartClient() call and from what I see always before Start() is called too. So you can call your hook during Start() or StartClient() ?
     
    TheMrCake likes this.
  3. Vanamerax

    Vanamerax

    Joined:
    Jan 12, 2012
    Posts:
    938
    I always find this a bit unintuitive. I would personally like to see an option in the attribute to also get a callback for initial states.
     
    Chom1czek likes this.
  4. Zullar

    Zullar

    Joined:
    May 21, 2013
    Posts:
    651
    I also would like to see an option to automatically call hooks on start. Having to manually call all hooks for all SyncVars on client connect leaves room for error.
     
    Chom1czek likes this.
  5. nuverian

    nuverian

    Joined:
    Oct 3, 2011
    Posts:
    2,087
    I also agree with that.
    Hooks should be called automaticaly.
     
    Chom1czek likes this.
  6. JoRouss

    JoRouss

    Joined:
    Apr 1, 2014
    Posts:
    53
    I agree... Why would you not want it to call the hook? :/
     
  7. sixolar

    sixolar

    Joined:
    Jan 8, 2014
    Posts:
    37
    Indeed, maybe fill a bug or a feedback?
     
    Zullar likes this.
  8. seanr

    seanr

    Unity Technologies

    Joined:
    Sep 22, 2014
    Posts:
    669
    SyncVar Hooks are for changes in state, not for initial state. There is the OnStartClient callback for handling initial state.

    There is no context during a SyncVar hook, so if they were called for initial state, the client would not be able to tell the difference between initialization of a variable and a change in the value of a variable. This causes un-expected results.

    For example, for a "[SyncVar] int health" with a SyncVar hook that causes a client-side "blood-spurt" effect when the object takes damage, the object would play the blood-spurt for any objects with non-maximum health when joining a game in progress. The hook would be called with the new health value, which is different from the default health value, so the object thinks it has taken damage - so it plays its blood-spurt. But this is not what should happen. Setting the initial health of the object to its current value from the server should NOT play the effect.

    This applies to all kinds of state changes, such as animation state, particles, etc. Initialization is different from incremental changes and the client-side code needs to know which is happening.
     
    wlwl2 likes this.
  9. PhilippG

    PhilippG

    Joined:
    Jan 7, 2014
    Posts:
    257
    @seanr I understand the intention, though: Speaking for myself, I expect callbacks to be called everytime a value is updated. I do also want to do things like rebuild the UI to fit the objects current state.

    So, would it be possible to pass an additional "isInit"-bool to the hook, that is set only once the value is initialized?
    That way, I could do something like this:

    Code (CSharp):
    1. [SyncVar (hook = "OnHealthUpdated")]
    2. private int Health = 100;
    3.  
    4. private void OnHealthUpdated(int value, bool isInit)
    5. {
    6.     if(!isInit)
    7.     {
    8.         if (value < Health)
    9.             Say("Ouch!");
    10.         else
    11.             Say("Ah, Thats Better!");
    12.     }
    13.     Health = value;
    14.     RefreshUI();
    15. }
    See, this way we could have the benefit of both.
     
  10. Baroni

    Baroni

    Joined:
    Aug 20, 2010
    Posts:
    3,261
    @PhilippG I guess that is not going to happen. Just call your hooks in OnStartClient for initialization.

    Code (csharp):
    1.  
    2.         public override void OnStartClient()
    3.         {
    4.              OnHealthUpdated(Health);
    5.         }
    6.  
    That's what I'm doing right now and this also allows for choosing to not init some SyncVars.
     
    Danielp299 likes this.
  11. PhilippG

    PhilippG

    Joined:
    Jan 7, 2014
    Posts:
    257
    @Baroni It's more a request concerning convenience. If you do it that way, you might forget calling a hook somewhere. The code would still compile, but you just produced a bug that might or might not be easy to track down. All the previous posters also seem to agree its counterintuitive, and thats not how Unity is meant to be.

    An additional parameter in the deserialization part won't hurt, and UNet is still in the makings after all.
     
  12. Baroni

    Baroni

    Joined:
    Aug 20, 2010
    Posts:
    3,261
    In case I'm activating a shield in something like "OnShieldUpdated", I would have to always check against the value passed in as I would not want to render a shield for players right at the beginning, when the value is 0 but the hook is called nonetheless. There are situations where I would like for hooks being called automatically (that's why I've found this thread in the first place) but there are others where I don't, so I've learned to live with that manual initialization.
     
  13. PhilippG

    PhilippG

    Joined:
    Jan 7, 2014
    Posts:
    257
    I can't agree, sorry.
     
  14. Baroni

    Baroni

    Joined:
    Aug 20, 2010
    Posts:
    3,261
    @PhilippG
    I'm not here to change your opinion. It is one thing to call something manually which isn't there, but it is a completely different thing to work around something you can't change.
     
  15. PhilippG

    PhilippG

    Joined:
    Jan 7, 2014
    Posts:
    257
    You might not want to change my mind but, intentionally or not, you're undermining my request. I tagged @seanr because I want to ask a unity staff member if this or a similar implementation might be an option for future releases. Still, thank you for your input.
     
  16. christophermrau

    christophermrau

    Joined:
    Mar 16, 2017
    Posts:
    5
    This is working a little differently for me. Because the hook kind of hijacks the sync, my client variable is actually NOT initialized by the time OnStartClient() is called. I verified this by creating a new SyncVar variable without a hook. It was properly initialized while the hooked variable was still at it's default.

    Code (CSharp):
    1.  
    2. [SyncVar]
    3. Int32 _notHooked;
    4.  
    5. [SyncVar(hook = "OnChangeCurrent")]
    6. Int32 _hooked;
    7. void OnChangeCurrent(Int32 current)
    8. {
    9.     _hooked = current;
    10.     //other code
    11. }
    12. ...
    13. void OnStartClient()
    14. {
    15.     Console.WriteLine(_notHooked.ToString()); //prints valid value
    16.     Console.WriteLine(_hooked.ToString()); //prints 0 no matter what
    17. }
    18.  
    19.  
    So, it looks like there is a need to come up with a clever workaround once again. Any suggestions?
     
  17. christophermrau

    christophermrau

    Joined:
    Mar 16, 2017
    Posts:
    5
    Can you explain a little more how you are getting this to work?
    For me, any SyncVars that are hook'ed are not updated by OnStartClient. Are you using two different Sync'ed variables? or are you doing something else? Also, my post above has an example of what my code looks like. Any suggestions? Thanks.
     
  18. Honeystain

    Honeystain

    Joined:
    Mar 12, 2017
    Posts:
    2
    I'm having a problem with this SyncVar stuff: So the hook function is not called for initializations, that's fine. So you suggested I should initialize the hook function from OnStartClient(). The problem with that is that it only calls the hook function the first time I join the server. If I leave then join back, the OnStartClient() is not called. This script is attached to an object in the scene called TestMachine (which is a Cube) not a player object.

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.Networking;
    5.  
    6. public class TestMachine : NetworkBehaviour
    7. {
    8.     [SyncVar(hook = "UpdateColour")]
    9.     int colour;
    10.  
    11.     void Start()
    12.     {
    13.         Debug.Log("Normal Start");
    14.     }
    15.  
    16.     public override void OnStartServer()
    17.     {
    18.         Debug.Log("Start Server Test Machine!");
    19.         colour = 0;
    20.         UpdateColour(colour);
    21.     }
    22.     public override void OnStartClient()
    23.     {
    24.         Debug.Log("Start Client Test Machine!");
    25.         UpdateColour(colour);
    26.     }
    27.  
    28.     void Update()
    29.     {
    30.         Debug.Log(colour);
    31.     }
    32.  
    33.     public void Interact()
    34.     {
    35.         RpcChangeColour();
    36.     }
    37.  
    38.     public void UpdateColour(int colour)
    39.     {
    40.         //Debug.Log(colour);
    41.         if (colour == 1)
    42.         {
    43.             GetComponent<MeshRenderer>().material.color = Color.blue;
    44.         }
    45.         else
    46.         {
    47.             GetComponent<MeshRenderer>().material.color = Color.red;
    48.         }
    49.     }
    50.  
    51.     [ClientRpc]
    52.     public void RpcChangeColour()
    53.     {
    54.         if (colour == 0)
    55.         {
    56.             colour = 1;
    57.         }
    58.         else
    59.         {
    60.             colour = 0;
    61.         }
    62.     }
    63. }
    64.  
     
  19. Saishy

    Saishy

    Joined:
    Jun 3, 2010
    Posts:
    79
    My hooks are def being called on client for initialized variables.

    Code (csharp):
    1.  
    2. [SyncVar(hook = "OnRandomSeedSync")]
    3.    public int randomSeed;
    4.  
    5.     public void OnRandomSeedSync(int newSeed) {
    6.        randomSeed = newSeed;
    7.        randGenerator = new System.Random(newSeed);
    8.    }
    9.  
    Putting a breakpoint in the OnRandomSeedSync it stops there every time a new enemy spawns in the client, but the randomSeed value is the same as the newSeed.

    Also, it seems sometimes it's not, as every 10ish enemy or so I will get an error that I tried to access randGenerator and it was null.
     
  20. LukeDawn

    LukeDawn

    Joined:
    Nov 10, 2016
    Posts:
    404
    With SyncVar, Hook, Command, Rpc I can get changes in value in one client to be sent to all the others, but for new clients, there is no way to get them to all send the new client all of their current values.

    For example Client C joins a game with a health of 100. Clients A and B have been fighting and have healths of less than 100.

    None of the networking commands/rpcs etc seem to start Client C off already knowing the healths of A and B. Although A and B seem to be given the health of C when C starts.
     
    Last edited: Oct 23, 2017
  21. Zullar

    Zullar

    Joined:
    May 21, 2013
    Posts:
    651
    SyncVar does exactly this.

    Lets say HostA and ClientB start off with SyncVar health 100. Then they get damaged and their healths are reduced to 80 and 70. A SyncVar callback will occur on HostA and ClientB showing A's health changed from 100 to 80 and B's health changed from 100 to 70.

    Then ClientC connects. All current UNET SyncVar data is serialized & sent to ClientC. After everything is deserialized on ClientC the SyncVar of the health for A and B will be 80 and 70 (not 100). ClientC immediately gets the latest values when he connects just as you want. However, ClientC will NOT get callbacks showing health reduced to 80 and 70. This is the part that is confusing/irritating. So during initialization you must check the initial SyncVar value (I do this during Start) and manually force the callback if you want it to be called (i.e. setting a health bar or something). Any subsequent changes to health (lets say ClientB's health is changed from 70 to 50) WILL call the callbacks on HostA, ClientB, and ClientC.

    It's worth mentioning that there are some UNET bugs that cause SyncVars to de-sync and also let callbacks fail, but these are rare cases and likely not what you are experiencing.
    https://forum.unity.com/threads/my-compiled-list-of-unet-bugs-issues.403578/
     
    wlwl2 likes this.