Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: networkvariablebase not being reinitialized if networkobject persists between sessions #3181

Open
wants to merge 11 commits into
base: develop-2.0.0
Choose a base branch
from
Open
1 change: 1 addition & 0 deletions com.unity.netcode.gameobjects/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Additional documentation and release notes are available at [Multiplayer Documen
### Fixed

- 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)
- Fixed issue where `NetworkVariableBase` derived classes were not being re-initialized if the associated `NetworkObject` instance was not destroyed and re-spawned. (#3181)

### Changed

Expand Down
27 changes: 27 additions & 0 deletions com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs
Original file line number Diff line number Diff line change
Expand Up @@ -815,6 +815,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 @@ -918,10 +925,30 @@ internal void __nameNetworkVariable(NetworkVariableBase variable, string varName
variable.Name = varName;
}

/// <summary>
/// Does a first pass initialization for RPCs and NetworkVariables
/// If already initialized, then it just re-initializes the NetworkVariables.
/// </summary>
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 @@ -35,6 +35,12 @@ public abstract class NetworkVariableBase : IDisposable

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; }

internal string GetWritePermissionError()
{
return $"|Client-{m_NetworkManager.LocalClientId}|{m_NetworkBehaviour.name}|{Name}| Write permissions ({WritePerm}) for this client instance is not allowed!";
Expand All @@ -45,17 +51,7 @@ 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;

public NetworkBehaviour GetBehaviour()
{
Expand All @@ -68,21 +64,77 @@ public NetworkBehaviour GetBehaviour()
/// <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;
// When in distributed authority mode, there is no such thing as server write permissions
InternalWritePerm = m_InternalNetworkManager.DistributedAuthorityMode ? NetworkVariableWritePermission.Owner : InternalWritePerm;
// 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;

// When in distributed authority mode, there is no such thing as server write permissions
InternalWritePerm = m_InternalNetworkManager.DistributedAuthorityMode ? NetworkVariableWritePermission.Owner : InternalWritePerm;

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 @@ -1821,8 +1821,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
Loading