using System; using System.Collections.Generic; using System.Linq; using System.Text; using Unity.Collections; using UnityEngine.SceneManagement; namespace Unity.Netcode { /// /// The different types of scene events communicated between a server and client.
/// Used by for messages.
/// Note: This is only when is enabled.
/// See also:
/// ///
public enum SceneEventType : byte { /// /// Load a scene
/// Invocation: Server Side
/// Message Flow: Server to client
/// Event Notification: Both server and client are notified a load scene event started ///
Load, /// /// Unload a scene
/// Invocation: Server Side
/// Message Flow: Server to client
/// Event Notification: Both server and client are notified an unload scene event started. ///
Unload, /// /// Synchronizes current game session state for newly approved clients
/// Invocation: Server Side
/// Message Flow: Server to client
/// Event Notification: Server and Client receives a local notification (server receives the ClientId being synchronized). ///
Synchronize, /// /// Game session re-synchronization of NetworkObjects that were destroyed during a event
/// Invocation: Server Side
/// Message Flow: Server to client
/// Event Notification: Both server and client receive a local notification
///
ReSynchronize, /// /// All clients have finished loading a scene
/// Invocation: Server Side
/// Message Flow: Server to Client
/// Event Notification: Both server and client receive a local notification containing the clients that finished /// as well as the clients that timed out(if any). ///
LoadEventCompleted, /// /// All clients have unloaded a scene
/// Invocation: Server Side
/// Message Flow: Server to Client
/// Event Notification: Both server and client receive a local notification containing the clients that finished /// as well as the clients that timed out(if any). ///
UnloadEventCompleted, /// /// A client has finished loading a scene
/// Invocation: Client Side
/// Message Flow: Client to Server
/// Event Notification: Both server and client receive a local notification. ///
LoadComplete, /// /// A client has finished unloading a scene
/// Invocation: Client Side
/// Message Flow: Client to Server
/// Event Notification: Both server and client receive a local notification. ///
UnloadComplete, /// /// A client has finished synchronizing from a event
/// Invocation: Client Side
/// Message Flow: Client to Server
/// Event Notification: Both server and client receive a local notification. ///
SynchronizeComplete, /// /// Synchronizes clients when the active scene has changed /// See: /// ActiveSceneChanged, /// /// Synchronizes clients when one or more NetworkObjects are migrated into a new scene /// See: /// ObjectSceneChanged, } /// /// Used by for messages /// Note: This is only when is enabled.
/// See also: ///
internal class SceneEventData : IDisposable { internal SceneEventType SceneEventType; internal LoadSceneMode LoadSceneMode; internal ForceNetworkSerializeByMemcpy SceneEventProgressId; internal uint SceneEventId; internal uint ActiveSceneHash; internal uint SceneHash; internal int SceneHandle; // Used by the client during synchronization internal uint ClientSceneHash; internal int NetworkSceneHandle; /// Only used for scene events, this assures permissions when writing /// NetworkVariable information. If that process changes, then we need to update this /// In distributed authority mode this is used to route messages to the appropriate destination client internal ulong TargetClientId; /// Only used with a DAHost internal ulong SenderClientId; private Dictionary> m_SceneNetworkObjects; private Dictionary m_SceneNetworkObjectDataOffsets; /// /// Client or Server Side: /// Client side: Generates a list of all NetworkObjects by their NetworkObjectId that was spawned during th synchronization process /// Server side: Compares list from client to make sure client didn't drop a message about a NetworkObject being despawned while it /// was synchronizing (if so server will send another message back to the client informing the client of NetworkObjects to remove) /// spawned during an initial synchronization. /// private List m_NetworkObjectsSync = new List(); private List m_DespawnedInSceneObjectsSync = new List(); private Dictionary> m_DespawnedInSceneObjects = new Dictionary>(); /// /// Server Side Re-Synchronization: /// If there happens to be NetworkObjects in the final Event_Sync_Complete message that are no longer spawned, /// the server will compile a list and send back an Event_ReSync message to the client. /// private List m_NetworkObjectsToBeRemoved = new List(); private bool m_HasInternalBuffer; internal FastBufferReader InternalBuffer; private NetworkManager m_NetworkManager; internal List ClientsCompleted; internal List ClientsTimedOut; internal Queue ScenesToSynchronize; internal Queue SceneHandlesToSynchronize; internal LoadSceneMode ClientSynchronizationMode; /// /// Server Side: /// Add a scene and its handle to the list of scenes the client should load before synchronizing /// Since scene handles are not the same per instance, the client builds a server scene handle to /// client scene handle lookup table. /// Why include the scene handle? In order to support loading of the same additive scene more than once /// we must distinguish which scene we are talking about when the server tells the client to unload a scene. /// The server will always communicate its local relative scene's handle and the client will determine its /// local relative handle from the table being built. /// Look for usage to see where /// entries are being added to or removed from the table /// /// /// internal void AddSceneToSynchronize(uint sceneHash, int sceneHandle) { ScenesToSynchronize.Enqueue(sceneHash); SceneHandlesToSynchronize.Enqueue((uint)sceneHandle); } /// /// Client Side: /// Gets the next scene hash to be loaded for approval and/or late joining /// /// internal uint GetNextSceneSynchronizationHash() { return ScenesToSynchronize.Dequeue(); } /// /// Client Side: /// Gets the next scene handle to be loaded for approval and/or late joining /// /// internal int GetNextSceneSynchronizationHandle() { return (int)SceneHandlesToSynchronize.Dequeue(); } /// /// Client Side: /// Determines if all scenes have been processed during the synchronization process /// /// true/false internal bool IsDoneWithSynchronization() { if (ScenesToSynchronize.Count == 0 && SceneHandlesToSynchronize.Count == 0) { return true; } else if (ScenesToSynchronize.Count != SceneHandlesToSynchronize.Count) { // This should never happen, but in the event it does... throw new Exception($"[{nameof(SceneEventData)}-Internal Mismatch Error] {nameof(ScenesToSynchronize)} count != {nameof(SceneHandlesToSynchronize)} count!"); } return false; } /// /// Server Side: /// Called just before the synchronization process /// internal void InitializeForSynch() { if (m_SceneNetworkObjects == null) { m_SceneNetworkObjects = new Dictionary>(); } else { m_SceneNetworkObjects.Clear(); } if (ScenesToSynchronize == null) { ScenesToSynchronize = new Queue(); } else { ScenesToSynchronize.Clear(); } if (SceneHandlesToSynchronize == null) { SceneHandlesToSynchronize = new Queue(); } else { SceneHandlesToSynchronize.Clear(); } ForwardSynchronization = false; } /// /// Used with SortParentedNetworkObjects to sort the children of the root parent NetworkObject /// /// object to be sorted /// object to be compared to for sorting the first object /// private int SortChildrenNetworkObjects(NetworkObject first, NetworkObject second) { var firstParent = first.GetCachedParent()?.GetComponent(); // If the second is the first's parent then move the first down if (firstParent != null && firstParent == second) { return 1; } var secondParent = second.GetCachedParent()?.GetComponent(); // If the first is the second's parent then move the first up if (secondParent != null && secondParent == first) { return -1; } // Otherwise, don't move the first at all return 0; } /// /// Sorts the synchronization order of the NetworkObjects to be serialized /// by parents before children order /// private void SortParentedNetworkObjects() { var networkObjectList = m_NetworkObjectsSync.ToList(); foreach (var networkObject in networkObjectList) { // Find only the root parent NetworkObjects if (networkObject.transform.childCount > 0 && networkObject.transform.parent == null) { // Get all child NetworkObjects of the root var childNetworkObjects = networkObject.GetComponentsInChildren().ToList(); childNetworkObjects.Sort(SortChildrenNetworkObjects); // Remove the root from the children list childNetworkObjects.Remove(networkObject); // Remove the root's children from the primary list foreach (var childObject in childNetworkObjects) { m_NetworkObjectsSync.Remove(childObject); } // Insert or Add the sorted children list var nextIndex = m_NetworkObjectsSync.IndexOf(networkObject) + 1; if (nextIndex == m_NetworkObjectsSync.Count) { m_NetworkObjectsSync.AddRange(childNetworkObjects); } else { m_NetworkObjectsSync.InsertRange(nextIndex, childNetworkObjects); } } } } internal static bool LogSerializationOrder = false; internal void AddSpawnedNetworkObjects() { m_NetworkObjectsSync.Clear(); // If distributed authority mode and sending to the service, then ignore observers var distributedAuthoritySendingToService = m_NetworkManager.DistributedAuthorityMode && TargetClientId == NetworkManager.ServerClientId; foreach (var sobj in m_NetworkManager.SpawnManager.SpawnedObjectsList) { if (sobj.Observers.Contains(TargetClientId) || distributedAuthoritySendingToService) { m_NetworkObjectsSync.Add(sobj); } } // Sort by INetworkPrefabInstanceHandler implementation before the // NetworkObjects spawned by the implementation m_NetworkObjectsSync.Sort(SortNetworkObjects); // The last thing we sort is parents before children SortParentedNetworkObjects(); // This is useful to know what NetworkObjects a client is going to be synchronized with // as well as the order in which they will be deserialized if (LogSerializationOrder && m_NetworkManager.LogLevel == LogLevel.Developer) { var messageBuilder = new StringBuilder(0xFFFF); messageBuilder.AppendLine("[Server-Side Client-Synchronization] NetworkObject serialization order:"); foreach (var networkObject in m_NetworkObjectsSync) { messageBuilder.AppendLine($"{networkObject.name}"); } NetworkLog.LogInfo(messageBuilder.ToString()); } } internal void AddDespawnedInSceneNetworkObjects() { m_DespawnedInSceneObjectsSync.Clear(); // Find all active and non-active in-scene placed NetworkObjects #if UNITY_2023_1_OR_NEWER var inSceneNetworkObjects = UnityEngine.Object.FindObjectsByType(UnityEngine.FindObjectsInactive.Include, UnityEngine.FindObjectsSortMode.InstanceID).Where((c) => c.NetworkManager == m_NetworkManager); #else var inSceneNetworkObjects = UnityEngine.Object.FindObjectsOfType(includeInactive: true).Where((c) => c.NetworkManager == m_NetworkManager); #endif foreach (var sobj in inSceneNetworkObjects) { if (sobj.IsSceneObject.HasValue && sobj.IsSceneObject.Value && !sobj.IsSpawned) { m_DespawnedInSceneObjectsSync.Add(sobj); } } } /// /// Server Side: /// Used during the synchronization process to associate NetworkObjects with scenes /// /// /// internal void AddNetworkObjectForSynch(uint sceneIndex, NetworkObject networkObject) { if (!m_SceneNetworkObjects.ContainsKey(sceneIndex)) { m_SceneNetworkObjects.Add(sceneIndex, new List()); } m_SceneNetworkObjects[sceneIndex].Add(networkObject); } /// /// Client and Server: /// Determines if the scene event type was intended for the client ( or server ) /// /// true (client should handle this message) false (server should handle this message) internal bool IsSceneEventClientSide() { switch (SceneEventType) { case SceneEventType.Load: case SceneEventType.Unload: case SceneEventType.Synchronize: case SceneEventType.ReSynchronize: case SceneEventType.LoadEventCompleted: case SceneEventType.UnloadEventCompleted: case SceneEventType.ActiveSceneChanged: case SceneEventType.ObjectSceneChanged: { return true; } } return false; } /// /// Server Side: /// Sorts the NetworkObjects to assure proper instantiation order of operations for /// registered INetworkPrefabInstanceHandler implementations /// /// /// /// private int SortNetworkObjects(NetworkObject first, NetworkObject second) { var doesFirstHaveHandler = m_NetworkManager.PrefabHandler.ContainsHandler(first); var doesSecondHaveHandler = m_NetworkManager.PrefabHandler.ContainsHandler(second); if (doesFirstHaveHandler != doesSecondHaveHandler) { if (doesFirstHaveHandler) { return 1; } else { return -1; } } return 0; } internal bool EnableSerializationLogs = false; private void LogArray(byte[] data, int start = 0, int stop = 0, StringBuilder builder = null) { var usingExternalBuilder = builder != null; if (!usingExternalBuilder) { builder = new StringBuilder(); } if (stop == 0) { stop = data.Length; } builder.AppendLine($"[Start Data Dump][Start = {start}][Stop = {stop}] Size ({stop - start})"); for (int i = start; i < stop; i++) { builder.Append($"{data[i]:X2} "); } builder.Append("\n"); if (!usingExternalBuilder) { UnityEngine.Debug.Log(builder.ToString()); } } internal bool ForwardSynchronization; /// /// Client and Server Side: /// Serializes data based on the SceneEvent type () /// /// to write the scene event data internal void Serialize(FastBufferWriter writer) { // Write the scene event type writer.WriteValueSafe(SceneEventType); if (m_NetworkManager.DistributedAuthorityMode) { BytePacker.WriteValueBitPacked(writer, TargetClientId); BytePacker.WriteValueBitPacked(writer, SenderClientId); } if (SceneEventType == SceneEventType.ActiveSceneChanged) { writer.WriteValueSafe(ActiveSceneHash); return; } if (SceneEventType == SceneEventType.ObjectSceneChanged) { SerializeObjectsMovedIntoNewScene(writer); return; } // Write the scene loading mode writer.WriteValueSafe((byte)LoadSceneMode); // Write the scene event progress Guid if (SceneEventType != SceneEventType.Synchronize) { writer.WriteValueSafe(SceneEventProgressId); } else { writer.WriteValueSafe(ClientSynchronizationMode); } // Write the scene index and handle writer.WriteValueSafe(SceneHash); writer.WriteValueSafe(SceneHandle); switch (SceneEventType) { case SceneEventType.Synchronize: { writer.WriteValueSafe(ActiveSceneHash); WriteSceneSynchronizationData(writer); if (EnableSerializationLogs) { LogArray(writer.ToArray(), 0, writer.Length); } break; } case SceneEventType.Load: { if (m_NetworkManager.DistributedAuthorityMode && IsForwarding && m_NetworkManager.DAHost) { CopyInternalBuffer(ref writer); } else { SerializeScenePlacedObjects(writer); } break; } case SceneEventType.SynchronizeComplete: { WriteClientSynchronizationResults(writer); break; } case SceneEventType.ReSynchronize: { WriteClientReSynchronizationData(writer); break; } case SceneEventType.LoadEventCompleted: case SceneEventType.UnloadEventCompleted: { WriteSceneEventProgressDone(writer); break; } } } private unsafe void CopyInternalBuffer(ref FastBufferWriter writer) { writer.WriteBytesSafe(InternalBuffer.GetUnsafePtrAtCurrentPosition(), InternalBuffer.Length); } /// /// Server Side: /// Called at the end of a event once the scene is loaded and scene placed NetworkObjects /// have been locally spawned /// internal void WriteSceneSynchronizationData(FastBufferWriter writer) { var builder = (StringBuilder)null; if (EnableSerializationLogs) { builder = new StringBuilder(); builder.AppendLine($"[Write][Synchronize-Start][WPos: {writer.Position}] Begin:"); } // Write the scenes we want to load, in the order we want to load them 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; if (m_NetworkManager.DistributedAuthorityMode && ForwardSynchronization && m_NetworkManager.DAHost) { writer.WriteValueSafe(m_InternalBufferSize); CopyInternalBuffer(ref writer); if (EnableSerializationLogs) { LogArray(writer.ToArray(), positionStart); } return; } // Size Place Holder -- Start // !!NOTE!!: Since this is a placeholder to be set after we know how much we have written, // for stream offset purposes this MUST not be a packed value! writer.WriteValueSafe(0); int totalBytes = 0; // Write the number of NetworkObjects we are serializing writer.WriteValueSafe(m_NetworkObjectsSync.Count); if (EnableSerializationLogs) { builder.AppendLine($"[Synchronize Objects][positionStart: {positionStart}][WPos: {writer.Position}][NO-Count: {m_NetworkObjectsSync.Count}] Begin:"); } var distributedAuthority = m_NetworkManager.DistributedAuthorityMode; // Serialize all NetworkObjects that are spawned for (var i = 0; i < m_NetworkObjectsSync.Count; ++i) { var networkObject = m_NetworkObjectsSync[i]; var noStart = writer.Position; // In distributed authority mode, we send the currently known observers of each NetworkObject to the client being synchronized. var sceneObject = m_NetworkObjectsSync[i].GetMessageSceneObject(TargetClientId, distributedAuthority); sceneObject.Serialize(writer); var noStop = writer.Position; totalBytes += noStop - noStart; if (EnableSerializationLogs) { var offStart = noStart - (positionStart + sizeof(int)); var offStop = noStop - (positionStart + sizeof(int)); builder.AppendLine($"[Head: {offStart}][Tail: {offStop}][Size: {offStop - offStart}][{networkObject.name}][NID-{networkObject.NetworkObjectId}][Children: {networkObject.ChildNetworkBehaviours.Count}]"); LogArray(writer.ToArray(), noStart, noStop, builder); } } if (EnableSerializationLogs) { UnityEngine.Debug.Log(builder.ToString()); } // 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; writer.WriteValueSafe(m_DespawnedInSceneObjectsSync[i].GetSceneOriginHandle()); writer.WriteValueSafe(m_DespawnedInSceneObjectsSync[i].GlobalObjectIdHash); var noStop = writer.Position; totalBytes += noStop - noStart; } // Size Place Holder -- End var positionEnd = writer.Position; var bytesWritten = (uint)(positionEnd - (positionStart + sizeof(uint))); writer.Seek(positionStart); // Write the total size written to the stream by NetworkObjects being serialized writer.WriteValueSafe(bytesWritten); writer.Seek(positionEnd); if (EnableSerializationLogs) { LogArray(writer.ToArray(), positionStart); } } /// /// Server Side: /// Called at the end of a event once the scene is loaded and scene placed NetworkObjects /// have been locally spawned /// Maximum number of objects that could theoretically be synchronized is 65536 /// internal void SerializeScenePlacedObjects(FastBufferWriter writer) { var numberOfObjects = (ushort)0; var headPosition = writer.Position; // Write our count place holder (must not be packed!) writer.WriteValueSafe((ushort)0); var distributedAuthority = m_NetworkManager.DistributedAuthorityMode; // If distributed authority mode and sending to the service, then ignore observers var distributedAuthoritySendingToService = distributedAuthority && TargetClientId == NetworkManager.ServerClientId; foreach (var keyValuePairByGlobalObjectIdHash in m_NetworkManager.SceneManager.ScenePlacedObjects) { foreach (var keyValuePairBySceneHandle in keyValuePairByGlobalObjectIdHash.Value) { if (keyValuePairBySceneHandle.Value.Observers.Contains(TargetClientId) || distributedAuthoritySendingToService) { // Serialize the NetworkObject var sceneObject = keyValuePairBySceneHandle.Value.GetMessageSceneObject(TargetClientId, distributedAuthority); sceneObject.Serialize(writer); numberOfObjects++; } } } // 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) { writer.WriteValueSafe(m_DespawnedInSceneObjectsSync[i].GetSceneOriginHandle()); writer.WriteValueSafe(m_DespawnedInSceneObjectsSync[i].GlobalObjectIdHash); } var tailPosition = writer.Position; // Reposition to our count position to the head before we wrote our object count writer.Seek(headPosition); // Write number of NetworkObjects serialized (must not be packed!) writer.WriteValueSafe(numberOfObjects); // Set our position back to the tail writer.Seek(tailPosition); } /// /// Client and Server Side: /// Deserialize data based on the SceneEvent type. /// /// internal void Deserialize(FastBufferReader reader) { reader.ReadValueSafe(out SceneEventType); if (m_NetworkManager.DistributedAuthorityMode) { ByteUnpacker.ReadValueBitPacked(reader, out TargetClientId); ByteUnpacker.ReadValueBitPacked(reader, out SenderClientId); } if (SceneEventType == SceneEventType.ActiveSceneChanged) { reader.ReadValueSafe(out ActiveSceneHash); return; } if (SceneEventType == SceneEventType.ObjectSceneChanged) { // Defer these scene event types if a client hasn't finished synchronizing if (!m_NetworkManager.IsConnectedClient) { DeferObjectsMovedIntoNewScene(reader); } else { DeserializeObjectsMovedIntoNewScene(reader); } return; } reader.ReadValueSafe(out byte loadSceneMode); LoadSceneMode = (LoadSceneMode)loadSceneMode; if (SceneEventType != SceneEventType.Synchronize) { reader.ReadValueSafe(out SceneEventProgressId); } else { reader.ReadValueSafe(out ClientSynchronizationMode); } reader.ReadValueSafe(out SceneHash); reader.ReadValueSafe(out SceneHandle); switch (SceneEventType) { case SceneEventType.Synchronize: { reader.ReadValueSafe(out ActiveSceneHash); if (EnableSerializationLogs) { LogArray(reader.ToArray(), 0, reader.Length); } CopySceneSynchronizationData(reader); break; } case SceneEventType.SynchronizeComplete: { CheckClientSynchronizationResults(reader); break; } case SceneEventType.Load: { unsafe { // We store off the trailing in-scene placed serialized NetworkObject data to // be processed once we are done loading. m_HasInternalBuffer = true; // We use Allocator.Persistent since scene loading could take longer than 4 frames InternalBuffer = new FastBufferReader(reader.GetUnsafePtrAtCurrentPosition(), Allocator.Persistent, reader.Length - reader.Position); } break; } case SceneEventType.ReSynchronize: { ReadClientReSynchronizationData(reader); break; } case SceneEventType.LoadEventCompleted: case SceneEventType.UnloadEventCompleted: { ReadSceneEventProgressDone(reader); break; } } } private int m_InternalBufferSize; /// /// Client Side: /// Prepares for a scene synchronization event and copies the scene synchronization data /// into the internal buffer to be used throughout the synchronization process. /// /// internal void CopySceneSynchronizationData(FastBufferReader reader) { m_NetworkObjectsSync.Clear(); reader.ReadValueSafe(out uint[] scenesToSynchronize); reader.ReadValueSafe(out uint[] sceneHandlesToSynchronize); ScenesToSynchronize = new Queue(scenesToSynchronize); SceneHandlesToSynchronize = new Queue(sceneHandlesToSynchronize); // is not packed! reader.ReadValueSafe(out int sizeToCopy); m_InternalBufferSize = sizeToCopy; unsafe { if (!reader.TryBeginRead(sizeToCopy)) { throw new OverflowException("Not enough space in the buffer to read recorded synchronization data size."); } m_HasInternalBuffer = true; // We use Allocator.Persistent since scene synchronization will most likely take longer than 4 frames InternalBuffer = new FastBufferReader(reader.GetUnsafePtrAtCurrentPosition(), Allocator.Persistent, sizeToCopy); if (EnableSerializationLogs) { LogArray(InternalBuffer.ToArray()); } } } /// /// Client Side: /// This needs to occur at the end of a event when the scene has finished loading /// Maximum number of objects that could theoretically be synchronized is 65536 /// internal void DeserializeScenePlacedObjects() { try { // is not packed! InternalBuffer.ReadValueSafe(out ushort newObjectsCount); var sceneObjects = new List(); for (ushort i = 0; i < newObjectsCount; i++) { var sceneObject = new NetworkObject.SceneObject(); sceneObject.Deserialize(InternalBuffer); if (sceneObject.IsSceneObject) { // Set our relative scene to the NetworkObject m_NetworkManager.SceneManager.SetTheSceneBeingSynchronized(sceneObject.NetworkSceneHandle); } var networkObject = NetworkObject.AddSceneObject(sceneObject, InternalBuffer, m_NetworkManager); if (sceneObject.IsSceneObject) { sceneObjects.Add(networkObject); } } // Now deserialize the despawned in-scene placed NetworkObjects list (if any) DeserializeDespawnedInScenePlacedNetworkObjects(); // Notify all newly spawned in-scene placed NetworkObjects that all in-scene placed // NetworkObjects have been spawned. foreach (var networkObject in sceneObjects) { networkObject.InternalInSceneNetworkObjectsSpawned(); } } finally { InternalBuffer.Dispose(); m_HasInternalBuffer = false; } } /// /// Client Side: /// If there happens to be NetworkObjects in the final Event_Sync_Complete message that are no longer spawned, /// the server will compile a list and send back an Event_ReSync message to the client. This is where the /// client handles any returned values by the server. /// /// internal void ReadClientReSynchronizationData(FastBufferReader reader) { reader.ReadValueSafe(out uint[] networkObjectsToRemove); if (networkObjectsToRemove.Length > 0) { #if UNITY_2023_1_OR_NEWER var networkObjects = UnityEngine.Object.FindObjectsByType(UnityEngine.FindObjectsSortMode.InstanceID); #else var networkObjects = UnityEngine.Object.FindObjectsOfType(); #endif var networkObjectIdToNetworkObject = new Dictionary(); foreach (var networkObject in networkObjects) { if (!networkObjectIdToNetworkObject.ContainsKey(networkObject.NetworkObjectId)) { networkObjectIdToNetworkObject.Add(networkObject.NetworkObjectId, networkObject); } } foreach (var networkObjectId in networkObjectsToRemove) { if (networkObjectIdToNetworkObject.ContainsKey(networkObjectId)) { var networkObject = networkObjectIdToNetworkObject[networkObjectId]; networkObjectIdToNetworkObject.Remove(networkObjectId); networkObject.IsSpawned = false; if (m_NetworkManager.PrefabHandler.ContainsHandler(networkObject)) { if (m_NetworkManager.SpawnManager.SpawnedObjects.ContainsKey(networkObjectId)) { m_NetworkManager.SpawnManager.SpawnedObjects.Remove(networkObjectId); } if (m_NetworkManager.SpawnManager.SpawnedObjectsList.Contains(networkObject)) { m_NetworkManager.SpawnManager.SpawnedObjectsList.Remove(networkObject); } NetworkManager.Singleton.PrefabHandler.HandleNetworkPrefabDestroy(networkObject); } else { UnityEngine.Object.DestroyImmediate(networkObject.gameObject); } } } } } /// /// Server Side: /// If there happens to be NetworkObjects in the final Event_Sync_Complete message that are no longer spawned, /// the server will compile a list and send back an Event_ReSync message to the client. /// /// internal void WriteClientReSynchronizationData(FastBufferWriter writer) { //Write how many objects need to be removed writer.WriteValueSafe(m_NetworkObjectsToBeRemoved.ToArray()); } /// /// Server Side: /// Determines if the client needs to be slightly re-synchronized if during the deserialization /// process the server finds NetworkObjects that the client still thinks are spawned. /// /// internal bool ClientNeedsReSynchronization() { return (m_NetworkObjectsToBeRemoved.Count > 0); } /// /// Server Side: /// Determines if the client needs to be re-synchronized if during the deserialization /// process the server finds NetworkObjects that the client still thinks are spawned but /// have since been despawned. /// /// internal void CheckClientSynchronizationResults(FastBufferReader reader) { m_NetworkObjectsToBeRemoved.Clear(); reader.ReadValueSafe(out uint networkObjectIdCount); for (int i = 0; i < networkObjectIdCount; i++) { reader.ReadValueSafe(out uint networkObjectId); if (!m_NetworkManager.SpawnManager.SpawnedObjects.ContainsKey(networkObjectId)) { m_NetworkObjectsToBeRemoved.Add(networkObjectId); } } } /// /// Client Side: /// During the deserialization process of the servers Event_Sync, the client builds a list of /// all NetworkObjectIds that were spawned. Upon responding to the server with the Event_Sync_Complete /// this list is included for the server to review over and determine if the client needs a minor resynchronization /// of NetworkObjects that might have been despawned while the client was processing the Event_Sync. /// /// internal void WriteClientSynchronizationResults(FastBufferWriter writer) { //Write how many objects were spawned writer.WriteValueSafe((uint)m_NetworkObjectsSync.Count); foreach (var networkObject in m_NetworkObjectsSync) { writer.WriteValueSafe((uint)networkObject.NetworkObjectId); } } /// /// For synchronizing any despawned in-scene placed NetworkObjects that were /// despawned by the server during synchronization or scene loading /// private void DeserializeDespawnedInScenePlacedNetworkObjects() { // Process all de-spawned in-scene NetworkObjects for this network session m_DespawnedInSceneObjects.Clear(); InternalBuffer.ReadValueSafe(out int despawnedObjectsCount); var sceneCache = new Dictionary>(); for (int i = 0; i < despawnedObjectsCount; i++) { // We just need to get the scene InternalBuffer.ReadValueSafe(out int networkSceneHandle); InternalBuffer.ReadValueSafe(out uint globalObjectIdHash); var sceneRelativeNetworkObjects = new Dictionary(); 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]; // Find all active and non-active in-scene placed NetworkObjects #if UNITY_2023_1_OR_NEWER var inSceneNetworkObjects = UnityEngine.Object.FindObjectsByType(UnityEngine.FindObjectsInactive.Include, UnityEngine.FindObjectsSortMode.InstanceID).Where((c) => c.GetSceneOriginHandle() == localSceneHandle && (c.IsSceneObject != false)).ToList(); #else var inSceneNetworkObjects = UnityEngine.Object.FindObjectsOfType(includeInactive: true).Where((c) => c.GetSceneOriginHandle() == localSceneHandle && (c.IsSceneObject != false)).ToList(); #endif foreach (var inSceneObject in inSceneNetworkObjects) { if (!sceneRelativeNetworkObjects.ContainsKey(inSceneObject.GlobalObjectIdHash)) { 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()); } 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!"); } } } /// /// Client Side: /// During the processing of a server sent Event_Sync, this method will be called for each scene once /// it is finished loading. The client will also build a list of NetworkObjects that it spawned during /// this process which will be used as part of the Event_Sync_Complete response. /// /// internal void SynchronizeSceneNetworkObjects(NetworkManager networkManager) { var builder = (StringBuilder)null; if (EnableSerializationLogs) { builder = new StringBuilder(); } try { // Process all spawned NetworkObjects for this network session InternalBuffer.ReadValueSafe(out int newObjectsCount); if (EnableSerializationLogs) { builder.AppendLine($"[Read][Synchronize Objects][WPos: {InternalBuffer.Position}][NO-Count: {newObjectsCount}] Begin:"); } for (int i = 0; i < newObjectsCount; i++) { var noStart = InternalBuffer.Position; var sceneObject = new NetworkObject.SceneObject(); sceneObject.Deserialize(InternalBuffer); // If the sceneObject is in-scene placed, then set the scene being synchronized if (sceneObject.IsSceneObject) { m_NetworkManager.SceneManager.SetTheSceneBeingSynchronized(sceneObject.NetworkSceneHandle); } var spawnedNetworkObject = NetworkObject.AddSceneObject(sceneObject, InternalBuffer, networkManager); var noStop = InternalBuffer.Position; if (EnableSerializationLogs) { builder.AppendLine($"[Head: {noStart}][Tail: {noStop}][Size: {noStop - noStart}][{spawnedNetworkObject.name}][NID-{spawnedNetworkObject.NetworkObjectId}][Children: {spawnedNetworkObject.ChildNetworkBehaviours.Count}]"); LogArray(InternalBuffer.ToArray(), noStart, noStop, builder); } // If we failed to deserialize the NetowrkObject then don't add null to the list if (spawnedNetworkObject != null) { if (!m_NetworkObjectsSync.Contains(spawnedNetworkObject)) { m_NetworkObjectsSync.Add(spawnedNetworkObject); } } } if (EnableSerializationLogs) { UnityEngine.Debug.Log(builder.ToString()); } // Notify that all in-scene placed NetworkObjects have been spawned foreach (var networkObject in m_NetworkObjectsSync) { if (networkObject.IsSceneObject.HasValue && networkObject.IsSceneObject.Value) { networkObject.InternalInSceneNetworkObjectsSpawned(); } } // Now deserialize the despawned in-scene placed NetworkObjects list (if any) DeserializeDespawnedInScenePlacedNetworkObjects(); } catch (Exception ex) { UnityEngine.Debug.LogException(ex); UnityEngine.Debug.Log(builder.ToString()); } finally { InternalBuffer.Dispose(); m_HasInternalBuffer = false; } } /// /// Writes the all clients loaded or unloaded completed and timed out lists /// /// internal void WriteSceneEventProgressDone(FastBufferWriter writer) { writer.WriteValueSafe((ushort)ClientsCompleted.Count); foreach (var clientId in ClientsCompleted) { writer.WriteValueSafe(clientId); } writer.WriteValueSafe((ushort)ClientsTimedOut.Count); foreach (var clientId in ClientsTimedOut) { writer.WriteValueSafe(clientId); } } /// /// Reads the all clients loaded or unloaded completed and timed out lists /// /// internal void ReadSceneEventProgressDone(FastBufferReader reader) { reader.ReadValueSafe(out ushort completedCount); ClientsCompleted = new List(); for (int i = 0; i < completedCount; i++) { reader.ReadValueSafe(out ulong clientId); ClientsCompleted.Add(clientId); } reader.ReadValueSafe(out ushort timedOutCount); ClientsTimedOut = new List(); for (int i = 0; i < timedOutCount; i++) { reader.ReadValueSafe(out ulong clientId); ClientsTimedOut.Add(clientId); } } /// /// Serialize scene handles and associated NetworkObjects that were migrated /// into a new scene. /// internal bool IsForwarding; private ulong m_OwnerId; private void SerializeObjectsMovedIntoNewScene(FastBufferWriter writer) { var sceneManager = m_NetworkManager.SceneManager; var ownerId = m_NetworkManager.LocalClientId; if (IsForwarding) { ownerId = m_OwnerId; } // Write the owner identifier writer.WriteValueSafe(ownerId); // Write the number of scene handles writer.WriteValueSafe(sceneManager.ObjectsMigratedIntoNewScene.Count); foreach (var sceneHandleObjects in sceneManager.ObjectsMigratedIntoNewScene) { if (!sceneManager.ObjectsMigratedIntoNewScene[sceneHandleObjects.Key].ContainsKey(ownerId)) { throw new Exception($"Trying to send object scene migration for Client-{ownerId} but the client has no entries to send!"); } // Write the scene handle writer.WriteValueSafe(sceneHandleObjects.Key); // Write the number of NetworkObjectIds to expect writer.WriteValueSafe(sceneHandleObjects.Value[ownerId].Count); foreach (var networkObject in sceneHandleObjects.Value[ownerId]) { writer.WriteValueSafe(networkObject.NetworkObjectId); } } } /// /// Deserialize scene handles and associated NetworkObjects that need to /// be migrated into a new scene. /// private void DeserializeObjectsMovedIntoNewScene(FastBufferReader reader) { var sceneManager = m_NetworkManager.SceneManager; var spawnManager = m_NetworkManager.SpawnManager; var numberOfScenes = 0; var sceneHandle = 0; var objectCount = 0; var networkObjectId = (ulong)0; var ownerID = (ulong)0; reader.ReadValueSafe(out ownerID); m_OwnerId = ownerID; reader.ReadValueSafe(out numberOfScenes); for (int i = 0; i < numberOfScenes; i++) { reader.ReadValueSafe(out sceneHandle); if (!sceneManager.ObjectsMigratedIntoNewScene.ContainsKey(sceneHandle)) { sceneManager.ObjectsMigratedIntoNewScene.Add(sceneHandle, new Dictionary>()); } if (!sceneManager.ObjectsMigratedIntoNewScene[sceneHandle].ContainsKey(ownerID)) { sceneManager.ObjectsMigratedIntoNewScene[sceneHandle].Add(ownerID, new List()); } reader.ReadValueSafe(out objectCount); for (int j = 0; j < objectCount; j++) { reader.ReadValueSafe(out networkObjectId); if (!spawnManager.SpawnedObjects.ContainsKey(networkObjectId)) { NetworkLog.LogError($"[Object Scene Migration] Trying to synchronize NetworkObjectId ({networkObjectId}) but it was not spawned or no longer exists!!"); continue; } var networkObject = spawnManager.SpawnedObjects[networkObjectId]; // Add NetworkObject scene migration to ObjectsMigratedIntoNewScene dictionary that is processed sceneManager.ObjectsMigratedIntoNewScene[sceneHandle][ownerID].Add(networkObject); } } } /// /// While a client is synchronizing ObjectSceneChanged messages could be received. /// This defers any ObjectSceneChanged message processing to occur after the client /// has completed synchronization to assure the associated NetworkObjects being /// migrated to a new scene are instantiated and spawned. /// private void DeferObjectsMovedIntoNewScene(FastBufferReader reader) { var sceneManager = m_NetworkManager.SceneManager; var spawnManager = m_NetworkManager.SpawnManager; var ownerId = (ulong)0; var numberOfScenes = 0; var sceneHandle = 0; var objectCount = 0; var networkObjectId = (ulong)0; reader.ReadValueSafe(out ownerId); var deferredObjectsMovedEvent = new NetworkSceneManager.DeferredObjectsMovedEvent() { OwnerId = ownerId, ObjectsMigratedTable = new Dictionary>(), }; reader.ReadValueSafe(out numberOfScenes); for (int i = 0; i < numberOfScenes; i++) { reader.ReadValueSafe(out sceneHandle); deferredObjectsMovedEvent.ObjectsMigratedTable.Add(sceneHandle, new List()); reader.ReadValueSafe(out objectCount); for (int j = 0; j < objectCount; j++) { reader.ReadValueSafe(out networkObjectId); deferredObjectsMovedEvent.ObjectsMigratedTable[sceneHandle].Add(networkObjectId); } } sceneManager.DeferredObjectsMovedEvents.Add(deferredObjectsMovedEvent); } internal void ProcessDeferredObjectSceneChangedEvents() { var sceneManager = m_NetworkManager.SceneManager; var spawnManager = m_NetworkManager.SpawnManager; if (sceneManager.DeferredObjectsMovedEvents.Count == 0) { return; } foreach (var objectsMovedEvent in sceneManager.DeferredObjectsMovedEvents) { foreach (var keyEntry in objectsMovedEvent.ObjectsMigratedTable) { if (!sceneManager.ObjectsMigratedIntoNewScene.ContainsKey(keyEntry.Key)) { sceneManager.ObjectsMigratedIntoNewScene.Add(keyEntry.Key, new Dictionary>()); } if (!sceneManager.ObjectsMigratedIntoNewScene[keyEntry.Key].ContainsKey(objectsMovedEvent.OwnerId)) { sceneManager.ObjectsMigratedIntoNewScene[keyEntry.Key].Add(objectsMovedEvent.OwnerId, new List()); } foreach (var objectId in keyEntry.Value) { if (!spawnManager.SpawnedObjects.ContainsKey(objectId)) { NetworkLog.LogWarning($"[Deferred][Object Scene Migration] Trying to synchronize NetworkObjectId ({objectId}) but it was not spawned or no longer exists!"); continue; } var networkObject = spawnManager.SpawnedObjects[objectId]; if (!sceneManager.ObjectsMigratedIntoNewScene[keyEntry.Key][objectsMovedEvent.OwnerId].Contains(networkObject)) { sceneManager.ObjectsMigratedIntoNewScene[keyEntry.Key][objectsMovedEvent.OwnerId].Add(networkObject); } } } objectsMovedEvent.ObjectsMigratedTable.Clear(); } sceneManager.DeferredObjectsMovedEvents.Clear(); } /// /// Used to release the pooled network buffer /// public void Dispose() { if (m_HasInternalBuffer) { InternalBuffer.Dispose(); m_HasInternalBuffer = false; } } /// /// Constructor for SceneEventData /// internal SceneEventData(NetworkManager networkManager) { m_NetworkManager = networkManager; SceneEventId = XXHash.Hash32(Guid.NewGuid().ToString()); } } }