com.unity.netcode.gameobjects@1.0.0-pre.10

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com).

## [1.0.0-pre.10] - 2022-06-21

### Added

- Added a new `OnTransportFailure` callback to `NetworkManager`. This callback is invoked when the manager's `NetworkTransport` encounters an unrecoverable error. Transport failures also cause the `NetworkManager` to shut down. Currently, this is only used by `UnityTransport` to signal a timeout of its connection to the Unity Relay servers. (#1994)
- Added `NetworkEvent.TransportFailure`, which can be used by implementations of `NetworkTransport` to signal to `NetworkManager` that an unrecoverable error was encountered. (#1994)
- Added test to ensure a warning occurs when nesting NetworkObjects in a NetworkPrefab (#1969)
- Added `NetworkManager.RemoveNetworkPrefab(...)` to remove a prefab from the prefabs list (#1950)

### Changed

- Updated `UnityTransport` dependency on `com.unity.transport` to 1.1.0. (#2025)
- (API Breaking) `ConnectionApprovalCallback` is no longer an `event` and will not allow more than 1 handler registered at a time. Also, `ConnectionApprovalCallback` is now a `Func<>` taking `ConnectionApprovalRequest` in and returning `ConnectionApprovalResponse` back out (#1972)

### Removed

### Fixed
- Fixed issue where dynamically spawned `NetworkObject`s could throw an exception if the scene of origin handle was zero (0) and the `NetworkObject` was already spawned. (#2017)
- Fixed issue where `NetworkObject.Observers` was not being cleared when despawned. (#2009)
- Fixed `NetworkAnimator` could not run in the server authoritative mode. (#2003)
- Fixed issue where late joining clients would get a soft synchronization error if any in-scene placed NetworkObjects were parented under another `NetworkObject`. (#1985)
- Fixed issue where `NetworkBehaviourReference` would throw a type cast exception if using `NetworkBehaviourReference.TryGet` and the component type was not found. (#1984)
- Fixed `NetworkSceneManager` was not sending scene event notifications for the currently active scene and any additively loaded scenes when loading a new scene in `LoadSceneMode.Single` mode. (#1975)
- Fixed issue where one or more clients disconnecting during a scene event would cause `LoadEventCompleted` or `UnloadEventCompleted` to wait until the `NetworkConfig.LoadSceneTimeOut` period before being triggered. (#1973)
- Fixed issues when multiple `ConnectionApprovalCallback`s were registered (#1972)
- Fixed a regression in serialization support: `FixedString`, `Vector2Int`, and `Vector3Int` types can now be used in NetworkVariables and RPCs again without requiring a `ForceNetworkSerializeByMemcpy<>` wrapper. (#1961)
- Fixed generic types that inherit from NetworkBehaviour causing crashes at compile time. (#1976)
- Fixed endless dialog boxes when adding a `NetworkBehaviour` to a `NetworkManager` or vice-versa. (#1947)
- Fixed `NetworkAnimator` issue where it was only synchronizing parameters if the layer or state changed or was transitioning between states. (#1946)
- Fixed `NetworkAnimator` issue where when it did detect a parameter had changed it would send all parameters as opposed to only the parameters that changed. (#1946)
- Fixed `NetworkAnimator` issue where it was not always disposing the `NativeArray` that is allocated when spawned. (#1946)
- Fixed `NetworkAnimator` issue where it was not taking the animation speed or state speed multiplier into consideration. (#1946)
- Fixed `NetworkAnimator` issue where it was not properly synchronizing late joining clients if they joined while `Animator` was transitioning between states. (#1946)
- Fixed `NetworkAnimator` issue where the server was not relaying changes to non-owner clients when a client was the owner. (#1946)
- Fixed issue where the `PacketLoss` metric for tools would return the packet loss over a connection lifetime instead of a single frame. (#2004)
This commit is contained in:
Unity Technologies
2022-06-21 00:00:00 +00:00
parent 5b1fc203ed
commit 0f7a30d285
62 changed files with 3763 additions and 1286 deletions

View File

@@ -15,8 +15,14 @@ namespace Unity.Netcode
{
internal uint SceneEventId;
internal Action<uint> EventAction;
/// <summary>
/// Used server-side for integration testing in order to
/// invoke the SceneEventProgress once done loading
/// </summary>
internal Action Completed;
internal void Invoke()
{
Completed?.Invoke();
EventAction.Invoke(SceneEventId);
}
}

View File

@@ -12,6 +12,7 @@ namespace Unity.Netcode
/// delegate type <see cref="NetworkSceneManager.SceneEventDelegate"/> uses this class to provide
/// scene event status.<br/>
/// <em>Note: This is only when <see cref="NetworkConfig.EnableSceneManagement"/> is enabled.</em><br/>
/// <em>*** Do not start new scene events within scene event notification callbacks.</em><br/>
/// See also: <br/>
/// <seealso cref="SceneEventType"/>
/// </summary>
@@ -166,6 +167,7 @@ namespace Unity.Netcode
/// <item><term><see cref="OnUnloadComplete"/> Invoked only when an <see cref="SceneEventType.UnloadComplete"/> event is being processed</term></item>
/// <item><term><see cref="OnSynchronizeComplete"/> Invoked only when a <see cref="SceneEventType.SynchronizeComplete"/> event is being processed</term></item>
/// </list>
/// <em>Note: Do not start new scene events within NetworkSceneManager scene event notification callbacks.</em><br/>
/// </summary>
public event SceneEventDelegate OnSceneEvent;
@@ -239,13 +241,15 @@ namespace Unity.Netcode
/// <summary>
/// Invoked when a <see cref="SceneEventType.Load"/> event is started by the server.<br/>
/// <em>Note: The server and connected client(s) will always receive this notification.</em>
/// <em>Note: The server and connected client(s) will always receive this notification.</em><br/>
/// <em>*** Do not start new scene events within scene event notification callbacks.</em><br/>
/// </summary>
public event OnLoadDelegateHandler OnLoad;
/// <summary>
/// Invoked when a <see cref="SceneEventType.Unload"/> event is started by the server.<br/>
/// <em>Note: The server and connected client(s) will always receive this notification.</em>
/// <em>Note: The server and connected client(s) will always receive this notification.</em><br/>
/// <em>*** Do not start new scene events within scene event notification callbacks.</em><br/>
/// </summary>
public event OnUnloadDelegateHandler OnUnload;
@@ -254,7 +258,8 @@ namespace Unity.Netcode
/// after a client is approved for connection in order to synchronize the client with the currently loaded
/// scenes and NetworkObjects. This event signifies the beginning of the synchronization event.<br/>
/// <em>Note: The server and connected client(s) will always receive this notification.
/// This event is generated on a per newly connected and approved client basis.</em>
/// This event is generated on a per newly connected and approved client basis.</em><br/>
/// <em>*** Do not start new scene events within scene event notification callbacks.</em><br/>
/// </summary>
public event OnSynchronizeDelegateHandler OnSynchronize;
@@ -263,7 +268,8 @@ namespace Unity.Netcode
/// This event signifies the end of an existing <see cref="SceneEventType.Load"/> event as it pertains
/// to all clients connected when the event was started. This event signifies that all clients (and server) have
/// finished the <see cref="SceneEventType.Load"/> event.<br/>
/// <em>Note: this is useful to know when all clients have loaded the same scene (single or additive mode)</em>
/// <em>Note: this is useful to know when all clients have loaded the same scene (single or additive mode)</em><br/>
/// <em>*** Do not start new scene events within scene event notification callbacks.</em><br/>
/// </summary>
public event OnEventCompletedDelegateHandler OnLoadEventCompleted;
@@ -273,21 +279,24 @@ namespace Unity.Netcode
/// to all clients connected when the event was started. This event signifies that all clients (and server) have
/// finished the <see cref="SceneEventType.Unload"/> event.<br/>
/// <em>Note: this is useful to know when all clients have unloaded a specific scene. The <see cref="LoadSceneMode"/> will
/// always be <see cref="LoadSceneMode.Additive"/> for this event.</em>
/// always be <see cref="LoadSceneMode.Additive"/> for this event.</em><br/>
/// <em>*** Do not start new scene events within scene event notification callbacks.</em><br/>
/// </summary>
public event OnEventCompletedDelegateHandler OnUnloadEventCompleted;
/// <summary>
/// Invoked when a <see cref="SceneEventType.LoadComplete"/> event is generated by a client or server.<br/>
/// <em>Note: The server receives this message from all clients (including itself).
/// Each client receives their own notification sent to the server.</em>
/// Each client receives their own notification sent to the server.</em><br/>
/// <em>*** Do not start new scene events within scene event notification callbacks.</em><br/>
/// </summary>
public event OnLoadCompleteDelegateHandler OnLoadComplete;
/// <summary>
/// Invoked when a <see cref="SceneEventType.UnloadComplete"/> event is generated by a client or server.<br/>
/// <em>Note: The server receives this message from all clients (including itself).
/// Each client receives their own notification sent to the server.</em>
/// Each client receives their own notification sent to the server.</em><br/>
/// <em>*** Do not start new scene events within scene event notification callbacks.</em><br/>
/// </summary>
public event OnUnloadCompleteDelegateHandler OnUnloadComplete;
@@ -296,6 +305,7 @@ namespace Unity.Netcode
/// <em> Note: The server receives this message from the client, but will never generate this event for itself.
/// Each client receives their own notification sent to the server. This is useful to know that a client has
/// completed the entire connection sequence, loaded all scenes, and synchronized all NetworkObjects.</em>
/// <em>*** Do not start new scene events within scene event notification callbacks.</em><br/>
/// </summary>
public event OnSynchronizeCompleteDelegateHandler OnSynchronizeComplete;
@@ -319,8 +329,12 @@ namespace Unity.Netcode
public VerifySceneBeforeLoadingDelegateHandler VerifySceneBeforeLoading;
/// <summary>
/// Proof of concept: Interface version that allows for the decoupling from
/// the SceneManager's Load and Unload methods for testing purposes (potentially other future usage)
/// The SceneManagerHandler implementation
/// </summary>
internal ISceneManagerHandler SceneManagerHandler = new DefaultSceneManagerHandler();
/// <summary>
/// The default SceneManagerHandler that interfaces between the SceneManager and NetworkSceneManager
/// </summary>
private class DefaultSceneManagerHandler : ISceneManagerHandler
{
@@ -339,10 +353,6 @@ namespace Unity.Netcode
}
}
internal ISceneManagerHandler SceneManagerHandler = new DefaultSceneManagerHandler();
/// End of Proof of Concept
internal readonly Dictionary<Guid, SceneEventProgress> SceneEventProgressTracking = new Dictionary<Guid, SceneEventProgress>();
/// <summary>
@@ -425,6 +435,8 @@ namespace Unity.Netcode
/// </summary>
public void Dispose()
{
SceneUnloadEventHandler.Shutdown();
foreach (var keypair in SceneEventDataStore)
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
@@ -632,6 +644,12 @@ namespace Unity.Netcode
return validated;
}
/// <summary>
/// Used for NetcodeIntegrationTest testing in order to properly
/// assign the right loaded scene to the right client's ScenesLoaded list
/// </summary>
internal Func<string, Scene> OverrideGetAndAddNewlyLoadedSceneByName;
/// <summary>
/// Since SceneManager.GetSceneByName only returns the first scene that matches the name
/// we must "find" a newly added scene by looking through all loaded scenes and determining
@@ -643,20 +661,27 @@ namespace Unity.Netcode
/// <returns></returns>
internal Scene GetAndAddNewlyLoadedSceneByName(string sceneName)
{
for (int i = 0; i < SceneManager.sceneCount; i++)
if (OverrideGetAndAddNewlyLoadedSceneByName != null)
{
var sceneLoaded = SceneManager.GetSceneAt(i);
if (sceneLoaded.name == sceneName)
return OverrideGetAndAddNewlyLoadedSceneByName.Invoke(sceneName);
}
else
{
for (int i = 0; i < SceneManager.sceneCount; i++)
{
if (!ScenesLoaded.ContainsKey(sceneLoaded.handle))
var sceneLoaded = SceneManager.GetSceneAt(i);
if (sceneLoaded.name == sceneName)
{
ScenesLoaded.Add(sceneLoaded.handle, sceneLoaded);
return sceneLoaded;
if (!ScenesLoaded.ContainsKey(sceneLoaded.handle))
{
ScenesLoaded.Add(sceneLoaded.handle, sceneLoaded);
return sceneLoaded;
}
}
}
}
throw new Exception($"Failed to find any loaded scene named {sceneName}!");
throw new Exception($"Failed to find any loaded scene named {sceneName}!");
}
}
/// <summary>
@@ -720,18 +745,18 @@ namespace Unity.Netcode
/// </summary>
/// <param name="globalObjectIdHash"></param>
/// <returns></returns>
internal NetworkObject GetSceneRelativeInSceneNetworkObject(uint globalObjectIdHash)
internal NetworkObject GetSceneRelativeInSceneNetworkObject(uint globalObjectIdHash, int? networkSceneHandle)
{
if (ScenePlacedObjects.ContainsKey(globalObjectIdHash))
{
if (ScenePlacedObjects[globalObjectIdHash].ContainsKey(SceneBeingSynchronized.handle))
var sceneHandle = SceneBeingSynchronized.handle;
if (networkSceneHandle.HasValue && networkSceneHandle.Value != 0)
{
var inScenePlacedNetworkObject = ScenePlacedObjects[globalObjectIdHash][SceneBeingSynchronized.handle];
// We can only have 1 duplicated globalObjectIdHash per scene instance, so remove it once it has been returned
ScenePlacedObjects[globalObjectIdHash].Remove(SceneBeingSynchronized.handle);
return inScenePlacedNetworkObject;
sceneHandle = ServerSceneHandleToClientSceneHandle[networkSceneHandle.Value];
}
if (ScenePlacedObjects[globalObjectIdHash].ContainsKey(sceneHandle))
{
return ScenePlacedObjects[globalObjectIdHash][sceneHandle];
}
}
return null;
@@ -859,7 +884,6 @@ namespace Unity.Netcode
/// Callback for the <see cref="SceneEventProgress.OnComplete"/> <see cref="SceneEventProgress.OnCompletedDelegate"/> handler
/// </summary>
/// <param name="sceneEventProgress"></param>
/// <returns></returns>
private bool OnSceneEventProgressCompleted(SceneEventProgress sceneEventProgress)
{
var sceneEventData = BeginSceneEvent();
@@ -868,7 +892,7 @@ namespace Unity.Netcode
sceneEventData.SceneEventType = sceneEventProgress.SceneEventType;
sceneEventData.ClientsCompleted = sceneEventProgress.DoneClients;
sceneEventData.LoadSceneMode = sceneEventProgress.LoadSceneMode;
sceneEventData.ClientsTimedOut = m_NetworkManager.ConnectedClients.Keys.Except(sceneEventProgress.DoneClients).ToList();
sceneEventData.ClientsTimedOut = sceneEventProgress.ClientsThatStartedSceneEvent.Except(sceneEventProgress.DoneClients).ToList();
var message = new SceneEventMessage
{
@@ -945,11 +969,18 @@ namespace Unity.Netcode
sceneEventProgress.SceneEventType = SceneEventType.UnloadEventCompleted;
ScenesLoaded.Remove(scene.handle);
var sceneEventAction = new ISceneManagerHandler.SceneEventAction() { SceneEventId = sceneEventData.SceneEventId, EventAction = OnSceneUnloaded };
var sceneUnload = SceneManagerHandler.UnloadSceneAsync(scene, sceneEventAction);
var sceneUnload = SceneManagerHandler.UnloadSceneAsync(scene,
new ISceneManagerHandler.SceneEventAction() { SceneEventId = sceneEventData.SceneEventId, EventAction = OnSceneUnloaded });
sceneEventProgress.SetSceneLoadOperation(sceneUnload);
// If integration testing, IntegrationTestSceneHandler returns null
if (sceneUnload == null)
{
sceneEventProgress.SetSceneLoadOperation(sceneEventAction);
}
else
{
sceneEventProgress.SetSceneLoadOperation(sceneUnload);
}
// Notify local server that a scene is going to be unloaded
OnSceneEvent?.Invoke(new SceneEvent()
@@ -1021,6 +1052,12 @@ namespace Unity.Netcode
/// </summary>
private void OnSceneUnloaded(uint sceneEventId)
{
// If we are shutdown or about to shutdown, then ignore this event
if (!m_NetworkManager.IsListening || m_NetworkManager.ShutdownInProgress)
{
return;
}
var sceneEventData = SceneEventDataStore[sceneEventId];
// First thing we do, if we are a server, is to send the unload scene event.
if (m_NetworkManager.IsServer)
@@ -1064,7 +1101,7 @@ namespace Unity.Netcode
private void EmptySceneUnloadedOperation(uint sceneEventId)
{
// Do nothing (this is a stub call since it is only used to flush all currently loaded scenes)
// Do nothing (this is a stub call since it is only used to flush all additively loaded scenes)
}
/// <summary>
@@ -1074,6 +1111,7 @@ namespace Unity.Netcode
/// </summary>
internal void UnloadAdditivelyLoadedScenes(uint sceneEventId)
{
var sceneEventData = SceneEventDataStore[sceneEventId];
// Unload all additive scenes while making sure we don't try to unload the base scene ( loaded in single mode ).
var currentActiveScene = SceneManager.GetActiveScene();
foreach (var keyHandleEntry in ScenesLoaded)
@@ -1083,15 +1121,7 @@ namespace Unity.Netcode
{
var sceneUnload = SceneManagerHandler.UnloadSceneAsync(keyHandleEntry.Value,
new ISceneManagerHandler.SceneEventAction() { SceneEventId = sceneEventId, EventAction = EmptySceneUnloadedOperation });
OnSceneEvent?.Invoke(new SceneEvent()
{
AsyncOperation = sceneUnload,
SceneEventType = SceneEventType.Unload,
SceneName = keyHandleEntry.Value.name,
LoadSceneMode = LoadSceneMode.Additive, // The only scenes unloaded are scenes that were additively loaded
ClientId = NetworkManager.ServerClientId
});
SceneUnloadEventHandler.RegisterScene(this, keyHandleEntry.Value, LoadSceneMode.Additive, sceneUnload);
}
}
// clear out our scenes loaded list
@@ -1124,12 +1154,12 @@ namespace Unity.Netcode
sceneEventData.SceneEventType = SceneEventType.Load;
sceneEventData.SceneHash = SceneHashFromNameOrPath(sceneName);
sceneEventData.LoadSceneMode = loadSceneMode;
var sceneEventId = sceneEventData.SceneEventId;
// This both checks to make sure the scene is valid and if not resets the active scene event
m_IsSceneEventActive = ValidateSceneBeforeLoading(sceneEventData.SceneHash, loadSceneMode);
if (!m_IsSceneEventActive)
{
EndSceneEvent(sceneEventData.SceneEventId);
EndSceneEvent(sceneEventId);
return SceneEventProgressStatus.SceneFailedVerification;
}
@@ -1142,14 +1172,24 @@ namespace Unity.Netcode
MoveObjectsToDontDestroyOnLoad();
// Now Unload all currently additively loaded scenes
UnloadAdditivelyLoadedScenes(sceneEventData.SceneEventId);
UnloadAdditivelyLoadedScenes(sceneEventId);
// Register the active scene for unload scene event notifications
SceneUnloadEventHandler.RegisterScene(this, SceneManager.GetActiveScene(), LoadSceneMode.Single);
}
// Now start loading the scene
var sceneLoad = SceneManagerHandler.LoadSceneAsync(sceneName, loadSceneMode,
new ISceneManagerHandler.SceneEventAction() { SceneEventId = sceneEventData.SceneEventId, EventAction = OnSceneLoaded });
sceneEventProgress.SetSceneLoadOperation(sceneLoad);
var sceneEventAction = new ISceneManagerHandler.SceneEventAction() { SceneEventId = sceneEventId, EventAction = OnSceneLoaded };
var sceneLoad = SceneManagerHandler.LoadSceneAsync(sceneName, loadSceneMode, sceneEventAction);
// If integration testing, IntegrationTestSceneHandler returns null
if (sceneLoad == null)
{
sceneEventProgress.SetSceneLoadOperation(sceneEventAction);
}
else
{
sceneEventProgress.SetSceneLoadOperation(sceneLoad);
}
// Notify the local server that a scene loading event has begun
OnSceneEvent?.Invoke(new SceneEvent()
@@ -1167,6 +1207,114 @@ namespace Unity.Netcode
return sceneEventProgress.Status;
}
/// <summary>
/// Helper class used to handle "odd ball" scene unload event notification scenarios
/// when scene switching.
/// </summary>
internal class SceneUnloadEventHandler
{
private static Dictionary<NetworkManager, List<SceneUnloadEventHandler>> s_Instances = new Dictionary<NetworkManager, List<SceneUnloadEventHandler>>();
internal static void RegisterScene(NetworkSceneManager networkSceneManager, Scene scene, LoadSceneMode loadSceneMode, AsyncOperation asyncOperation = null)
{
var networkManager = networkSceneManager.m_NetworkManager;
if (!s_Instances.ContainsKey(networkManager))
{
s_Instances.Add(networkManager, new List<SceneUnloadEventHandler>());
}
var clientId = networkManager.IsServer ? NetworkManager.ServerClientId : networkManager.LocalClientId;
s_Instances[networkManager].Add(new SceneUnloadEventHandler(networkSceneManager, scene, clientId, loadSceneMode, asyncOperation));
}
private static void SceneUnloadComplete(SceneUnloadEventHandler sceneUnloadEventHandler)
{
if (sceneUnloadEventHandler == null || sceneUnloadEventHandler.m_NetworkSceneManager == null || sceneUnloadEventHandler.m_NetworkSceneManager.m_NetworkManager == null)
{
return;
}
var networkManager = sceneUnloadEventHandler.m_NetworkSceneManager.m_NetworkManager;
if (s_Instances.ContainsKey(networkManager))
{
s_Instances[networkManager].Remove(sceneUnloadEventHandler);
if (s_Instances[networkManager].Count == 0)
{
s_Instances.Remove(networkManager);
}
}
}
/// <summary>
/// Called by NetworkSceneManager when it is disposing
/// </summary>
internal static void Shutdown()
{
foreach (var instanceEntry in s_Instances)
{
foreach (var instance in instanceEntry.Value)
{
instance.OnShutdown();
}
instanceEntry.Value.Clear();
}
s_Instances.Clear();
}
private NetworkSceneManager m_NetworkSceneManager;
private AsyncOperation m_AsyncOperation;
private LoadSceneMode m_LoadSceneMode;
private ulong m_ClientId;
private Scene m_Scene;
private bool m_ShuttingDown;
private void OnShutdown()
{
m_ShuttingDown = true;
SceneManager.sceneUnloaded -= SceneUnloaded;
}
private void SceneUnloaded(Scene scene)
{
if (m_Scene.handle == scene.handle && !m_ShuttingDown)
{
if (m_NetworkSceneManager != null && m_NetworkSceneManager.m_NetworkManager != null)
{
m_NetworkSceneManager.OnSceneEvent?.Invoke(new SceneEvent()
{
AsyncOperation = m_AsyncOperation,
SceneEventType = SceneEventType.UnloadComplete,
SceneName = m_Scene.name,
LoadSceneMode = m_LoadSceneMode,
ClientId = m_ClientId
});
m_NetworkSceneManager.OnUnloadComplete?.Invoke(m_ClientId, m_Scene.name);
}
SceneManager.sceneUnloaded -= SceneUnloaded;
SceneUnloadComplete(this);
}
}
private SceneUnloadEventHandler(NetworkSceneManager networkSceneManager, Scene scene, ulong clientId, LoadSceneMode loadSceneMode, AsyncOperation asyncOperation = null)
{
m_LoadSceneMode = loadSceneMode;
m_AsyncOperation = asyncOperation;
m_NetworkSceneManager = networkSceneManager;
m_ClientId = clientId;
m_Scene = scene;
SceneManager.sceneUnloaded += SceneUnloaded;
// Send the initial unload event notification
m_NetworkSceneManager.OnSceneEvent?.Invoke(new SceneEvent()
{
AsyncOperation = m_AsyncOperation,
SceneEventType = SceneEventType.Unload,
SceneName = m_Scene.name,
LoadSceneMode = m_LoadSceneMode,
ClientId = clientId
});
m_NetworkSceneManager.OnUnload?.Invoke(networkSceneManager.m_NetworkManager.LocalClientId, m_Scene.name, null);
}
}
/// <summary>
/// Client Side:
/// Handles both forms of scene loading
@@ -1201,6 +1349,10 @@ namespace Unity.Netcode
if (sceneEventData.LoadSceneMode == LoadSceneMode.Single)
{
IsSpawnedObjectsPendingInDontDestroyOnLoad = true;
// Register the active scene for unload scene event notifications
SceneUnloadEventHandler.RegisterScene(this, SceneManager.GetActiveScene(), LoadSceneMode.Single);
}
var sceneLoad = SceneManagerHandler.LoadSceneAsync(sceneName, sceneEventData.LoadSceneMode,
@@ -1218,13 +1370,18 @@ namespace Unity.Netcode
OnLoad?.Invoke(m_NetworkManager.LocalClientId, sceneName, sceneEventData.LoadSceneMode, sceneLoad);
}
/// <summary>
/// Client and Server:
/// Generic on scene loaded callback method to be called upon a scene loading
/// </summary>
private void OnSceneLoaded(uint sceneEventId)
{
// If we are shutdown or about to shutdown, then ignore this event
if (!m_NetworkManager.IsListening || m_NetworkManager.ShutdownInProgress)
{
return;
}
var sceneEventData = SceneEventDataStore[sceneEventId];
var nextScene = GetAndAddNewlyLoadedSceneByName(SceneNameFromHash(sceneEventData.SceneHash));
if (!nextScene.isLoaded || !nextScene.IsValid())
@@ -1363,6 +1520,13 @@ namespace Unity.Netcode
EndSceneEvent(sceneEventId);
}
/// <summary>
/// Used for integration testing, due to the complexities of having all clients loading scenes
/// this is needed to "filter" out the scenes not loaded by NetworkSceneManager
/// (i.e. we don't want a late joining player to load all of the other client scenes)
/// </summary>
internal Func<Scene, bool> ExcludeSceneFromSychronization;
/// <summary>
/// Server Side:
/// This is used for players that have just had their connection approved and will assure they are synchronized
@@ -1389,6 +1553,13 @@ namespace Unity.Netcode
{
var scene = SceneManager.GetSceneAt(i);
// NetworkSceneManager does not synchronize scenes that are not loaded by NetworkSceneManager
// unless the scene in question is the currently active scene.
if (ExcludeSceneFromSychronization != null && !ExcludeSceneFromSychronization(scene))
{
continue;
}
var sceneHash = SceneHashFromNameOrPath(scene.path);
// This would depend upon whether we are additive or not
@@ -1406,11 +1577,11 @@ namespace Unity.Netcode
{
continue;
}
sceneEventData.AddSceneToSynchronize(sceneHash, scene.handle);
}
sceneEventData.AddSpawnedNetworkObjects();
sceneEventData.AddDespawnedInSceneNetworkObjects();
var message = new SceneEventMessage
{
@@ -1447,7 +1618,7 @@ namespace Unity.Netcode
var loadSceneMode = sceneHash == sceneEventData.SceneHash ? sceneEventData.LoadSceneMode : LoadSceneMode.Additive;
// Store the sceneHandle and hash
sceneEventData.ClientSceneHandle = sceneHandle;
sceneEventData.NetworkSceneHandle = sceneHandle;
sceneEventData.ClientSceneHash = sceneHash;
// If this is the beginning of the synchronization event, then send client a notification that synchronization has begun
@@ -1536,9 +1707,9 @@ namespace Unity.Netcode
SceneManager.SetActiveScene(nextScene);
}
if (!ServerSceneHandleToClientSceneHandle.ContainsKey(sceneEventData.ClientSceneHandle))
if (!ServerSceneHandleToClientSceneHandle.ContainsKey(sceneEventData.NetworkSceneHandle))
{
ServerSceneHandleToClientSceneHandle.Add(sceneEventData.ClientSceneHandle, nextScene.handle);
ServerSceneHandleToClientSceneHandle.Add(sceneEventData.NetworkSceneHandle, nextScene.handle);
}
else
{
@@ -1862,10 +2033,9 @@ namespace Unity.Netcode
foreach (var networkObjectInstance in networkObjects)
{
var globalObjectIdHash = networkObjectInstance.GlobalObjectIdHash;
var sceneHandle = networkObjectInstance.gameObject.scene.handle;
var sceneHandle = networkObjectInstance.GetSceneOriginHandle();
// We check to make sure the NetworkManager instance is the same one to be "NetcodeIntegrationTestHelpers" compatible and filter the list on a per scene basis (for additive scenes)
if (networkObjectInstance.IsSceneObject != false && networkObjectInstance.NetworkManager == m_NetworkManager && networkObjectInstance.gameObject.scene == sceneToFilterBy &&
sceneHandle == sceneToFilterBy.handle)
if (networkObjectInstance.IsSceneObject != false && networkObjectInstance.NetworkManager == m_NetworkManager && sceneHandle == sceneToFilterBy.handle)
{
if (!ScenePlacedObjects.ContainsKey(globalObjectIdHash))
{

View File

@@ -100,7 +100,7 @@ namespace Unity.Netcode
// Used by the client during synchronization
internal uint ClientSceneHash;
internal int ClientSceneHandle;
internal int NetworkSceneHandle;
/// Only used for <see cref="SceneEventType.Synchronize"/> scene events, this assures permissions when writing
/// NetworkVariable information. If that process changes, then we need to update this
@@ -118,6 +118,9 @@ namespace Unity.Netcode
/// </summary>
private List<NetworkObject> m_NetworkObjectsSync = new List<NetworkObject>();
private List<NetworkObject> m_DespawnedInSceneObjectsSync = new List<NetworkObject>();
private Dictionary<int, List<uint>> m_DespawnedInSceneObjects = new Dictionary<int, List<uint>>();
/// <summary>
/// Server Side Re-Synchronization:
/// If there happens to be NetworkObjects in the final Event_Sync_Complete message that are no longer spawned,
@@ -243,6 +246,19 @@ namespace Unity.Netcode
m_NetworkObjectsSync.Sort(SortNetworkObjects);
}
internal void AddDespawnedInSceneNetworkObjects()
{
m_DespawnedInSceneObjectsSync.Clear();
var inSceneNetworkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>().Where((c) => c.NetworkManager == m_NetworkManager);
foreach (var sobj in inSceneNetworkObjects)
{
if (sobj.IsSceneObject.HasValue && sobj.IsSceneObject.Value && !sobj.IsSpawned)
{
m_DespawnedInSceneObjectsSync.Add(sobj);
}
}
}
/// <summary>
/// Server Side:
/// Used during the synchronization process to associate NetworkObjects with scenes
@@ -372,7 +388,6 @@ namespace Unity.Netcode
writer.WriteValueSafe(ScenesToSynchronize.ToArray());
writer.WriteValueSafe(SceneHandlesToSynchronize.ToArray());
// Store our current position in the stream to come back and say how much data we have written
var positionStart = writer.Position;
@@ -383,17 +398,31 @@ namespace Unity.Netcode
int totalBytes = 0;
// Write the number of NetworkObjects we are serializing
writer.WriteValueSafe(m_NetworkObjectsSync.Count());
BytePacker.WriteValuePacked(writer, m_NetworkObjectsSync.Count());
// Serialize all NetworkObjects that are spawned
for (var i = 0; i < m_NetworkObjectsSync.Count(); ++i)
{
var noStart = writer.Position;
var sceneObject = m_NetworkObjectsSync[i].GetMessageSceneObject(TargetClientId);
writer.WriteValueSafe(m_NetworkObjectsSync[i].gameObject.scene.handle);
BytePacker.WriteValuePacked(writer, m_NetworkObjectsSync[i].GetSceneOriginHandle());
sceneObject.Serialize(writer);
var noStop = writer.Position;
totalBytes += (int)(noStop - noStart);
}
// Write the number of despawned in-scene placed NetworkObjects
writer.WriteValueSafe(m_DespawnedInSceneObjectsSync.Count());
// Write the scene handle and GlobalObjectIdHash value
for (var i = 0; i < m_DespawnedInSceneObjectsSync.Count(); ++i)
{
var noStart = writer.Position;
var sceneObject = m_DespawnedInSceneObjectsSync[i].GetMessageSceneObject(TargetClientId);
BytePacker.WriteValuePacked(writer, m_DespawnedInSceneObjectsSync[i].GetSceneOriginHandle());
BytePacker.WriteValuePacked(writer, m_DespawnedInSceneObjectsSync[i].GlobalObjectIdHash);
var noStop = writer.Position;
totalBytes += (int)(noStop - noStart);
}
// Size Place Holder -- End
var positionEnd = writer.Position;
var bytesWritten = (uint)(positionEnd - (positionStart + sizeof(uint)));
@@ -683,15 +712,16 @@ namespace Unity.Netcode
{
try
{
// Process all NetworkObjects for this scene
InternalBuffer.ReadValueSafe(out int newObjectsCount);
// Process all spawned NetworkObjects for this network session
ByteUnpacker.ReadValuePacked(InternalBuffer, out int newObjectsCount);
for (int i = 0; i < newObjectsCount; i++)
{
// We want to make sure for each NetworkObject we have the appropriate scene selected as the scene that is
// currently being synchronized. This assures in-scene placed NetworkObjects will use the right NetworkObject
// from the list of populated <see cref="NetworkSceneManager.ScenePlacedObjects"/>
InternalBuffer.ReadValueSafe(out int handle);
ByteUnpacker.ReadValuePacked(InternalBuffer, out int handle);
m_NetworkManager.SceneManager.SetTheSceneBeingSynchronized(handle);
var sceneObject = new NetworkObject.SceneObject();
@@ -703,6 +733,73 @@ namespace Unity.Netcode
m_NetworkObjectsSync.Add(spawnedNetworkObject);
}
}
// Process all de-spawned in-scene NetworkObjects for this network session
m_DespawnedInSceneObjects.Clear();
InternalBuffer.ReadValueSafe(out int despawnedObjectsCount);
var sceneCache = new Dictionary<int, Dictionary<uint, NetworkObject>>();
for (int i = 0; i < despawnedObjectsCount; i++)
{
// We just need to get the scene
ByteUnpacker.ReadValuePacked(InternalBuffer, out int networkSceneHandle);
ByteUnpacker.ReadValuePacked(InternalBuffer, out uint globalObjectIdHash);
var sceneRelativeNetworkObjects = new Dictionary<uint, NetworkObject>();
if (!sceneCache.ContainsKey(networkSceneHandle))
{
if (m_NetworkManager.SceneManager.ServerSceneHandleToClientSceneHandle.ContainsKey(networkSceneHandle))
{
var localSceneHandle = m_NetworkManager.SceneManager.ServerSceneHandleToClientSceneHandle[networkSceneHandle];
if (m_NetworkManager.SceneManager.ScenesLoaded.ContainsKey(localSceneHandle))
{
var objectRelativeScene = m_NetworkManager.SceneManager.ScenesLoaded[localSceneHandle];
var inSceneNetworkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>().Where((c) =>
c.GetSceneOriginHandle() == localSceneHandle && (c.IsSceneObject != false)).ToList();
foreach (var inSceneObject in inSceneNetworkObjects)
{
sceneRelativeNetworkObjects.Add(inSceneObject.GlobalObjectIdHash, inSceneObject);
}
// Add this to a cache so we don't have to run this potentially multiple times (nothing will spawn or despawn during this time
sceneCache.Add(networkSceneHandle, sceneRelativeNetworkObjects);
}
else
{
UnityEngine.Debug.LogError($"In-Scene NetworkObject GlobalObjectIdHash ({globalObjectIdHash}) cannot find its relative local scene handle {localSceneHandle}!");
}
}
else
{
UnityEngine.Debug.LogError($"In-Scene NetworkObject GlobalObjectIdHash ({globalObjectIdHash}) cannot find its relative NetworkSceneHandle {networkSceneHandle}!");
}
}
else // Use the cached NetworkObjects if they exist
{
sceneRelativeNetworkObjects = sceneCache[networkSceneHandle];
}
// Now find the in-scene NetworkObject with the current GlobalObjectIdHash we are looking for
if (sceneRelativeNetworkObjects.ContainsKey(globalObjectIdHash))
{
// Since this is a NetworkObject that was never spawned, we just need to send a notification
// out that it was despawned so users can make adjustments
sceneRelativeNetworkObjects[globalObjectIdHash].InvokeBehaviourNetworkDespawn();
if (!m_NetworkManager.SceneManager.ScenePlacedObjects.ContainsKey(globalObjectIdHash))
{
m_NetworkManager.SceneManager.ScenePlacedObjects.Add(globalObjectIdHash, new Dictionary<int, NetworkObject>());
}
if (!m_NetworkManager.SceneManager.ScenePlacedObjects[globalObjectIdHash].ContainsKey(sceneRelativeNetworkObjects[globalObjectIdHash].GetSceneOriginHandle()))
{
m_NetworkManager.SceneManager.ScenePlacedObjects[globalObjectIdHash].Add(sceneRelativeNetworkObjects[globalObjectIdHash].GetSceneOriginHandle(), sceneRelativeNetworkObjects[globalObjectIdHash]);
}
}
else
{
UnityEngine.Debug.LogError($"In-Scene NetworkObject GlobalObjectIdHash ({globalObjectIdHash}) could not be found!");
}
}
}
finally
{

View File

@@ -61,9 +61,9 @@ namespace Unity.Netcode
internal List<ulong> DoneClients { get; } = new List<ulong>();
/// <summary>
/// The NetworkTime at the moment the scene switch was initiated by the server.
/// The local time when the scene event was "roughly started"
/// </summary>
internal NetworkTime TimeAtInitiation { get; }
internal float TimeAtInitiation { get; }
/// <summary>
/// Delegate type for when the switch scene progress is completed. Either by all clients done loading the scene or by time out.
@@ -105,22 +105,40 @@ namespace Unity.Netcode
internal LoadSceneMode LoadSceneMode;
internal List<ulong> ClientsThatStartedSceneEvent;
internal SceneEventProgress(NetworkManager networkManager, SceneEventProgressStatus status = SceneEventProgressStatus.Started)
{
if (status == SceneEventProgressStatus.Started)
{
// Track the clients that were connected when we started this event
ClientsThatStartedSceneEvent = new List<ulong>(networkManager.ConnectedClientsIds);
m_NetworkManager = networkManager;
m_TimeOutCoroutine = m_NetworkManager.StartCoroutine(TimeOutSceneEventProgress());
TimeAtInitiation = networkManager.LocalTime;
TimeAtInitiation = Time.realtimeSinceStartup;
}
Status = status;
}
/// <summary>
/// Coroutine that checks to see if the scene event is complete every network tick period.
/// This will handle completing the scene event when one or more client(s) disconnect(s)
/// during a scene event and if it does not complete within the scene loading time out period
/// it will time out the scene event.
/// </summary>
internal IEnumerator TimeOutSceneEventProgress()
{
yield return new WaitForSecondsRealtime(m_NetworkManager.NetworkConfig.LoadSceneTimeOut);
TimedOut = true;
CheckCompletion();
var waitForNetworkTick = new WaitForSeconds(1.0f / m_NetworkManager.NetworkConfig.TickRate);
while (!TimedOut && !IsCompleted)
{
yield return waitForNetworkTick;
CheckCompletion();
if (!IsCompleted)
{
TimedOut = TimeAtInitiation - Time.realtimeSinceStartup >= m_NetworkManager.NetworkConfig.LoadSceneTimeOut;
}
}
}
internal void AddClientAsDone(ulong clientId)
@@ -141,19 +159,49 @@ namespace Unity.Netcode
m_SceneLoadOperation.completed += operation => CheckCompletion();
}
/// <summary>
/// Called only on the server-side during integration test (NetcodeIntegrationTest specific)
/// scene loading and unloading.
///
/// Note: During integration testing we must queue all scene loading and unloading requests for
/// both the server and all clients so they can be processed in a FIFO/linear fashion to avoid
/// conflicts when the <see cref="SceneManager.sceneLoaded"/> and <see cref="SceneManager.sceneUnloaded"/>
/// events are triggered. The Completed action simulates the <see cref="AsyncOperation.completed"/> event.
/// (See: Unity.Netcode.TestHelpers.Runtime.IntegrationTestSceneHandler)
/// </summary>
internal void SetSceneLoadOperation(ISceneManagerHandler.SceneEventAction sceneEventAction)
{
sceneEventAction.Completed = SetComplete;
}
/// <summary>
/// Finalizes the SceneEventProgress
/// </summary>
internal void SetComplete()
{
IsCompleted = true;
AreAllClientsDoneLoading = true;
// If OnComplete is not registered or it is and returns true then remove this from the progress tracking
if (OnComplete == null || (OnComplete != null && OnComplete.Invoke(this)))
{
m_NetworkManager.SceneManager.SceneEventProgressTracking.Remove(Guid);
}
m_NetworkManager.StopCoroutine(m_TimeOutCoroutine);
}
internal void CheckCompletion()
{
if ((!IsCompleted && DoneClients.Count == m_NetworkManager.ConnectedClientsList.Count && m_SceneLoadOperation.isDone) || (!IsCompleted && TimedOut))
try
{
IsCompleted = true;
AreAllClientsDoneLoading = true;
// If OnComplete is not registered or it is and returns true then remove this from the progress tracking
if (OnComplete == null || (OnComplete != null && OnComplete.Invoke(this)))
if ((!IsCompleted && DoneClients.Count == m_NetworkManager.ConnectedClientsList.Count && (m_SceneLoadOperation == null || m_SceneLoadOperation.isDone)) || (!IsCompleted && TimedOut))
{
m_NetworkManager.SceneManager.SceneEventProgressTracking.Remove(Guid);
SetComplete();
}
m_NetworkManager.StopCoroutine(m_TimeOutCoroutine);
}
catch (Exception ex)
{
Debug.LogException(ex);
}
}
}