This repository has been archived on 2025-04-22. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs
Unity Technologies 143a6cbd34 com.unity.netcode.gameobjects@2.0.0-exp.2
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).

## [2.0.0-exp.2] - 2024-04-02

### Added
- Added updates to all internal messages to account for a distributed authority network session connection.  (#2863)
- Added `NetworkRigidbodyBase` that provides users with a more customizable network rigidbody, handles both `Rigidbody` and `Rigidbody2D`, and provides an option to make `NetworkTransform` use the rigid body for motion.  (#2863)
  - For a customized `NetworkRigidbodyBase` class:
    - `NetworkRigidbodyBase.AutoUpdateKinematicState` provides control on whether the kinematic setting will be automatically set or not when ownership changes.
    - `NetworkRigidbodyBase.AutoSetKinematicOnDespawn` provides control on whether isKinematic will automatically be set to true when the associated `NetworkObject` is despawned.
    - `NetworkRigidbodyBase.Initialize` is a protected method that, when invoked, will initialize the instance. This includes options to:
      - Set whether using a `RigidbodyTypes.Rigidbody` or `RigidbodyTypes.Rigidbody2D`.
      - Includes additional optional parameters to set the `NetworkTransform`, `Rigidbody`, and `Rigidbody2d` to use.
  - Provides additional public methods:
    - `NetworkRigidbodyBase.GetPosition` to return the position of the `Rigidbody` or `Rigidbody2d` (depending upon its initialized setting).
    - `NetworkRigidbodyBase.GetRotation` to return the rotation of the `Rigidbody` or `Rigidbody2d` (depending upon its initialized setting).
    - `NetworkRigidbodyBase.MovePosition` to move to the position of the `Rigidbody` or `Rigidbody2d` (depending upon its initialized setting).
    - `NetworkRigidbodyBase.MoveRotation` to move to the rotation of the `Rigidbody` or `Rigidbody2d` (depending upon its initialized setting).
    - `NetworkRigidbodyBase.Move` to move to the position and rotation of the `Rigidbody` or `Rigidbody2d` (depending upon its initialized setting).
    - `NetworkRigidbodyBase.Move` to move to the position and rotation of the `Rigidbody` or `Rigidbody2d` (depending upon its initialized setting).
    - `NetworkRigidbodyBase.SetPosition` to set the position of the `Rigidbody` or `Rigidbody2d` (depending upon its initialized setting).
    - `NetworkRigidbodyBase.SetRotation` to set the rotation of the `Rigidbody` or `Rigidbody2d` (depending upon its initialized setting).
    - `NetworkRigidbodyBase.ApplyCurrentTransform` to set the position and rotation of the `Rigidbody` or `Rigidbody2d` based on the associated `GameObject` transform (depending upon its initialized setting).
    - `NetworkRigidbodyBase.WakeIfSleeping` to wake up the rigid body if sleeping.
    - `NetworkRigidbodyBase.SleepRigidbody` to put the rigid body to sleep.
    - `NetworkRigidbodyBase.IsKinematic` to determine if the `Rigidbody` or `Rigidbody2d` (depending upon its initialized setting) is currently kinematic.
    - `NetworkRigidbodyBase.SetIsKinematic` to set the `Rigidbody` or `Rigidbody2d` (depending upon its initialized setting) current kinematic state.
    - `NetworkRigidbodyBase.ResetInterpolation` to reset the `Rigidbody` or `Rigidbody2d` (depending upon its initialized setting) back to its original interpolation value when initialized.
  - Now includes a `MonoBehaviour.FixedUpdate` implementation that will update the assigned `NetworkTransform` when `NetworkRigidbodyBase.UseRigidBodyForMotion` is true. (#2863)
- Added `RigidbodyContactEventManager` that provides a more optimized way to process collision enter and collision stay events as opposed to the `Monobehaviour` approach. (#2863)
  - Can be used in client-server and distributed authority modes, but is particularly useful in distributed authority.
- Added rigid body motion updates to `NetworkTransform` which allows users to set interolation on rigid bodies. (#2863)
  - Extrapolation is only allowed on authoritative instances, but custom class derived from `NetworkRigidbodyBase` or `NetworkRigidbody` or `NetworkRigidbody2D` automatically switches non-authoritative instances to interpolation if set to extrapolation.
- Added distributed authority mode support to `NetworkAnimator`. (#2863)
- Added session mode selection to `NetworkManager` inspector view. (#2863)
- Added distributed authority permissions feature. (#2863)
- Added distributed authority mode specific `NetworkObject` permissions flags (Distributable, Transferable, and RequestRequired). (#2863)
- Added distributed authority mode specific `NetworkObject.SetOwnershipStatus` method that applies one or more `NetworkObject` instance's ownership flags. If updated when spawned, the ownership permission changes are synchronized with the other connected clients. (#2863)
- Added distributed authority mode specific `NetworkObject.RemoveOwnershipStatus` method that removes one or more `NetworkObject` instance's ownership flags. If updated when spawned, the ownership permission changes are synchronized with the other connected clients. (#2863)
- Added distributed authority mode specific `NetworkObject.HasOwnershipStatus` method that will return (true or false) whether one or more ownership flags is set. (#2863)
- Added distributed authority mode specific `NetworkObject.SetOwnershipLock` method that locks ownership of a `NetworkObject` to prevent ownership from changing until the current owner releases the lock. (#2863)
- Added distributed authority mode specific `NetworkObject.RequestOwnership` method that sends an ownership request to the current owner of a spawned `NetworkObject` instance. (#2863)
- Added distributed authority mode specific `NetworkObject.OnOwnershipRequested` callback handler that is invoked on the owner/authoritative side when a non-owner requests ownership. Depending upon the boolean returned value depends upon whether the request is approved or denied. (#2863)
- Added distributed authority mode specific `NetworkObject.OnOwnershipRequestResponse` callback handler that is invoked when a non-owner's request has been processed. This callback includes a `NetworkObjet.OwnershipRequestResponseStatus` response parameter that describes whether the request was approved or the reason why it was not approved. (#2863)
- Added distributed authority mode specific `NetworkObject.DeferDespawn` method that defers the despawning of `NetworkObject` instances on non-authoritative clients based on the tick offset parameter. (#2863)
- Added distributed authority mode specific `NetworkObject.OnDeferredDespawnComplete` callback handler that can be used to further control when deferring the despawning of a `NetworkObject` on non-authoritative instances. (#2863)
- Added `NetworkClient.SessionModeType` as one way to determine the current session mode of the network session a client is connected to. (#2863)
- Added distributed authority mode specific `NetworkClient.IsSessionOwner` property to determine if the current local client is the current session owner of a distributed authority session. (#2863)
- Added distributed authority mode specific client side spawning capabilities. When running in distributed authority mode, clients can instantiate and spawn `NetworkObject` instances (the local client is authomatically the owner of the spawned object). (#2863)
  - This is useful to better visually synchronize owner authoritative motion models and newly spawned `NetworkObject` instances (i.e. projectiles for example).
- Added distributed authority mode specific client side player spawning capabilities. Clients will automatically spawn their associated player object locally. (#2863)
- Added distributed authority mode specific `NetworkConfig.AutoSpawnPlayerPrefabClientSide` property (default is true) to provide control over the automatic spawning of player prefabs on the local client side. (#2863)
- Added distributed authority mode specific `NetworkManager.OnFetchLocalPlayerPrefabToSpawn` callback that, when assigned, will allow the local client to provide the player prefab to be spawned for the local client. (#2863)
  - This is only invoked if the `NetworkConfig.AutoSpawnPlayerPrefabClientSide` property is set to true.
- Added distributed authority mode specific `NetworkBehaviour.HasAuthority` property that determines if the local client has authority over the associated `NetworkObject` instance (typical use case is within a `NetworkBehaviour` script much like that of `IsServer` or `IsClient`). (#2863)
- Added distributed authority mode specific `NetworkBehaviour.IsSessionOwner` property that determines if the local client is the session owner (typical use case would be to determine if the local client can has scene management authority within a `NetworkBehaviour` script). (#2863)
- Added support for distributed authority mode scene management where the currently assigned session owner can start scene events (i.e. scene loading and scene unloading). (#2863)

### Fixed

- Fixed issue where the host was not invoking `OnClientDisconnectCallback` for its own local client when internally shutting down. (#2822)
- Fixed issue where NetworkTransform could potentially attempt to "unregister" a named message prior to it being registered. (#2807)
- Fixed issue where in-scene placed `NetworkObject`s with complex nested children `NetworkObject`s (more than one child in depth) would not synchronize properly if WorldPositionStays was set to true. (#2796)

### Changed
- Changed client side awareness of other clients is now the same as a server or host. (#2863)
- Changed `NetworkManager.ConnectedClients` can now be accessed by both server and clients. (#2863)
- Changed `NetworkManager.ConnectedClientsList` can now be accessed by both server and clients. (#2863)
- Changed `NetworkTransform` defaults to owner authoritative when connected to a distributed authority session. (#2863)
- Changed `NetworkVariable` defaults to owner write and everyone read permissions when connected to a distributed authority session (even if declared with server read or write permissions).  (#2863)
- Changed `NetworkObject` no longer implements the `MonoBehaviour.Update` method in order to determine whether a `NetworkObject` instance has been migrated to a different scene. Instead, only `NetworkObjects` with the `SceneMigrationSynchronization` property set will be updated internally during the `NetworkUpdateStage.PostLateUpdate` by `NetworkManager`. (#2863)
- Changed `NetworkManager` inspector view layout where properties are now organized by category. (#2863)
- Changed `NetworkTransform` to now use `NetworkTransformMessage` as opposed to named messages for NetworkTransformState updates. (#2810)
- Changed `CustomMessageManager` so it no longer attempts to register or "unregister" a null or empty string and will log an error if this condition occurs. (#2807)
2024-04-02 00:00:00 +00:00

1373 lines
59 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Unity.Collections;
using UnityEngine.SceneManagement;
namespace Unity.Netcode
{
/// <summary>
/// The different types of scene events communicated between a server and client. <br/>
/// Used by <see cref="NetworkSceneManager"/> for <see cref="SceneEventMessage"/> messages.<br/>
/// <em>Note: This is only when <see cref="NetworkConfig.EnableSceneManagement"/> is enabled.</em><br/>
/// See also: <br/>
/// <seealso cref="SceneEvent"/>
/// </summary>
public enum SceneEventType : byte
{
/// <summary>
/// Load a scene<br/>
/// <b>Invocation:</b> Server Side<br/>
/// <b>Message Flow:</b> Server to client<br/>
/// <b>Event Notification:</b> Both server and client are notified a load scene event started
/// </summary>
Load,
/// <summary>
/// Unload a scene<br/>
/// <b>Invocation:</b> Server Side<br/>
/// <b>Message Flow:</b> Server to client<br/>
/// <b>Event Notification:</b> Both server and client are notified an unload scene event started.
/// </summary>
Unload,
/// <summary>
/// Synchronizes current game session state for newly approved clients<br/>
/// <b>Invocation:</b> Server Side<br/>
/// <b>Message Flow:</b> Server to client<br/>
/// <b>Event Notification:</b> Server and Client receives a local notification (<em>server receives the ClientId being synchronized</em>).
/// </summary>
Synchronize,
/// <summary>
/// Game session re-synchronization of NetworkObjects that were destroyed during a <see cref="Synchronize"/> event<br/>
/// <b>Invocation:</b> Server Side<br/>
/// <b>Message Flow:</b> Server to client<br/>
/// <b>Event Notification:</b> Both server and client receive a local notification<br/>
/// </summary>
ReSynchronize,
/// <summary>
/// All clients have finished loading a scene<br/>
/// <b>Invocation:</b> Server Side<br/>
/// <b>Message Flow:</b> Server to Client<br/>
/// <b>Event Notification:</b> Both server and client receive a local notification containing the clients that finished
/// as well as the clients that timed out(<em>if any</em>).
/// </summary>
LoadEventCompleted,
/// <summary>
/// All clients have unloaded a scene<br/>
/// <b>Invocation:</b> Server Side<br/>
/// <b>Message Flow:</b> Server to Client<br/>
/// <b>Event Notification:</b> Both server and client receive a local notification containing the clients that finished
/// as well as the clients that timed out(<em>if any</em>).
/// </summary>
UnloadEventCompleted,
/// <summary>
/// A client has finished loading a scene<br/>
/// <b>Invocation:</b> Client Side<br/>
/// <b>Message Flow:</b> Client to Server<br/>
/// <b>Event Notification:</b> Both server and client receive a local notification.
/// </summary>
LoadComplete,
/// <summary>
/// A client has finished unloading a scene<br/>
/// <b>Invocation:</b> Client Side<br/>
/// <b>Message Flow:</b> Client to Server<br/>
/// <b>Event Notification:</b> Both server and client receive a local notification.
/// </summary>
UnloadComplete,
/// <summary>
/// A client has finished synchronizing from a <see cref="Synchronize"/> event<br/>
/// <b>Invocation:</b> Client Side<br/>
/// <b>Message Flow:</b> Client to Server<br/>
/// <b>Event Notification:</b> Both server and client receive a local notification.
/// </summary>
SynchronizeComplete,
/// <summary>
/// Synchronizes clients when the active scene has changed
/// See: <see cref="NetworkObject.ActiveSceneSynchronization"/>
/// </summary>
ActiveSceneChanged,
/// <summary>
/// Synchronizes clients when one or more NetworkObjects are migrated into a new scene
/// See: <see cref="NetworkObject.SceneMigrationSynchronization"/>
/// </summary>
ObjectSceneChanged,
}
/// <summary>
/// Used by <see cref="NetworkSceneManager"/> for <see cref="SceneEventMessage"/> messages
/// <em>Note: This is only when <see cref="NetworkConfig.EnableSceneManagement"/> is enabled.</em><br/>
/// See also: <seealso cref="SceneEvent"/>
/// </summary>
internal class SceneEventData : IDisposable
{
internal SceneEventType SceneEventType;
internal LoadSceneMode LoadSceneMode;
internal ForceNetworkSerializeByMemcpy<Guid> 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 <see cref="SceneEventType.Synchronize"/> 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<uint, List<NetworkObject>> m_SceneNetworkObjects;
private Dictionary<uint, long> m_SceneNetworkObjectDataOffsets;
/// <summary>
/// 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.
/// </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,
/// the server will compile a list and send back an Event_ReSync message to the client.
/// </summary>
private List<ulong> m_NetworkObjectsToBeRemoved = new List<ulong>();
private bool m_HasInternalBuffer;
internal FastBufferReader InternalBuffer;
private NetworkManager m_NetworkManager;
internal List<ulong> ClientsCompleted;
internal List<ulong> ClientsTimedOut;
internal Queue<uint> ScenesToSynchronize;
internal Queue<uint> SceneHandlesToSynchronize;
internal LoadSceneMode ClientSynchronizationMode;
/// <summary>
/// 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 <see cref="NetworkSceneManager.m_ServerSceneHandleToClientSceneHandle"/> usage to see where
/// entries are being added to or removed from the table
/// </summary>
/// <param name="sceneIndex"></param>
/// <param name="sceneHandle"></param>
internal void AddSceneToSynchronize(uint sceneHash, int sceneHandle)
{
ScenesToSynchronize.Enqueue(sceneHash);
SceneHandlesToSynchronize.Enqueue((uint)sceneHandle);
}
/// <summary>
/// Client Side:
/// Gets the next scene hash to be loaded for approval and/or late joining
/// </summary>
/// <returns></returns>
internal uint GetNextSceneSynchronizationHash()
{
return ScenesToSynchronize.Dequeue();
}
/// <summary>
/// Client Side:
/// Gets the next scene handle to be loaded for approval and/or late joining
/// </summary>
/// <returns></returns>
internal int GetNextSceneSynchronizationHandle()
{
return (int)SceneHandlesToSynchronize.Dequeue();
}
/// <summary>
/// Client Side:
/// Determines if all scenes have been processed during the synchronization process
/// </summary>
/// <returns>true/false</returns>
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;
}
/// <summary>
/// Server Side:
/// Called just before the synchronization process
/// </summary>
internal void InitializeForSynch()
{
if (m_SceneNetworkObjects == null)
{
m_SceneNetworkObjects = new Dictionary<uint, List<NetworkObject>>();
}
else
{
m_SceneNetworkObjects.Clear();
}
if (ScenesToSynchronize == null)
{
ScenesToSynchronize = new Queue<uint>();
}
else
{
ScenesToSynchronize.Clear();
}
if (SceneHandlesToSynchronize == null)
{
SceneHandlesToSynchronize = new Queue<uint>();
}
else
{
SceneHandlesToSynchronize.Clear();
}
ForwardSynchronization = false;
}
/// <summary>
/// Used with SortParentedNetworkObjects to sort the children of the root parent NetworkObject
/// </summary>
/// <param name="first">object to be sorted</param>
/// <param name="second">object to be compared to for sorting the first object</param>
/// <returns></returns>
private int SortChildrenNetworkObjects(NetworkObject first, NetworkObject second)
{
var firstParent = first.GetCachedParent()?.GetComponent<NetworkObject>();
// 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<NetworkObject>();
// 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;
}
/// <summary>
/// Sorts the synchronization order of the NetworkObjects to be serialized
/// by parents before children order
/// </summary>
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<NetworkObject>().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();
foreach (var sobj in m_NetworkManager.SpawnManager.SpawnedObjectsList)
{
if (sobj.Observers.Contains(TargetClientId))
{
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<NetworkObject>(UnityEngine.FindObjectsInactive.Include, UnityEngine.FindObjectsSortMode.InstanceID).Where((c) => c.NetworkManager == m_NetworkManager);
#else
var inSceneNetworkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>(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);
}
}
}
/// <summary>
/// Server Side:
/// Used during the synchronization process to associate NetworkObjects with scenes
/// </summary>
/// <param name="sceneIndex"></param>
/// <param name="networkObject"></param>
internal void AddNetworkObjectForSynch(uint sceneIndex, NetworkObject networkObject)
{
if (!m_SceneNetworkObjects.ContainsKey(sceneIndex))
{
m_SceneNetworkObjects.Add(sceneIndex, new List<NetworkObject>());
}
m_SceneNetworkObjects[sceneIndex].Add(networkObject);
}
/// <summary>
/// Client and Server:
/// Determines if the scene event type was intended for the client ( or server )
/// </summary>
/// <returns>true (client should handle this message) false (server should handle this message)</returns>
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;
}
/// <summary>
/// Server Side:
/// Sorts the NetworkObjects to assure proper instantiation order of operations for
/// registered INetworkPrefabInstanceHandler implementations
/// </summary>
/// <param name="first"></param>
/// <param name="second"></param>
/// <returns></returns>
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;
/// <summary>
/// Client and Server Side:
/// Serializes data based on the SceneEvent type (<see cref="SceneEventType"/>)
/// </summary>
/// <param name="writer"><see cref="FastBufferWriter"/> to write the scene event data</param>
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);
}
/// <summary>
/// Server Side:
/// Called at the end of a <see cref="SceneEventType.Load"/> event once the scene is loaded and scene placed NetworkObjects
/// have been locally spawned
/// </summary>
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);
}
}
/// <summary>
/// Server Side:
/// Called at the end of a <see cref="SceneEventType.Load"/> 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
/// </summary>
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;
foreach (var keyValuePairByGlobalObjectIdHash in m_NetworkManager.SceneManager.ScenePlacedObjects)
{
foreach (var keyValuePairBySceneHandle in keyValuePairByGlobalObjectIdHash.Value)
{
if (keyValuePairBySceneHandle.Value.Observers.Contains(TargetClientId))
{
// 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);
}
/// <summary>
/// Client and Server Side:
/// Deserialize data based on the SceneEvent type.
/// </summary>
/// <param name="reader"></param>
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;
/// <summary>
/// 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.
/// </summary>
/// <param name="reader"></param>
internal void CopySceneSynchronizationData(FastBufferReader reader)
{
m_NetworkObjectsSync.Clear();
reader.ReadValueSafe(out uint[] scenesToSynchronize);
reader.ReadValueSafe(out uint[] sceneHandlesToSynchronize);
ScenesToSynchronize = new Queue<uint>(scenesToSynchronize);
SceneHandlesToSynchronize = new Queue<uint>(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());
}
}
}
/// <summary>
/// Client Side:
/// This needs to occur at the end of a <see cref="SceneEventType.Load"/> event when the scene has finished loading
/// Maximum number of objects that could theoretically be synchronized is 65536
/// </summary>
internal void DeserializeScenePlacedObjects()
{
try
{
// is not packed!
InternalBuffer.ReadValueSafe(out ushort newObjectsCount);
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);
}
NetworkObject.AddSceneObject(sceneObject, InternalBuffer, m_NetworkManager);
}
// Now deserialize the despawned in-scene placed NetworkObjects list (if any)
DeserializeDespawnedInScenePlacedNetworkObjects();
}
finally
{
InternalBuffer.Dispose();
m_HasInternalBuffer = false;
}
}
/// <summary>
/// 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.
/// </summary>
/// <param name="reader"></param>
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<NetworkObject>(UnityEngine.FindObjectsSortMode.InstanceID);
#else
var networkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>();
#endif
var networkObjectIdToNetworkObject = new Dictionary<ulong, NetworkObject>();
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);
}
}
}
}
}
/// <summary>
/// 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.
/// </summary>
/// <param name="writer"></param>
internal void WriteClientReSynchronizationData(FastBufferWriter writer)
{
//Write how many objects need to be removed
writer.WriteValueSafe(m_NetworkObjectsToBeRemoved.ToArray());
}
/// <summary>
/// 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.
/// </summary>
/// <returns></returns>
internal bool ClientNeedsReSynchronization()
{
return (m_NetworkObjectsToBeRemoved.Count > 0);
}
/// <summary>
/// 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.
/// </summary>
/// <param name="reader"></param>
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);
}
}
}
/// <summary>
/// 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.
/// </summary>
/// <param name="writer"></param>
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);
}
}
/// <summary>
/// For synchronizing any despawned in-scene placed NetworkObjects that were
/// despawned by the server during synchronization or scene loading
/// </summary>
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<int, Dictionary<uint, NetworkObject>>();
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<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];
// Find all active and non-active in-scene placed NetworkObjects
#if UNITY_2023_1_OR_NEWER
var inSceneNetworkObjects = UnityEngine.Object.FindObjectsByType<NetworkObject>(UnityEngine.FindObjectsInactive.Include, UnityEngine.FindObjectsSortMode.InstanceID).Where((c) =>
c.GetSceneOriginHandle() == localSceneHandle && (c.IsSceneObject != false)).ToList();
#else
var inSceneNetworkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>(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<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!");
}
}
}
/// <summary>
/// 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.
/// </summary>
/// <param name="networkManager"></param>
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());
}
// 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;
}
}
/// <summary>
/// Writes the all clients loaded or unloaded completed and timed out lists
/// </summary>
/// <param name="writer"></param>
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);
}
}
/// <summary>
/// Reads the all clients loaded or unloaded completed and timed out lists
/// </summary>
/// <param name="reader"></param>
internal void ReadSceneEventProgressDone(FastBufferReader reader)
{
reader.ReadValueSafe(out ushort completedCount);
ClientsCompleted = new List<ulong>();
for (int i = 0; i < completedCount; i++)
{
reader.ReadValueSafe(out ulong clientId);
ClientsCompleted.Add(clientId);
}
reader.ReadValueSafe(out ushort timedOutCount);
ClientsTimedOut = new List<ulong>();
for (int i = 0; i < timedOutCount; i++)
{
reader.ReadValueSafe(out ulong clientId);
ClientsTimedOut.Add(clientId);
}
}
/// <summary>
/// Serialize scene handles and associated NetworkObjects that were migrated
/// into a new scene.
/// </summary>
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);
}
}
}
/// <summary>
/// Deserialize scene handles and associated NetworkObjects that need to
/// be migrated into a new scene.
/// </summary>
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<ulong, List<NetworkObject>>());
}
if (!sceneManager.ObjectsMigratedIntoNewScene[sceneHandle].ContainsKey(ownerID))
{
sceneManager.ObjectsMigratedIntoNewScene[sceneHandle].Add(ownerID, new List<NetworkObject>());
}
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);
}
}
}
/// <summary>
/// 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.
/// </summary>
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<int, List<ulong>>(),
};
reader.ReadValueSafe(out numberOfScenes);
for (int i = 0; i < numberOfScenes; i++)
{
reader.ReadValueSafe(out sceneHandle);
deferredObjectsMovedEvent.ObjectsMigratedTable.Add(sceneHandle, new List<ulong>());
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<ulong, List<NetworkObject>>());
}
if (!sceneManager.ObjectsMigratedIntoNewScene[keyEntry.Key].ContainsKey(objectsMovedEvent.OwnerId))
{
sceneManager.ObjectsMigratedIntoNewScene[keyEntry.Key].Add(objectsMovedEvent.OwnerId, new List<NetworkObject>());
}
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();
}
/// <summary>
/// Used to release the pooled network buffer
/// </summary>
public void Dispose()
{
if (m_HasInternalBuffer)
{
InternalBuffer.Dispose();
m_HasInternalBuffer = false;
}
}
/// <summary>
/// Constructor for SceneEventData
/// </summary>
internal SceneEventData(NetworkManager networkManager)
{
m_NetworkManager = networkManager;
SceneEventId = XXHash.Hash32(Guid.NewGuid().ToString());
}
}
}