Skip to content

Commit

Permalink
fix: networkvariablebase not being reinitialized if networkobject per…
Browse files Browse the repository at this point in the history
…sists between sessions (#3217)

* fix

Back-port of  #3181 final fix.

* test

The test to validate this PR.

* update

Only log something if we have something to log.

* test

remove verbose mode.

* update

adding changelog entry
  • Loading branch information
NoelStephensUnity authored Jan 24, 2025
1 parent 0d8d95a commit c032b55
Show file tree
Hide file tree
Showing 6 changed files with 518 additions and 20 deletions.
1 change: 1 addition & 0 deletions com.unity.netcode.gameobjects/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Additional documentation and release notes are available at [Multiplayer Documen

### Fixed

- Fixed issue where `NetworkVariableBase` derived classes were not being re-initialized if the associated `NetworkObject` instance was not destroyed and re-spawned. (#3217)
- 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. (#3202)
- Fixed issue where `NetworkRigidBody2D` was still using the deprecated `isKinematic` property in Unity versions 2022.3 and newer. (#3199)
- Fixed issue where an exception was thrown when calling `NetworkManager.Shutdown` after calling `UnityTransport.Shutdown`. (#3118)
Expand Down
23 changes: 23 additions & 0 deletions com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs
Original file line number Diff line number Diff line change
Expand Up @@ -755,6 +755,13 @@ internal void InternalOnNetworkDespawn()
{
Debug.LogException(e);
}

// Deinitialize all NetworkVariables in the event the associated
// NetworkObject is recylced (in-scene placed or pooled).
for (int i = 0; i < NetworkVariableFields.Count; i++)
{
NetworkVariableFields[i].Deinitialize();
}
}

/// <summary>
Expand Down Expand Up @@ -858,6 +865,22 @@ internal void InitializeVariables()
{
if (m_VarInit)
{
// If the primary initialization has already been done, then go ahead
// and re-initialize each NetworkVariable in the event it is an in-scene
// placed NetworkObject in an already loaded scene that has already been
// used within a network session =or= if this is a pooled NetworkObject
// that is being repurposed.
for (int i = 0; i < NetworkVariableFields.Count; i++)
{
// If already initialized, then skip
if (NetworkVariableFields[i].HasBeenInitialized)
{
continue;
}
NetworkVariableFields[i].Initialize(this);
}
// Exit early as we don't need to run through the rest of this initialization
// process
return;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ public abstract class NetworkVariableBase : IDisposable
private protected NetworkBehaviour m_NetworkBehaviour;
private NetworkManager m_InternalNetworkManager;

// Determines if this NetworkVariable has been "initialized" to prevent initializing more than once which can happen when first
// instantiated and spawned. If this NetworkVariable instance is on an in-scene placed NetworkObject =or= a pooled NetworkObject
// that can persist between sessions and/or be recycled we need to reset the LastUpdateSent value prior to spawning otherwise
// this NetworkVariableBase property instance will not update until the last session time used.
internal bool HasBeenInitialized { get; private set; }

public NetworkBehaviour GetBehaviour()
{
return m_NetworkBehaviour;
Expand All @@ -49,37 +55,82 @@ internal void LogWritePermissionError()
Debug.LogError(GetWritePermissionError());
}

private protected NetworkManager m_NetworkManager
{
get
{
if (m_InternalNetworkManager == null && m_NetworkBehaviour && m_NetworkBehaviour.NetworkObject?.NetworkManager)
{
m_InternalNetworkManager = m_NetworkBehaviour.NetworkObject?.NetworkManager;
}
return m_InternalNetworkManager;
}
}
private protected NetworkManager m_NetworkManager => m_InternalNetworkManager;

/// <summary>
/// Initializes the NetworkVariable
/// </summary>
/// <param name="networkBehaviour">The NetworkBehaviour the NetworkVariable belongs to</param>
public void Initialize(NetworkBehaviour networkBehaviour)
{
m_InternalNetworkManager = null;
// If we have already been initialized, then exit early.
// This can happen on the very first instantiation and spawning of the associated NetworkObject
if (HasBeenInitialized)
{
return;
}

// Throw an exception if there is an invalid NetworkBehaviour parameter
if (!networkBehaviour)
{
throw new Exception($"[{GetType().Name}][Initialize] {nameof(NetworkBehaviour)} parameter passed in is null!");
}
m_NetworkBehaviour = networkBehaviour;
if (m_NetworkBehaviour && m_NetworkBehaviour.NetworkObject?.NetworkManager)

// Throw an exception if there is no NetworkManager available
if (!m_NetworkBehaviour.NetworkManager)
{
m_InternalNetworkManager = m_NetworkBehaviour.NetworkObject?.NetworkManager;
// Exit early if there has yet to be a NetworkManager assigned.
// This is ok because Initialize is invoked multiple times until
// it is considered "initialized".
return;
}

if (m_NetworkBehaviour.NetworkManager.NetworkTimeSystem != null)
{
UpdateLastSentTime();
}
if (!m_NetworkBehaviour.NetworkObject)
{
// Exit early if there has yet to be a NetworkObject assigned.
// This is ok because Initialize is invoked multiple times until
// it is considered "initialized".
return;
}

if (!m_NetworkBehaviour.NetworkObject.NetworkManagerOwner)
{
// Exit early if there has yet to be a NetworkManagerOwner assigned
// to the NetworkObject. This is ok because Initialize is invoked
// multiple times until it is considered "initialized".
return;
}
m_InternalNetworkManager = m_NetworkBehaviour.NetworkObject.NetworkManagerOwner;

OnInitialize();

// Some unit tests don't operate with a running NetworkManager.
// Only update the last time if there is a NetworkTimeSystem.
if (m_InternalNetworkManager.NetworkTimeSystem != null)
{
// Update our last sent time relative to when this was initialized
UpdateLastSentTime();

// At this point, this instance is considered initialized
HasBeenInitialized = true;
}
else if (m_InternalNetworkManager.LogLevel == LogLevel.Developer)
{
Debug.LogWarning($"[{m_NetworkBehaviour.name}][{m_NetworkBehaviour.GetType().Name}][{GetType().Name}][Initialize] {nameof(NetworkManager)} has no {nameof(NetworkTimeSystem)} assigned!");
}
}

/// <summary>
/// Deinitialize is invoked when a NetworkObject is despawned.
/// This allows for a recyled NetworkObject (in-scene or pooled)
/// to be properly initialized upon the next use/spawn.
/// </summary>
internal void Deinitialize()
{
// When despawned, reset the HasBeenInitialized so if the associated NetworkObject instance
// is recylced (i.e. in-scene placed or pooled) it will re-initialize the LastUpdateSent time.
HasBeenInitialized = false;
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1550,8 +1550,12 @@ private void UnloadRemainingScenes()

private void LogWaitForMessages()
{
VerboseDebug(m_WaitForLog.ToString());
m_WaitForLog.Clear();
// If there is nothing to log, then don't log anything
if (m_WaitForLog.Length > 0)
{
VerboseDebug(m_WaitForLog.ToString());
m_WaitForLog.Clear();
}
}

private IEnumerator WaitForTickAndFrames(NetworkManager networkManager, int tickCount, float targetFrames)
Expand Down
Loading

0 comments on commit c032b55

Please sign in to comment.