Unity-Technologies / com.unity.netcode.gameobjects

Netcode for GameObjects is a high-level netcode SDK that provides networking capabilities to GameObject/MonoBehaviour workflows within Unity and sits on top of underlying transport layer.
MIT License
2.16k stars 435 forks source link

Scene network objects are broken on first "enter playmode" using disabled scene reload in editor #2892

Closed Yoraiz0r closed 6 months ago

Yoraiz0r commented 7 months ago

Description

If you have a network object in a scene (a "scene" network object, not spawned by players), and you have the project setting "Editor/Enter Playmode Settings/Reload Scene" disabled, and you reload the domain (such as after changing code and triggering a recompile), the scene network object will not trigger any of its network behaviour calls (such as OnNetworkSpawn).

This has 100% reproduction rate if you have "domain reloading" enabled, and "scene reloading" disabled.

If you also have "domain reloading" disabled, then exiting playmode, and then entering playmode a second time, will trigger the network behaviour calls properly.

I've pulled the netcode for gameobjects package locally and debugged it. The situation seems as follows: After the domain reload, at the moment of the NetworkObject.Awake, the member m_ChildNetworkBehaviours is set to a list of 0 size. This is erroneous, because the list of child network behaviours should begin as null. See the code below.

        private List<NetworkBehaviour> m_ChildNetworkBehaviours;

        internal List<NetworkBehaviour> ChildNetworkBehaviours
        {
            get
            {
                if (m_ChildNetworkBehaviours != null)
                {
                    return m_ChildNetworkBehaviours;
                }

                m_ChildNetworkBehaviours = new List<NetworkBehaviour>();
                var networkBehaviours = GetComponentsInChildren<NetworkBehaviour>(true);
                for (int i = 0; i < networkBehaviours.Length; i++)
                {
                    if (networkBehaviours[i].NetworkObject == this)
                    {
                        m_ChildNetworkBehaviours.Add(networkBehaviours[i]);
                    }
                }

                return m_ChildNetworkBehaviours;
            }
        }

The list only ever fetches the proper set of behaviours if it is set to null, but it seems that somewhere, the editor gives it an instance without ever calling the ChildNetworkBehaviours getter.

If have domain reloading disabled and enter playmode a second time, you will witness that m_ChildNetworkBehaviours is null this time, which is even weirder.

Reproduce Steps

  1. Disable scene reloading (keep domain reloading on!)
  2. Create a test networkbehaviour that has public override void OnNetworkSpawn() { Debug.Log("test"); }
  3. Create a new scene, with a NetworkManager, some button to begin hosting, and a gameobject with a NetworkObject and your networkbehaviour.
  4. Trigger domain reloading (such as by adding an empty space to your script and saving it)
  5. Enter playmode
  6. Begin hosting, and witness that the test method does not get called
  7. Exit playmode
  8. Enter playmode again
  9. Begin hosting, and witness that the 'test' method does not get called.
  10. Exit playmode
  11. Disable domain reloading
  12. Enter playmode
  13. Begin hosting, and witness that the 'test' method finally gets properly called
  14. Exit playmode
  15. Trigger domain reloading again
  16. Enter playmode
  17. Begin hosting, and witness that the 'test' method does not get called.

Actual Outcome

NetworkBehaviour calls do not get called if scene reload is disabled

Expected Outcome

NetworkBehaviour calls properly get called regardless of if scene reload is disabled

Environment

Additional Context

I'm not sure whether to put the blame on Unity's editor, or the getter property using faulty caching strategy. Additionally, my personal thought is that this collection would be better off being assigned on validate, instead of being paid its cost at runtime. Is there any reason that is not the case? Either way, this bug makes it impossible to work with NGO with domain & scene reloading both disabled for maximum editor speed.

Yoraiz0r commented 7 months ago

Although this problem seems to hint at a far bigger issue to my intuition, I've resolved it for development purposes with a temporary fix, I've locally adjusted NetworkObject.Awake to include m_ChildNetworkBehaviours = null;. I don't think this should be necessary but, as I don't know where the auto-initialization of this field comes from, I'll use this for now. It seems to resolve this specific issue well enough.

NoelStephensUnity commented 7 months ago

Although this problem seems to hint at a far bigger issue to my intuition, I've resolved it for development purposes with a temporary fix, I've locally adjusted NetworkObject.Awake to include m_ChildNetworkBehaviours = null;

I will look into this potential fix for the issue described above. (Thank you for posting your findings!)