Skip to content

Commit 144bd78

Browse files
fix: networkvariablebase not being reinitialized if networkobject persists between sessions (#3181)
* fix This will "re-initialize" NetworkVariableBase derived classes when the instance is not destroyed and repurposed. * update adding changelog entry * update Adding PR number to changelog entry * fix ? This test needs more comments. * update Changing the approach to this fix while also cleaning up the initialize process so it can be invoked when the derived class is not yet fully spawned (or just recently instantiated) without potentially assigning the wrong NetworkManager instance. It is being invoked multiple times anyway, so this just gradually initializes all of the required elements until it is finally considered "initialized". This also prevents OnInitialize from being invoked multiple times during this process and OnInitialize is only invoked as a last step to being considered "initialized". Added a Deinitialize step to NetworkVariableBase when despawning a NetworkObject where the initialized status is reset. This allows for the next spawning of the NetworkObject (if in-scene placed or pooled) to run through the initialization process. * test reverting the changes to WhenServerChangesSmoothValue_ValuesAreLerped as the recent update resolves the issue from the previous fix. * update Removing exception. Adjusting when we update the last time. Logging warning if log level is developer and there is no NetworkTimeSystem. * update remove the try catch because evidently it is the only way we can catch certain exceptions during unit testing when duplicating the value (i.e. serializing). * test This validates the fix for resetting the NetworkVariableBase.LastUpdated property when a NetworkObject is persisted (i.e. recycled via in-scene placed NetworkObject or object pools). Also minor update to prevent the integration test from logging a blank entry if there has been no logs added. * test The first iteration should ignore the time delta as that could be impacted by the test or VM running the test.
1 parent 870452b commit 144bd78

File tree

6 files changed

+520
-22
lines changed

6 files changed

+520
-22
lines changed

com.unity.netcode.gameobjects/CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ Additional documentation and release notes are available at [Multiplayer Documen
1414

1515
- Changed the `NetworkTimeSystem.Sync` method to use half RTT to calculate the desired local time offset as opposed to the full RTT. (#3212)
1616
- Fixed issue where a spawned `NetworkObject` that was registered with a prefab handler and owned by a client would invoke destroy more than once on the host-server side if the client disconnected while the `NetworkObject` was still spawned. (#3200)
17+
- Fixed issue where `NetworkVariableBase` derived classes were not being re-initialized if the associated `NetworkObject` instance was not destroyed and re-spawned. (#3181)
1718

1819
### Changed
1920

com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs

+27
Original file line numberDiff line numberDiff line change
@@ -815,6 +815,13 @@ internal void InternalOnNetworkDespawn()
815815
{
816816
Debug.LogException(e);
817817
}
818+
819+
// Deinitialize all NetworkVariables in the event the associated
820+
// NetworkObject is recylced (in-scene placed or pooled).
821+
for (int i = 0; i < NetworkVariableFields.Count; i++)
822+
{
823+
NetworkVariableFields[i].Deinitialize();
824+
}
818825
}
819826

820827
/// <summary>
@@ -918,10 +925,30 @@ internal void __nameNetworkVariable(NetworkVariableBase variable, string varName
918925
variable.Name = varName;
919926
}
920927

928+
/// <summary>
929+
/// Does a first pass initialization for RPCs and NetworkVariables
930+
/// If already initialized, then it just re-initializes the NetworkVariables.
931+
/// </summary>
921932
internal void InitializeVariables()
922933
{
923934
if (m_VarInit)
924935
{
936+
// If the primary initialization has already been done, then go ahead
937+
// and re-initialize each NetworkVariable in the event it is an in-scene
938+
// placed NetworkObject in an already loaded scene that has already been
939+
// used within a network session =or= if this is a pooled NetworkObject
940+
// that is being repurposed.
941+
for (int i = 0; i < NetworkVariableFields.Count; i++)
942+
{
943+
// If already initialized, then skip
944+
if (NetworkVariableFields[i].HasBeenInitialized)
945+
{
946+
continue;
947+
}
948+
NetworkVariableFields[i].Initialize(this);
949+
}
950+
// Exit early as we don't need to run through the rest of this initialization
951+
// process
925952
return;
926953
}
927954

com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs

+72-20
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@ public abstract class NetworkVariableBase : IDisposable
3535

3636
private NetworkManager m_InternalNetworkManager;
3737

38+
// Determines if this NetworkVariable has been "initialized" to prevent initializing more than once which can happen when first
39+
// instantiated and spawned. If this NetworkVariable instance is on an in-scene placed NetworkObject =or= a pooled NetworkObject
40+
// that can persist between sessions and/or be recycled we need to reset the LastUpdateSent value prior to spawning otherwise
41+
// this NetworkVariableBase property instance will not update until the last session time used.
42+
internal bool HasBeenInitialized { get; private set; }
43+
3844
internal string GetWritePermissionError()
3945
{
4046
return $"|Client-{m_NetworkManager.LocalClientId}|{m_NetworkBehaviour.name}|{Name}| Write permissions ({WritePerm}) for this client instance is not allowed!";
@@ -45,17 +51,7 @@ internal void LogWritePermissionError()
4551
Debug.LogError(GetWritePermissionError());
4652
}
4753

48-
private protected NetworkManager m_NetworkManager
49-
{
50-
get
51-
{
52-
if (m_InternalNetworkManager == null && m_NetworkBehaviour && m_NetworkBehaviour.NetworkObject?.NetworkManager)
53-
{
54-
m_InternalNetworkManager = m_NetworkBehaviour.NetworkObject?.NetworkManager;
55-
}
56-
return m_InternalNetworkManager;
57-
}
58-
}
54+
private protected NetworkManager m_NetworkManager => m_InternalNetworkManager;
5955

6056
public NetworkBehaviour GetBehaviour()
6157
{
@@ -68,21 +64,77 @@ public NetworkBehaviour GetBehaviour()
6864
/// <param name="networkBehaviour">The NetworkBehaviour the NetworkVariable belongs to</param>
6965
public void Initialize(NetworkBehaviour networkBehaviour)
7066
{
71-
m_InternalNetworkManager = null;
67+
// If we have already been initialized, then exit early.
68+
// This can happen on the very first instantiation and spawning of the associated NetworkObject
69+
if (HasBeenInitialized)
70+
{
71+
return;
72+
}
73+
74+
// Throw an exception if there is an invalid NetworkBehaviour parameter
75+
if (!networkBehaviour)
76+
{
77+
throw new Exception($"[{GetType().Name}][Initialize] {nameof(NetworkBehaviour)} parameter passed in is null!");
78+
}
7279
m_NetworkBehaviour = networkBehaviour;
73-
if (m_NetworkBehaviour && m_NetworkBehaviour.NetworkObject?.NetworkManager)
80+
81+
// Throw an exception if there is no NetworkManager available
82+
if (!m_NetworkBehaviour.NetworkManager)
7483
{
75-
m_InternalNetworkManager = m_NetworkBehaviour.NetworkObject?.NetworkManager;
76-
// When in distributed authority mode, there is no such thing as server write permissions
77-
InternalWritePerm = m_InternalNetworkManager.DistributedAuthorityMode ? NetworkVariableWritePermission.Owner : InternalWritePerm;
84+
// Exit early if there has yet to be a NetworkManager assigned.
85+
// This is ok because Initialize is invoked multiple times until
86+
// it is considered "initialized".
87+
return;
88+
}
7889

79-
if (m_NetworkBehaviour.NetworkManager.NetworkTimeSystem != null)
80-
{
81-
UpdateLastSentTime();
82-
}
90+
if (!m_NetworkBehaviour.NetworkObject)
91+
{
92+
// Exit early if there has yet to be a NetworkObject assigned.
93+
// This is ok because Initialize is invoked multiple times until
94+
// it is considered "initialized".
95+
return;
96+
}
97+
98+
if (!m_NetworkBehaviour.NetworkObject.NetworkManagerOwner)
99+
{
100+
// Exit early if there has yet to be a NetworkManagerOwner assigned
101+
// to the NetworkObject. This is ok because Initialize is invoked
102+
// multiple times until it is considered "initialized".
103+
return;
83104
}
105+
m_InternalNetworkManager = m_NetworkBehaviour.NetworkObject.NetworkManagerOwner;
106+
107+
// When in distributed authority mode, there is no such thing as server write permissions
108+
InternalWritePerm = m_InternalNetworkManager.DistributedAuthorityMode ? NetworkVariableWritePermission.Owner : InternalWritePerm;
84109

85110
OnInitialize();
111+
112+
// Some unit tests don't operate with a running NetworkManager.
113+
// Only update the last time if there is a NetworkTimeSystem.
114+
if (m_InternalNetworkManager.NetworkTimeSystem != null)
115+
{
116+
// Update our last sent time relative to when this was initialized
117+
UpdateLastSentTime();
118+
119+
// At this point, this instance is considered initialized
120+
HasBeenInitialized = true;
121+
}
122+
else if (m_InternalNetworkManager.LogLevel == LogLevel.Developer)
123+
{
124+
Debug.LogWarning($"[{m_NetworkBehaviour.name}][{m_NetworkBehaviour.GetType().Name}][{GetType().Name}][Initialize] {nameof(NetworkManager)} has no {nameof(NetworkTimeSystem)} assigned!");
125+
}
126+
}
127+
128+
/// <summary>
129+
/// Deinitialize is invoked when a NetworkObject is despawned.
130+
/// This allows for a recyled NetworkObject (in-scene or pooled)
131+
/// to be properly initialized upon the next use/spawn.
132+
/// </summary>
133+
internal void Deinitialize()
134+
{
135+
// When despawned, reset the HasBeenInitialized so if the associated NetworkObject instance
136+
// is recylced (i.e. in-scene placed or pooled) it will re-initialize the LastUpdateSent time.
137+
HasBeenInitialized = false;
86138
}
87139

88140
/// <summary>

com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs

+6-2
Original file line numberDiff line numberDiff line change
@@ -1821,8 +1821,12 @@ private void UnloadRemainingScenes()
18211821

18221822
private void LogWaitForMessages()
18231823
{
1824-
VerboseDebug(m_WaitForLog.ToString());
1825-
m_WaitForLog.Clear();
1824+
// If there is nothing to log, then don't log anything
1825+
if (m_WaitForLog.Length > 0)
1826+
{
1827+
VerboseDebug(m_WaitForLog.ToString());
1828+
m_WaitForLog.Clear();
1829+
}
18261830
}
18271831

18281832
private IEnumerator WaitForTickAndFrames(NetworkManager networkManager, int tickCount, float targetFrames)

0 commit comments

Comments
 (0)