From 3ff9326919b166fbf1341802021c7065e8fc8565 Mon Sep 17 00:00:00 2001 From: Extrys Date: Sun, 27 Apr 2025 02:29:39 +0200 Subject: [PATCH 1/8] feat: Network Object Instantiation Payload --- .../Connection/NetworkConnectionManager.cs | 8 ++- .../Runtime/Core/NetworkObject.cs | 60 ++++++++++++++++++- ...NetworkInstantiationPayloadSynchronizer.cs | 20 +++++++ ...rkInstantiationPayloadSynchronizer.cs.meta | 2 + .../Runtime/Spawning/NetworkPrefabHandler.cs | 27 ++++++++- .../Runtime/Spawning/NetworkSpawnManager.cs | 50 ++++++++-------- .../Prefabs/NetworkPrefabHandlerTests.cs | 9 ++- 7 files changed, 143 insertions(+), 33 deletions(-) create mode 100644 com.unity.netcode.gameobjects/Runtime/Spawning/INetworkInstantiationPayloadSynchronizer.cs create mode 100644 com.unity.netcode.gameobjects/Runtime/Spawning/INetworkInstantiationPayloadSynchronizer.cs.meta diff --git a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs index 59a187a5fc..59e2164dd3 100644 --- a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs @@ -797,8 +797,9 @@ internal void HandleConnectionApproval(ulong ownerClientId, NetworkManager.Conne // Server-side spawning (only if there is a prefab hash or player prefab provided) if (!NetworkManager.DistributedAuthorityMode && response.CreatePlayerObject && (response.PlayerPrefabHash.HasValue || NetworkManager.NetworkConfig.PlayerPrefab != null)) { - var playerObject = response.PlayerPrefabHash.HasValue ? NetworkManager.SpawnManager.GetNetworkObjectToSpawn(response.PlayerPrefabHash.Value, ownerClientId, response.Position ?? null, response.Rotation ?? null) - : NetworkManager.SpawnManager.GetNetworkObjectToSpawn(NetworkManager.NetworkConfig.PlayerPrefab.GetComponent().GlobalObjectIdHash, ownerClientId, response.Position ?? null, response.Rotation ?? null); + var instantiationPayloadWriter = new BufferSerializer(new BufferSerializerWriter(new FastBufferWriter(0,Allocator.Temp))); + var playerObject = response.PlayerPrefabHash.HasValue ? NetworkManager.SpawnManager.GetNetworkObjectToSpawn(response.PlayerPrefabHash.Value, ownerClientId, ref instantiationPayloadWriter, response.Position ?? null, response.Rotation ?? null) + : NetworkManager.SpawnManager.GetNetworkObjectToSpawn(NetworkManager.NetworkConfig.PlayerPrefab.GetComponent().GlobalObjectIdHash, ownerClientId, ref instantiationPayloadWriter, response.Position ?? null, response.Rotation ?? null); // Spawn the player NetworkObject locally NetworkManager.SpawnManager.SpawnNetworkObjectLocally( @@ -950,7 +951,8 @@ internal void CreateAndSpawnPlayer(ulong ownerId) if (playerPrefab != null) { var globalObjectIdHash = playerPrefab.GetComponent().GlobalObjectIdHash; - var networkObject = NetworkManager.SpawnManager.GetNetworkObjectToSpawn(globalObjectIdHash, ownerId, playerPrefab.transform.position, playerPrefab.transform.rotation); + var instantiationPayloadWriter = new BufferSerializer(new BufferSerializerWriter(new FastBufferWriter(0, Allocator.Temp))); + var networkObject = NetworkManager.SpawnManager.GetNetworkObjectToSpawn(globalObjectIdHash, ownerId, ref instantiationPayloadWriter, playerPrefab.transform.position, playerPrefab.transform.rotation); networkObject.IsSceneObject = false; networkObject.SpawnAsPlayerObject(ownerId, networkObject.DestroyWithScene); } diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 64c8e96076..26d03b87ee 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; +using Unity.Collections; using Unity.Netcode.Components; #if UNITY_EDITOR using UnityEditor; @@ -45,6 +46,13 @@ public sealed class NetworkObject : MonoBehaviour [SerializeField] internal uint InScenePlacedSourceGlobalObjectIdHash; + /// + /// Metadata sent during the instantiation process. + /// Retrieved in INetworkCustomSpawnDataSynchronizer before instantiation, + /// and available to INetworkPrefabInstanceHandler.Instantiate() for custom handling by user code. + /// + internal FastBufferReader InstantiationPayload; + /// /// Gets the Prefab Hash Id of this object if the object is registerd as a prefab otherwise it returns 0 /// @@ -2812,6 +2820,7 @@ internal struct SceneObject public ulong NetworkObjectId; public ulong OwnerClientId; public ushort OwnershipFlags; + public FastBufferReader InstantiationPayload; public bool IsPlayerObject { @@ -2882,6 +2891,12 @@ public bool SpawnWithObservers set => ByteUtility.SetBit(ref m_BitField, 10, value); } + public bool HasInstantiationPayload + { + get => ByteUtility.GetBit(m_BitField, 11); + set => ByteUtility.SetBit(ref m_BitField, 11, value); + } + // When handling the initial synchronization of NetworkObjects, // this will be populated with the known observers. public ulong[] Observers; @@ -2948,12 +2963,26 @@ public void Serialize(FastBufferWriter writer) var writeSize = 0; writeSize += HasTransform ? FastBufferWriter.GetWriteSize() : 0; writeSize += FastBufferWriter.GetWriteSize(); + if (HasInstantiationPayload) + { + writeSize += FastBufferWriter.GetWriteSize(); + writeSize += InstantiationPayload.Length; + } if (!writer.TryBeginWrite(writeSize)) { throw new OverflowException("Could not serialize SceneObject: Out of buffer space."); } + if (HasInstantiationPayload) + { + writer.WriteValueSafe(InstantiationPayload.Length); + unsafe + { + writer.WriteBytes(InstantiationPayload.GetUnsafePtr(), InstantiationPayload.Length); + } + } + if (HasTransform) { writer.WriteValue(Transform); @@ -3014,12 +3043,34 @@ public void Deserialize(FastBufferReader reader) readSize += HasTransform ? FastBufferWriter.GetWriteSize() : 0; readSize += FastBufferWriter.GetWriteSize(); + int preInstanceDataSize = 0; + if (HasInstantiationPayload) + { + if (!reader.TryBeginRead(FastBufferWriter.GetWriteSize())) + { + throw new OverflowException($"Could not deserialize SceneObject: Reading past the end of the buffer ({nameof(InstantiationPayload)} size)"); + } + + reader.ReadValueSafe(out preInstanceDataSize); + readSize += FastBufferWriter.GetWriteSize(); + readSize += preInstanceDataSize; + } + // Try to begin reading the remaining bytes if (!reader.TryBeginRead(readSize)) { throw new OverflowException("Could not deserialize SceneObject: Reading past the end of the buffer"); } + if (HasInstantiationPayload) + { + unsafe + { + InstantiationPayload = new FastBufferReader(reader.GetUnsafePtrAtCurrentPosition(), Allocator.Persistent, preInstanceDataSize); + reader.Seek(reader.Position + preInstanceDataSize); + } + } + if (HasTransform) { reader.ReadValue(out Transform); @@ -3148,7 +3199,9 @@ internal SceneObject GetMessageSceneObject(ulong targetClientId = NetworkManager NetworkSceneHandle = NetworkSceneHandle, Hash = CheckForGlobalObjectIdHashOverride(), OwnerObject = this, - TargetClientId = targetClientId + TargetClientId = targetClientId, + HasInstantiationPayload = InstantiationPayload.IsInitialized, + InstantiationPayload = InstantiationPayload }; // Handle Parenting @@ -3243,6 +3296,11 @@ internal static NetworkObject AddSceneObject(in SceneObject sceneObject, FastBuf // in order to be able to determine which NetworkVariables the client will be allowed to read. networkObject.OwnerClientId = sceneObject.OwnerClientId; + // Even though the Instantiation Payload is typically consumed during the spawn message handling phase, + // we still assign it here to preserve the original spawn metadata for potential inspection, diagnostics, + // or in case future systems want to access it directly without relying on synchronization messages. + networkObject.InstantiationPayload = sceneObject.InstantiationPayload; + // Special Case: Invoke NetworkBehaviour.OnPreSpawn methods here before SynchronizeNetworkBehaviours networkObject.InvokeBehaviourNetworkPreSpawn(); diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/INetworkInstantiationPayloadSynchronizer.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/INetworkInstantiationPayloadSynchronizer.cs new file mode 100644 index 0000000000..b83752aca1 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/INetworkInstantiationPayloadSynchronizer.cs @@ -0,0 +1,20 @@ +namespace Unity.Netcode +{ + /// + /// Interface for synchronizing custom instantiation payloads during NetworkObject spawning. + /// Used alongside to extend instantiation behavior. + /// + public interface INetworkInstantiationPayloadSynchronizer + { + /// + /// Provides a method for synchronizing instantiation payload data during the spawn process. + /// Extends to allow passing additional data prior to instantiation + /// to help identify or configure the local object instance that should be linked to the spawned NetworkObject. + /// + /// This method is invoked immediately before is called, + /// allowing you to cache or prepare information needed during instantiation. + /// + /// The buffer serializer used to read or write custom instantiation data. + void OnSynchronize(ref BufferSerializer serializer) where T : IReaderWriter; + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/INetworkInstantiationPayloadSynchronizer.cs.meta b/com.unity.netcode.gameobjects/Runtime/Spawning/INetworkInstantiationPayloadSynchronizer.cs.meta new file mode 100644 index 0000000000..3895ba444c --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/INetworkInstantiationPayloadSynchronizer.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 9bf5119a47f8d3247aaa4cd13c1ee96b \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs index fe0dd270e9..7841ae5f9f 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs @@ -252,12 +252,35 @@ internal uint GetSourceGlobalObjectIdHash(uint networkPrefabHash) /// /// /// - internal NetworkObject HandleNetworkPrefabSpawn(uint networkPrefabAssetHash, ulong ownerClientId, Vector3 position, Quaternion rotation) + internal NetworkObject HandleNetworkPrefabSpawn(uint networkPrefabAssetHash, ulong ownerClientId, ref BufferSerializer preInstanceDataSerializer, Vector3 position, Quaternion rotation) where T : IReaderWriter { if (m_PrefabAssetToPrefabHandler.TryGetValue(networkPrefabAssetHash, out var prefabInstanceHandler)) { - var networkObjectInstance = prefabInstanceHandler.Instantiate(ownerClientId, position, rotation); + if (prefabInstanceHandler is INetworkInstantiationPayloadSynchronizer synchronizer) + { + synchronizer.OnSynchronize(ref preInstanceDataSerializer); + } + var networkObjectInstance = prefabInstanceHandler.Instantiate(ownerClientId, position, rotation); + + if (networkObjectInstance != null) + { + if (preInstanceDataSerializer.IsReader) + { + networkObjectInstance.InstantiationPayload = preInstanceDataSerializer.GetFastBufferReader(); + } + else + { + var writer = preInstanceDataSerializer.GetFastBufferWriter(); + if (writer.Length > 0) + { + unsafe + { + networkObjectInstance.InstantiationPayload = new FastBufferReader(writer.GetUnsafePtr(), Collections.Allocator.Persistent, writer.Length); + } + } + } + } //Now we must make sure this alternate PrefabAsset spawned in place of the prefab asset with the networkPrefabAssetHash (GlobalObjectIdHash) //is registered and linked to the networkPrefabAssetHash so during the HandleNetworkPrefabDestroy process we can identify the alternate prefab asset. if (networkObjectInstance != null && !m_PrefabInstanceToPrefabAsset.ContainsKey(networkObjectInstance.GlobalObjectIdHash)) diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 19227c82ba..9bf6759bb9 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -678,12 +678,12 @@ internal enum InstantiateAndSpawnErrorTypes internal static readonly Dictionary InstantiateAndSpawnErrors = new Dictionary( new KeyValuePair[]{ - new KeyValuePair(InstantiateAndSpawnErrorTypes.NetworkPrefabNull, $"The {nameof(NetworkObject)} prefab parameter was null!"), - new KeyValuePair(InstantiateAndSpawnErrorTypes.NotAuthority, $"Only the server has authority to {nameof(InstantiateAndSpawn)}!"), - new KeyValuePair(InstantiateAndSpawnErrorTypes.InvokedWhenShuttingDown, $"Invoking {nameof(InstantiateAndSpawn)} while shutting down! Calls to {nameof(InstantiateAndSpawn)} will be ignored."), - new KeyValuePair(InstantiateAndSpawnErrorTypes.NotRegisteredNetworkPrefab, $"The {nameof(NetworkObject)} parameter is not a registered network prefab. Did you forget to register it or are you trying to instantiate and spawn an instance of a network prefab?"), - new KeyValuePair(InstantiateAndSpawnErrorTypes.NetworkManagerNull, $"The {nameof(NetworkManager)} parameter was null!"), - new KeyValuePair(InstantiateAndSpawnErrorTypes.NoActiveSession, "You can only invoke this method when you are connected to an existing/in-progress network session!") + new KeyValuePair(InstantiateAndSpawnErrorTypes.NetworkPrefabNull, $"The {nameof(NetworkObject)} prefab parameter was null!"), + new KeyValuePair(InstantiateAndSpawnErrorTypes.NotAuthority, $"Only the server has authority to {nameof(InstantiateAndSpawn)}!"), + new KeyValuePair(InstantiateAndSpawnErrorTypes.InvokedWhenShuttingDown, $"Invoking {nameof(InstantiateAndSpawn)} while shutting down! Calls to {nameof(InstantiateAndSpawn)} will be ignored."), + new KeyValuePair(InstantiateAndSpawnErrorTypes.NotRegisteredNetworkPrefab, $"The {nameof(NetworkObject)} parameter is not a registered network prefab. Did you forget to register it or are you trying to instantiate and spawn an instance of a network prefab?"), + new KeyValuePair(InstantiateAndSpawnErrorTypes.NetworkManagerNull, $"The {nameof(NetworkManager)} parameter was null!"), + new KeyValuePair(InstantiateAndSpawnErrorTypes.NoActiveSession, "You can only invoke this method when you are connected to an existing/in-progress network session!") }); /// @@ -748,7 +748,8 @@ internal NetworkObject InstantiateAndSpawnNoParameterChecks(NetworkObject networ // - Distributed authority mode always spawns the override if one exists. if (forceOverride || NetworkManager.IsClient || NetworkManager.DistributedAuthorityMode || NetworkManager.PrefabHandler.ContainsHandler(networkPrefab.GlobalObjectIdHash)) { - networkObject = GetNetworkObjectToSpawn(networkPrefab.GlobalObjectIdHash, ownerClientId, position, rotation); + var intantiationPayloadWriter = new BufferSerializer(new BufferSerializerWriter(new FastBufferWriter(20, Collections.Allocator.Temp))); + networkObject = GetNetworkObjectToSpawn(networkPrefab.GlobalObjectIdHash, ownerClientId, ref intantiationPayloadWriter, position, rotation); } else // Under this case, server instantiate the prefab passed in. { @@ -779,14 +780,14 @@ internal NetworkObject InstantiateAndSpawnNoParameterChecks(NetworkObject networ /// Gets the right NetworkObject prefab instance to spawn. If a handler is registered or there is an override assigned to the /// passed in globalObjectIdHash value, then that is what will be instantiated, spawned, and returned. /// - internal NetworkObject GetNetworkObjectToSpawn(uint globalObjectIdHash, ulong ownerId, Vector3? position, Quaternion? rotation, bool isScenePlaced = false) + internal NetworkObject GetNetworkObjectToSpawn(uint globalObjectIdHash, ulong ownerId, ref BufferSerializer instantiationPayloadSerializer, Vector3? position, Quaternion? rotation, bool isScenePlaced = false) where T : IReaderWriter { NetworkObject networkObject = null; // If the prefab hash has a registered INetworkPrefabInstanceHandler derived class if (NetworkManager.PrefabHandler.ContainsHandler(globalObjectIdHash)) { // Let the handler spawn the NetworkObject - networkObject = NetworkManager.PrefabHandler.HandleNetworkPrefabSpawn(globalObjectIdHash, ownerId, position ?? default, rotation ?? default); + networkObject = NetworkManager.PrefabHandler.HandleNetworkPrefabSpawn(globalObjectIdHash, ownerId, ref instantiationPayloadSerializer, position ?? default, rotation ?? default); networkObject.NetworkManagerOwner = NetworkManager; } else @@ -807,22 +808,22 @@ internal NetworkObject GetNetworkObjectToSpawn(uint globalObjectIdHash, ulong ow break; case NetworkPrefabOverride.Hash: case NetworkPrefabOverride.Prefab: + { + // When scene management is disabled and this is an in-scene placed NetworkObject, we want to always use the + // SourcePrefabToOverride and not any possible prefab override as a user might want to spawn overrides dynamically + // but might want to use the same source network prefab as an in-scene placed NetworkObject. + // (When scene management is enabled, clients don't delete their in-scene placed NetworkObjects prior to dynamically + // spawning them so the original prefab placed is preserved and this is not needed) + if (inScenePlacedWithNoSceneManagement) { - // When scene management is disabled and this is an in-scene placed NetworkObject, we want to always use the - // SourcePrefabToOverride and not any possible prefab override as a user might want to spawn overrides dynamically - // but might want to use the same source network prefab as an in-scene placed NetworkObject. - // (When scene management is enabled, clients don't delete their in-scene placed NetworkObjects prior to dynamically - // spawning them so the original prefab placed is preserved and this is not needed) - if (inScenePlacedWithNoSceneManagement) - { - networkPrefabReference = networkPrefab.SourcePrefabToOverride ? networkPrefab.SourcePrefabToOverride : networkPrefab.Prefab; - } - else - { - networkPrefabReference = NetworkManager.NetworkConfig.Prefabs.NetworkPrefabOverrideLinks[globalObjectIdHash].OverridingTargetPrefab; - } - break; + networkPrefabReference = networkPrefab.SourcePrefabToOverride ? networkPrefab.SourcePrefabToOverride : networkPrefab.Prefab; } + else + { + networkPrefabReference = NetworkManager.NetworkConfig.Prefabs.NetworkPrefabOverrideLinks[globalObjectIdHash].OverridingTargetPrefab; + } + break; + } } } @@ -888,7 +889,8 @@ internal NetworkObject CreateLocalNetworkObject(NetworkObject.SceneObject sceneO // If scene management is disabled or the NetworkObject was dynamically spawned if (!NetworkManager.NetworkConfig.EnableSceneManagement || !sceneObject.IsSceneObject) { - networkObject = GetNetworkObjectToSpawn(sceneObject.Hash, sceneObject.OwnerClientId, position, rotation, sceneObject.IsSceneObject); + var instantiationPayloadReader = new BufferSerializer(new BufferSerializerReader(sceneObject.InstantiationPayload)); + networkObject = GetNetworkObjectToSpawn(sceneObject.Hash, sceneObject.OwnerClientId, ref instantiationPayloadReader, position, rotation, sceneObject.IsSceneObject); } else // Get the in-scene placed NetworkObject { diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerTests.cs index c66c843abf..40b48e7a35 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerTests.cs @@ -110,7 +110,8 @@ public void NetworkPrefabHandlerClass([Values] bool distributedAuthority) //Test result of registering via GameObject reference Assert.True(gameObjectRegistered); - var spawnedObject = networkPrefabHandler.HandleNetworkPrefabSpawn(baseObject.GlobalObjectIdHash, 0, prefabPosition, prefabRotation); + var instantiationPayloadWriter = new BufferSerializer(new BufferSerializerWriter(new FastBufferWriter(0, Collections.Allocator.Temp))); + var spawnedObject = networkPrefabHandler.HandleNetworkPrefabSpawn(baseObject.GlobalObjectIdHash, 0, ref instantiationPayloadWriter, prefabPosition, prefabRotation); //Test that something was instantiated Assert.NotNull(spawnedObject); @@ -135,7 +136,8 @@ public void NetworkPrefabHandlerClass([Values] bool distributedAuthority) prefabPosition = new Vector3(2.0f, 1.0f, 5.0f); prefabRotation = new Quaternion(4.0f, 1.5f, 5.4f, 5.1f); - spawnedObject = networkPrefabHandler.HandleNetworkPrefabSpawn(baseObject.GlobalObjectIdHash, 0, prefabPosition, prefabRotation); + instantiationPayloadWriter = new BufferSerializer(new BufferSerializerWriter(new FastBufferWriter(0, Collections.Allocator.Temp))); + spawnedObject = networkPrefabHandler.HandleNetworkPrefabSpawn(baseObject.GlobalObjectIdHash, 0, ref instantiationPayloadWriter, prefabPosition, prefabRotation); //Test that something was instantiated Assert.NotNull(spawnedObject); @@ -160,7 +162,8 @@ public void NetworkPrefabHandlerClass([Values] bool distributedAuthority) prefabPosition = new Vector3(6.0f, 4.0f, 1.0f); prefabRotation = new Quaternion(3f, 2f, 4f, 1f); - spawnedObject = networkPrefabHandler.HandleNetworkPrefabSpawn(baseObject.GlobalObjectIdHash, 0, prefabPosition, prefabRotation); + instantiationPayloadWriter = new BufferSerializer(new BufferSerializerWriter(new FastBufferWriter(0, Collections.Allocator.Temp))); + spawnedObject = networkPrefabHandler.HandleNetworkPrefabSpawn(baseObject.GlobalObjectIdHash, 0, ref instantiationPayloadWriter, prefabPosition, prefabRotation); //Test that something was instantiated Assert.NotNull(spawnedObject); From f0d49aaad4d2f23828b1c732f73c6e14704789bb Mon Sep 17 00:00:00 2001 From: Extrys Date: Sun, 27 Apr 2025 03:23:07 +0200 Subject: [PATCH 2/8] insight on INetworkInstantiationPayloadSynchronizer in the INetworkPrefabInstanceHandler.Instantiate() documentation --- .../Runtime/Spawning/NetworkPrefabHandler.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs index 7841ae5f9f..ed5e244a3e 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs @@ -20,6 +20,9 @@ public interface INetworkPrefabInstanceHandler /// /// Note on Pooling: If you are using a NetworkObject pool, don't forget to make the NetworkObject active /// via the method. + /// + /// If you need to pass custom data at instantiation time (e.g., selecting a variant, setting initialization parameters, or choosing a pre-instantiated object), + /// implement alongside this interface to receive the data before instantiation. /// /// the owner for the to be instantiated /// the initial/default position for the to be instantiated From 3f34e83231b48ee1b75236552b652ca2d155dda8 Mon Sep 17 00:00:00 2001 From: Extrys Date: Sun, 27 Apr 2025 12:49:02 +0200 Subject: [PATCH 3/8] cleaning diff --- .../Runtime/Spawning/NetworkSpawnManager.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 9bf6759bb9..876eb5fe2f 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -678,12 +678,12 @@ internal enum InstantiateAndSpawnErrorTypes internal static readonly Dictionary InstantiateAndSpawnErrors = new Dictionary( new KeyValuePair[]{ - new KeyValuePair(InstantiateAndSpawnErrorTypes.NetworkPrefabNull, $"The {nameof(NetworkObject)} prefab parameter was null!"), - new KeyValuePair(InstantiateAndSpawnErrorTypes.NotAuthority, $"Only the server has authority to {nameof(InstantiateAndSpawn)}!"), - new KeyValuePair(InstantiateAndSpawnErrorTypes.InvokedWhenShuttingDown, $"Invoking {nameof(InstantiateAndSpawn)} while shutting down! Calls to {nameof(InstantiateAndSpawn)} will be ignored."), - new KeyValuePair(InstantiateAndSpawnErrorTypes.NotRegisteredNetworkPrefab, $"The {nameof(NetworkObject)} parameter is not a registered network prefab. Did you forget to register it or are you trying to instantiate and spawn an instance of a network prefab?"), - new KeyValuePair(InstantiateAndSpawnErrorTypes.NetworkManagerNull, $"The {nameof(NetworkManager)} parameter was null!"), - new KeyValuePair(InstantiateAndSpawnErrorTypes.NoActiveSession, "You can only invoke this method when you are connected to an existing/in-progress network session!") + new KeyValuePair(InstantiateAndSpawnErrorTypes.NetworkPrefabNull, $"The {nameof(NetworkObject)} prefab parameter was null!"), + new KeyValuePair(InstantiateAndSpawnErrorTypes.NotAuthority, $"Only the server has authority to {nameof(InstantiateAndSpawn)}!"), + new KeyValuePair(InstantiateAndSpawnErrorTypes.InvokedWhenShuttingDown, $"Invoking {nameof(InstantiateAndSpawn)} while shutting down! Calls to {nameof(InstantiateAndSpawn)} will be ignored."), + new KeyValuePair(InstantiateAndSpawnErrorTypes.NotRegisteredNetworkPrefab, $"The {nameof(NetworkObject)} parameter is not a registered network prefab. Did you forget to register it or are you trying to instantiate and spawn an instance of a network prefab?"), + new KeyValuePair(InstantiateAndSpawnErrorTypes.NetworkManagerNull, $"The {nameof(NetworkManager)} parameter was null!"), + new KeyValuePair(InstantiateAndSpawnErrorTypes.NoActiveSession, "You can only invoke this method when you are connected to an existing/in-progress network session!") }); /// From 47680f554abd8f600d61860890f34268c8427992 Mon Sep 17 00:00:00 2001 From: Extrys Date: Sun, 27 Apr 2025 21:23:17 +0200 Subject: [PATCH 4/8] Some fixes for object instantiated in the server without handler --- .../Connection/NetworkConnectionManager.cs | 4 +- .../Runtime/Core/NetworkObject.cs | 2 + .../Runtime/Spawning/NetworkPrefabHandler.cs | 55 +++++++++++-------- .../Runtime/Spawning/NetworkSpawnManager.cs | 2 +- .../Prefabs/NetworkPrefabHandlerTests.cs | 6 +- 5 files changed, 41 insertions(+), 28 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs index 59e2164dd3..53c59084ca 100644 --- a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs @@ -797,7 +797,7 @@ internal void HandleConnectionApproval(ulong ownerClientId, NetworkManager.Conne // Server-side spawning (only if there is a prefab hash or player prefab provided) if (!NetworkManager.DistributedAuthorityMode && response.CreatePlayerObject && (response.PlayerPrefabHash.HasValue || NetworkManager.NetworkConfig.PlayerPrefab != null)) { - var instantiationPayloadWriter = new BufferSerializer(new BufferSerializerWriter(new FastBufferWriter(0,Allocator.Temp))); + var instantiationPayloadWriter = new BufferSerializer(new BufferSerializerWriter(new FastBufferWriter(16,Allocator.Temp, int.MaxValue))); var playerObject = response.PlayerPrefabHash.HasValue ? NetworkManager.SpawnManager.GetNetworkObjectToSpawn(response.PlayerPrefabHash.Value, ownerClientId, ref instantiationPayloadWriter, response.Position ?? null, response.Rotation ?? null) : NetworkManager.SpawnManager.GetNetworkObjectToSpawn(NetworkManager.NetworkConfig.PlayerPrefab.GetComponent().GlobalObjectIdHash, ownerClientId, ref instantiationPayloadWriter, response.Position ?? null, response.Rotation ?? null); @@ -951,7 +951,7 @@ internal void CreateAndSpawnPlayer(ulong ownerId) if (playerPrefab != null) { var globalObjectIdHash = playerPrefab.GetComponent().GlobalObjectIdHash; - var instantiationPayloadWriter = new BufferSerializer(new BufferSerializerWriter(new FastBufferWriter(0, Allocator.Temp))); + var instantiationPayloadWriter = new BufferSerializer(new BufferSerializerWriter(new FastBufferWriter(16, Allocator.Temp, int.MaxValue))); var networkObject = NetworkManager.SpawnManager.GetNetworkObjectToSpawn(globalObjectIdHash, ownerId, ref instantiationPayloadWriter, playerPrefab.transform.position, playerPrefab.transform.rotation); networkObject.IsSceneObject = false; networkObject.SpawnAsPlayerObject(ownerId, networkObject.DestroyWithScene); diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 26d03b87ee..dd811e98e1 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -1819,6 +1819,8 @@ internal void SpawnInternal(bool destroyWithScene, ulong ownerClientId, bool pla } } + NetworkManager.PrefabHandler.InjectInstantiationPayload(this); + NetworkManager.SpawnManager.SpawnNetworkObjectLocally(this, NetworkManager.SpawnManager.GetNetworkObjectId(), IsSceneObject.HasValue && IsSceneObject.Value, playerObject, ownerClientId, destroyWithScene); if ((NetworkManager.DistributedAuthorityMode && NetworkManager.DAHost) || (!NetworkManager.DistributedAuthorityMode && NetworkManager.IsServer)) diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs index ed5e244a3e..5b14989b1b 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs @@ -226,6 +226,30 @@ public bool RemoveHandler(uint globalObjectIdHash) /// true or false internal bool ContainsHandler(uint networkPrefabHash) => m_PrefabAssetToPrefabHandler.ContainsKey(networkPrefabHash) || m_PrefabInstanceToPrefabAsset.ContainsKey(networkPrefabHash); + /// + /// Injects instantiation payload on the networkObject + /// + /// Injection target + internal void InjectInstantiationPayload(NetworkObject networkObject) + { + if (m_PrefabAssetToPrefabHandler.TryGetValue(networkObject.GlobalObjectIdHash, out var prefabInstanceHandler)) + { + if (prefabInstanceHandler is INetworkInstantiationPayloadSynchronizer synchronizer) + { + var fastBufferWriter = new FastBufferWriter(16, Collections.Allocator.Temp, int.MaxValue); + var instantiationPayloadBufferWriter = new BufferSerializer(new BufferSerializerWriter(fastBufferWriter)); + synchronizer.OnSynchronize(ref instantiationPayloadBufferWriter); + if (fastBufferWriter.Length > 0) + { + unsafe + { + networkObject.InstantiationPayload = new FastBufferReader(fastBufferWriter.GetUnsafePtr(), Collections.Allocator.Persistent, fastBufferWriter.Length); + } + } + } + } + } + /// /// Returns the source NetworkPrefab's /// @@ -255,35 +279,22 @@ internal uint GetSourceGlobalObjectIdHash(uint networkPrefabHash) /// /// /// - internal NetworkObject HandleNetworkPrefabSpawn(uint networkPrefabAssetHash, ulong ownerClientId, ref BufferSerializer preInstanceDataSerializer, Vector3 position, Quaternion rotation) where T : IReaderWriter + internal NetworkObject HandleNetworkPrefabSpawn(uint networkPrefabAssetHash, ulong ownerClientId, ref BufferSerializer InstantiationPayloadSerializer, Vector3 position, Quaternion rotation) where T : IReaderWriter { if (m_PrefabAssetToPrefabHandler.TryGetValue(networkPrefabAssetHash, out var prefabInstanceHandler)) { - if (prefabInstanceHandler is INetworkInstantiationPayloadSynchronizer synchronizer) + if (InstantiationPayloadSerializer.IsReader) { - synchronizer.OnSynchronize(ref preInstanceDataSerializer); - } - - var networkObjectInstance = prefabInstanceHandler.Instantiate(ownerClientId, position, rotation); - - if (networkObjectInstance != null) - { - if (preInstanceDataSerializer.IsReader) - { - networkObjectInstance.InstantiationPayload = preInstanceDataSerializer.GetFastBufferReader(); - } - else + FastBufferReader instantiationPayloadReader = InstantiationPayloadSerializer.GetFastBufferReader(); + bool ShouldSychronize = instantiationPayloadReader.IsInitialized && instantiationPayloadReader.Length > 0; + if (ShouldSychronize && prefabInstanceHandler is INetworkInstantiationPayloadSynchronizer synchronizer) { - var writer = preInstanceDataSerializer.GetFastBufferWriter(); - if (writer.Length > 0) - { - unsafe - { - networkObjectInstance.InstantiationPayload = new FastBufferReader(writer.GetUnsafePtr(), Collections.Allocator.Persistent, writer.Length); - } - } + synchronizer.OnSynchronize(ref InstantiationPayloadSerializer); } } + + var networkObjectInstance = prefabInstanceHandler.Instantiate(ownerClientId, position, rotation); + //Now we must make sure this alternate PrefabAsset spawned in place of the prefab asset with the networkPrefabAssetHash (GlobalObjectIdHash) //is registered and linked to the networkPrefabAssetHash so during the HandleNetworkPrefabDestroy process we can identify the alternate prefab asset. if (networkObjectInstance != null && !m_PrefabInstanceToPrefabAsset.ContainsKey(networkObjectInstance.GlobalObjectIdHash)) diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 876eb5fe2f..11887ce169 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -748,7 +748,7 @@ internal NetworkObject InstantiateAndSpawnNoParameterChecks(NetworkObject networ // - Distributed authority mode always spawns the override if one exists. if (forceOverride || NetworkManager.IsClient || NetworkManager.DistributedAuthorityMode || NetworkManager.PrefabHandler.ContainsHandler(networkPrefab.GlobalObjectIdHash)) { - var intantiationPayloadWriter = new BufferSerializer(new BufferSerializerWriter(new FastBufferWriter(20, Collections.Allocator.Temp))); + var intantiationPayloadWriter = new BufferSerializer(new BufferSerializerWriter(new FastBufferWriter(16, Collections.Allocator.Temp, int.MaxValue))); networkObject = GetNetworkObjectToSpawn(networkPrefab.GlobalObjectIdHash, ownerClientId, ref intantiationPayloadWriter, position, rotation); } else // Under this case, server instantiate the prefab passed in. diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerTests.cs index 40b48e7a35..4519471c70 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerTests.cs @@ -110,7 +110,7 @@ public void NetworkPrefabHandlerClass([Values] bool distributedAuthority) //Test result of registering via GameObject reference Assert.True(gameObjectRegistered); - var instantiationPayloadWriter = new BufferSerializer(new BufferSerializerWriter(new FastBufferWriter(0, Collections.Allocator.Temp))); + var instantiationPayloadWriter = new BufferSerializer(new BufferSerializerWriter(new FastBufferWriter(16, Collections.Allocator.Temp, int.MaxValue))); var spawnedObject = networkPrefabHandler.HandleNetworkPrefabSpawn(baseObject.GlobalObjectIdHash, 0, ref instantiationPayloadWriter, prefabPosition, prefabRotation); //Test that something was instantiated @@ -136,7 +136,7 @@ public void NetworkPrefabHandlerClass([Values] bool distributedAuthority) prefabPosition = new Vector3(2.0f, 1.0f, 5.0f); prefabRotation = new Quaternion(4.0f, 1.5f, 5.4f, 5.1f); - instantiationPayloadWriter = new BufferSerializer(new BufferSerializerWriter(new FastBufferWriter(0, Collections.Allocator.Temp))); + instantiationPayloadWriter = new BufferSerializer(new BufferSerializerWriter(new FastBufferWriter(16, Collections.Allocator.Temp, int.MaxValue))); spawnedObject = networkPrefabHandler.HandleNetworkPrefabSpawn(baseObject.GlobalObjectIdHash, 0, ref instantiationPayloadWriter, prefabPosition, prefabRotation); //Test that something was instantiated @@ -162,7 +162,7 @@ public void NetworkPrefabHandlerClass([Values] bool distributedAuthority) prefabPosition = new Vector3(6.0f, 4.0f, 1.0f); prefabRotation = new Quaternion(3f, 2f, 4f, 1f); - instantiationPayloadWriter = new BufferSerializer(new BufferSerializerWriter(new FastBufferWriter(0, Collections.Allocator.Temp))); + instantiationPayloadWriter = new BufferSerializer(new BufferSerializerWriter(new FastBufferWriter(16, Collections.Allocator.Temp, int.MaxValue))); spawnedObject = networkPrefabHandler.HandleNetworkPrefabSpawn(baseObject.GlobalObjectIdHash, 0, ref instantiationPayloadWriter, prefabPosition, prefabRotation); //Test that something was instantiated From de9fc05a491762b048e4936f54a6f248e64bdd72 Mon Sep 17 00:00:00 2001 From: Extrys Date: Tue, 29 Apr 2025 18:09:57 +0200 Subject: [PATCH 5/8] Simplified approach that reuses createObjectMesage bufferSerializer --- .../Connection/NetworkConnectionManager.cs | 8 +- .../Runtime/Core/NetworkObject.cs | 73 +++++-------------- ...NetworkInstantiationPayloadSynchronizer.cs | 4 +- .../Runtime/Spawning/NetworkPrefabHandler.cs | 49 ++++++------- .../Runtime/Spawning/NetworkSpawnManager.cs | 9 +-- .../Prefabs/NetworkPrefabHandlerTests.cs | 9 +-- 6 files changed, 53 insertions(+), 99 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs index 53c59084ca..59a187a5fc 100644 --- a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs @@ -797,9 +797,8 @@ internal void HandleConnectionApproval(ulong ownerClientId, NetworkManager.Conne // Server-side spawning (only if there is a prefab hash or player prefab provided) if (!NetworkManager.DistributedAuthorityMode && response.CreatePlayerObject && (response.PlayerPrefabHash.HasValue || NetworkManager.NetworkConfig.PlayerPrefab != null)) { - var instantiationPayloadWriter = new BufferSerializer(new BufferSerializerWriter(new FastBufferWriter(16,Allocator.Temp, int.MaxValue))); - var playerObject = response.PlayerPrefabHash.HasValue ? NetworkManager.SpawnManager.GetNetworkObjectToSpawn(response.PlayerPrefabHash.Value, ownerClientId, ref instantiationPayloadWriter, response.Position ?? null, response.Rotation ?? null) - : NetworkManager.SpawnManager.GetNetworkObjectToSpawn(NetworkManager.NetworkConfig.PlayerPrefab.GetComponent().GlobalObjectIdHash, ownerClientId, ref instantiationPayloadWriter, response.Position ?? null, response.Rotation ?? null); + var playerObject = response.PlayerPrefabHash.HasValue ? NetworkManager.SpawnManager.GetNetworkObjectToSpawn(response.PlayerPrefabHash.Value, ownerClientId, response.Position ?? null, response.Rotation ?? null) + : NetworkManager.SpawnManager.GetNetworkObjectToSpawn(NetworkManager.NetworkConfig.PlayerPrefab.GetComponent().GlobalObjectIdHash, ownerClientId, response.Position ?? null, response.Rotation ?? null); // Spawn the player NetworkObject locally NetworkManager.SpawnManager.SpawnNetworkObjectLocally( @@ -951,8 +950,7 @@ internal void CreateAndSpawnPlayer(ulong ownerId) if (playerPrefab != null) { var globalObjectIdHash = playerPrefab.GetComponent().GlobalObjectIdHash; - var instantiationPayloadWriter = new BufferSerializer(new BufferSerializerWriter(new FastBufferWriter(16, Allocator.Temp, int.MaxValue))); - var networkObject = NetworkManager.SpawnManager.GetNetworkObjectToSpawn(globalObjectIdHash, ownerId, ref instantiationPayloadWriter, playerPrefab.transform.position, playerPrefab.transform.rotation); + var networkObject = NetworkManager.SpawnManager.GetNetworkObjectToSpawn(globalObjectIdHash, ownerId, playerPrefab.transform.position, playerPrefab.transform.rotation); networkObject.IsSceneObject = false; networkObject.SpawnAsPlayerObject(ownerId, networkObject.DestroyWithScene); } diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index dd811e98e1..4fb719a950 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; -using Unity.Collections; using Unity.Netcode.Components; #if UNITY_EDITOR using UnityEditor; @@ -46,13 +45,6 @@ public sealed class NetworkObject : MonoBehaviour [SerializeField] internal uint InScenePlacedSourceGlobalObjectIdHash; - /// - /// Metadata sent during the instantiation process. - /// Retrieved in INetworkCustomSpawnDataSynchronizer before instantiation, - /// and available to INetworkPrefabInstanceHandler.Instantiate() for custom handling by user code. - /// - internal FastBufferReader InstantiationPayload; - /// /// Gets the Prefab Hash Id of this object if the object is registerd as a prefab otherwise it returns 0 /// @@ -1819,8 +1811,6 @@ internal void SpawnInternal(bool destroyWithScene, ulong ownerClientId, bool pla } } - NetworkManager.PrefabHandler.InjectInstantiationPayload(this); - NetworkManager.SpawnManager.SpawnNetworkObjectLocally(this, NetworkManager.SpawnManager.GetNetworkObjectId(), IsSceneObject.HasValue && IsSceneObject.Value, playerObject, ownerClientId, destroyWithScene); if ((NetworkManager.DistributedAuthorityMode && NetworkManager.DAHost) || (!NetworkManager.DistributedAuthorityMode && NetworkManager.IsServer)) @@ -2822,7 +2812,6 @@ internal struct SceneObject public ulong NetworkObjectId; public ulong OwnerClientId; public ushort OwnershipFlags; - public FastBufferReader InstantiationPayload; public bool IsPlayerObject { @@ -2965,26 +2954,12 @@ public void Serialize(FastBufferWriter writer) var writeSize = 0; writeSize += HasTransform ? FastBufferWriter.GetWriteSize() : 0; writeSize += FastBufferWriter.GetWriteSize(); - if (HasInstantiationPayload) - { - writeSize += FastBufferWriter.GetWriteSize(); - writeSize += InstantiationPayload.Length; - } if (!writer.TryBeginWrite(writeSize)) { throw new OverflowException("Could not serialize SceneObject: Out of buffer space."); } - if (HasInstantiationPayload) - { - writer.WriteValueSafe(InstantiationPayload.Length); - unsafe - { - writer.WriteBytes(InstantiationPayload.GetUnsafePtr(), InstantiationPayload.Length); - } - } - if (HasTransform) { writer.WriteValue(Transform); @@ -3001,8 +2976,15 @@ public void Serialize(FastBufferWriter writer) writer.WriteValue(OwnerObject.GetSceneOriginHandle()); } - // Synchronize NetworkVariables and NetworkBehaviours + // Synchronize Payload, NetworkVariables and NetworkBehaviours var bufferSerializer = new BufferSerializer(new BufferSerializerWriter(writer)); + + if (HasInstantiationPayload) + { + if (NetworkManager.Singleton.PrefabHandler.TryGetPayloadSynchronizer(Hash, out INetworkInstantiationPayloadSynchronizer synchronizer)) + synchronizer.OnSynchronize(ref bufferSerializer); + } + OwnerObject.SynchronizeNetworkBehaviours(ref bufferSerializer, TargetClientId); } @@ -3045,34 +3027,12 @@ public void Deserialize(FastBufferReader reader) readSize += HasTransform ? FastBufferWriter.GetWriteSize() : 0; readSize += FastBufferWriter.GetWriteSize(); - int preInstanceDataSize = 0; - if (HasInstantiationPayload) - { - if (!reader.TryBeginRead(FastBufferWriter.GetWriteSize())) - { - throw new OverflowException($"Could not deserialize SceneObject: Reading past the end of the buffer ({nameof(InstantiationPayload)} size)"); - } - - reader.ReadValueSafe(out preInstanceDataSize); - readSize += FastBufferWriter.GetWriteSize(); - readSize += preInstanceDataSize; - } - // Try to begin reading the remaining bytes if (!reader.TryBeginRead(readSize)) { throw new OverflowException("Could not deserialize SceneObject: Reading past the end of the buffer"); } - if (HasInstantiationPayload) - { - unsafe - { - InstantiationPayload = new FastBufferReader(reader.GetUnsafePtrAtCurrentPosition(), Allocator.Persistent, preInstanceDataSize); - reader.Seek(reader.Position + preInstanceDataSize); - } - } - if (HasTransform) { reader.ReadValue(out Transform); @@ -3081,6 +3041,15 @@ public void Deserialize(FastBufferReader reader) // The NetworkSceneHandle is the server-side relative // scene handle that the NetworkObject resides in. reader.ReadValue(out NetworkSceneHandle); + + if (HasInstantiationPayload) + { + if (NetworkManager.Singleton.PrefabHandler.TryGetPayloadSynchronizer(Hash, out INetworkInstantiationPayloadSynchronizer synchronizer)) + { + var instantiationPayloadBufferReader = new BufferSerializer(new BufferSerializerReader(reader)); + synchronizer.OnSynchronize(ref instantiationPayloadBufferReader); + } + } } } @@ -3202,8 +3171,7 @@ internal SceneObject GetMessageSceneObject(ulong targetClientId = NetworkManager Hash = CheckForGlobalObjectIdHashOverride(), OwnerObject = this, TargetClientId = targetClientId, - HasInstantiationPayload = InstantiationPayload.IsInitialized, - InstantiationPayload = InstantiationPayload + HasInstantiationPayload = NetworkManager.PrefabHandler.HasPayloadSynchronizer(GlobalObjectIdHash), }; // Handle Parenting @@ -3298,11 +3266,6 @@ internal static NetworkObject AddSceneObject(in SceneObject sceneObject, FastBuf // in order to be able to determine which NetworkVariables the client will be allowed to read. networkObject.OwnerClientId = sceneObject.OwnerClientId; - // Even though the Instantiation Payload is typically consumed during the spawn message handling phase, - // we still assign it here to preserve the original spawn metadata for potential inspection, diagnostics, - // or in case future systems want to access it directly without relying on synchronization messages. - networkObject.InstantiationPayload = sceneObject.InstantiationPayload; - // Special Case: Invoke NetworkBehaviour.OnPreSpawn methods here before SynchronizeNetworkBehaviours networkObject.InvokeBehaviourNetworkPreSpawn(); diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/INetworkInstantiationPayloadSynchronizer.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/INetworkInstantiationPayloadSynchronizer.cs index b83752aca1..ead0c0b21a 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/INetworkInstantiationPayloadSynchronizer.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/INetworkInstantiationPayloadSynchronizer.cs @@ -16,5 +16,7 @@ public interface INetworkInstantiationPayloadSynchronizer /// /// The buffer serializer used to read or write custom instantiation data. void OnSynchronize(ref BufferSerializer serializer) where T : IReaderWriter; - } + void Serialize(FastBufferWriter writer); + void Deserialize(FastBufferReader reader); + } } diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs index 5b14989b1b..736f8b7960 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Unity.Collections; using UnityEngine; namespace Unity.Netcode @@ -226,29 +227,33 @@ public bool RemoveHandler(uint globalObjectIdHash) /// true or false internal bool ContainsHandler(uint networkPrefabHash) => m_PrefabAssetToPrefabHandler.ContainsKey(networkPrefabHash) || m_PrefabInstanceToPrefabAsset.ContainsKey(networkPrefabHash); - /// - /// Injects instantiation payload on the networkObject - /// - /// Injection target - internal void InjectInstantiationPayload(NetworkObject networkObject) + internal bool HasPayloadSynchronizer(uint objectHash) => TryGetPayloadSynchronizer(objectHash, out _); + internal bool TryGetPayloadSynchronizer(uint objectHash, out INetworkInstantiationPayloadSynchronizer synchronizer) + { + if (m_PrefabAssetToPrefabHandler.TryGetValue(objectHash, out var prefabInstanceHandler)) + { + if (prefabInstanceHandler is INetworkInstantiationPayloadSynchronizer payloadSynchronizer) + { + synchronizer = payloadSynchronizer; + return true; + } + } + synchronizer = null; + return false; + } + + internal void ReadInstantiationPayload(uint objectHash, FastBufferReader reader) { - if (m_PrefabAssetToPrefabHandler.TryGetValue(networkObject.GlobalObjectIdHash, out var prefabInstanceHandler)) + if (m_PrefabAssetToPrefabHandler.TryGetValue(objectHash, out var prefabInstanceHandler)) { if (prefabInstanceHandler is INetworkInstantiationPayloadSynchronizer synchronizer) { - var fastBufferWriter = new FastBufferWriter(16, Collections.Allocator.Temp, int.MaxValue); - var instantiationPayloadBufferWriter = new BufferSerializer(new BufferSerializerWriter(fastBufferWriter)); - synchronizer.OnSynchronize(ref instantiationPayloadBufferWriter); - if (fastBufferWriter.Length > 0) - { - unsafe - { - networkObject.InstantiationPayload = new FastBufferReader(fastBufferWriter.GetUnsafePtr(), Collections.Allocator.Persistent, fastBufferWriter.Length); - } - } + var instantiationPayloadBufferReader = new BufferSerializer(new BufferSerializerReader(reader)); + synchronizer.OnSynchronize(ref instantiationPayloadBufferReader); } } } + /// /// Returns the source NetworkPrefab's @@ -279,20 +284,10 @@ internal uint GetSourceGlobalObjectIdHash(uint networkPrefabHash) /// /// /// - internal NetworkObject HandleNetworkPrefabSpawn(uint networkPrefabAssetHash, ulong ownerClientId, ref BufferSerializer InstantiationPayloadSerializer, Vector3 position, Quaternion rotation) where T : IReaderWriter + internal NetworkObject HandleNetworkPrefabSpawn(uint networkPrefabAssetHash, ulong ownerClientId, Vector3 position, Quaternion rotation) { if (m_PrefabAssetToPrefabHandler.TryGetValue(networkPrefabAssetHash, out var prefabInstanceHandler)) { - if (InstantiationPayloadSerializer.IsReader) - { - FastBufferReader instantiationPayloadReader = InstantiationPayloadSerializer.GetFastBufferReader(); - bool ShouldSychronize = instantiationPayloadReader.IsInitialized && instantiationPayloadReader.Length > 0; - if (ShouldSychronize && prefabInstanceHandler is INetworkInstantiationPayloadSynchronizer synchronizer) - { - synchronizer.OnSynchronize(ref InstantiationPayloadSerializer); - } - } - var networkObjectInstance = prefabInstanceHandler.Instantiate(ownerClientId, position, rotation); //Now we must make sure this alternate PrefabAsset spawned in place of the prefab asset with the networkPrefabAssetHash (GlobalObjectIdHash) diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 11887ce169..7b5a572d4f 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -749,7 +749,7 @@ internal NetworkObject InstantiateAndSpawnNoParameterChecks(NetworkObject networ if (forceOverride || NetworkManager.IsClient || NetworkManager.DistributedAuthorityMode || NetworkManager.PrefabHandler.ContainsHandler(networkPrefab.GlobalObjectIdHash)) { var intantiationPayloadWriter = new BufferSerializer(new BufferSerializerWriter(new FastBufferWriter(16, Collections.Allocator.Temp, int.MaxValue))); - networkObject = GetNetworkObjectToSpawn(networkPrefab.GlobalObjectIdHash, ownerClientId, ref intantiationPayloadWriter, position, rotation); + networkObject = GetNetworkObjectToSpawn(networkPrefab.GlobalObjectIdHash, ownerClientId, position, rotation); } else // Under this case, server instantiate the prefab passed in. { @@ -780,14 +780,14 @@ internal NetworkObject InstantiateAndSpawnNoParameterChecks(NetworkObject networ /// Gets the right NetworkObject prefab instance to spawn. If a handler is registered or there is an override assigned to the /// passed in globalObjectIdHash value, then that is what will be instantiated, spawned, and returned. /// - internal NetworkObject GetNetworkObjectToSpawn(uint globalObjectIdHash, ulong ownerId, ref BufferSerializer instantiationPayloadSerializer, Vector3? position, Quaternion? rotation, bool isScenePlaced = false) where T : IReaderWriter + internal NetworkObject GetNetworkObjectToSpawn(uint globalObjectIdHash, ulong ownerId, Vector3? position, Quaternion? rotation, bool isScenePlaced = false) { NetworkObject networkObject = null; // If the prefab hash has a registered INetworkPrefabInstanceHandler derived class if (NetworkManager.PrefabHandler.ContainsHandler(globalObjectIdHash)) { // Let the handler spawn the NetworkObject - networkObject = NetworkManager.PrefabHandler.HandleNetworkPrefabSpawn(globalObjectIdHash, ownerId, ref instantiationPayloadSerializer, position ?? default, rotation ?? default); + networkObject = NetworkManager.PrefabHandler.HandleNetworkPrefabSpawn(globalObjectIdHash, ownerId, position ?? default, rotation ?? default); networkObject.NetworkManagerOwner = NetworkManager; } else @@ -889,8 +889,7 @@ internal NetworkObject CreateLocalNetworkObject(NetworkObject.SceneObject sceneO // If scene management is disabled or the NetworkObject was dynamically spawned if (!NetworkManager.NetworkConfig.EnableSceneManagement || !sceneObject.IsSceneObject) { - var instantiationPayloadReader = new BufferSerializer(new BufferSerializerReader(sceneObject.InstantiationPayload)); - networkObject = GetNetworkObjectToSpawn(sceneObject.Hash, sceneObject.OwnerClientId, ref instantiationPayloadReader, position, rotation, sceneObject.IsSceneObject); + networkObject = GetNetworkObjectToSpawn(sceneObject.Hash, sceneObject.OwnerClientId, position, rotation, sceneObject.IsSceneObject); } else // Get the in-scene placed NetworkObject { diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerTests.cs index 4519471c70..c66c843abf 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerTests.cs @@ -110,8 +110,7 @@ public void NetworkPrefabHandlerClass([Values] bool distributedAuthority) //Test result of registering via GameObject reference Assert.True(gameObjectRegistered); - var instantiationPayloadWriter = new BufferSerializer(new BufferSerializerWriter(new FastBufferWriter(16, Collections.Allocator.Temp, int.MaxValue))); - var spawnedObject = networkPrefabHandler.HandleNetworkPrefabSpawn(baseObject.GlobalObjectIdHash, 0, ref instantiationPayloadWriter, prefabPosition, prefabRotation); + var spawnedObject = networkPrefabHandler.HandleNetworkPrefabSpawn(baseObject.GlobalObjectIdHash, 0, prefabPosition, prefabRotation); //Test that something was instantiated Assert.NotNull(spawnedObject); @@ -136,8 +135,7 @@ public void NetworkPrefabHandlerClass([Values] bool distributedAuthority) prefabPosition = new Vector3(2.0f, 1.0f, 5.0f); prefabRotation = new Quaternion(4.0f, 1.5f, 5.4f, 5.1f); - instantiationPayloadWriter = new BufferSerializer(new BufferSerializerWriter(new FastBufferWriter(16, Collections.Allocator.Temp, int.MaxValue))); - spawnedObject = networkPrefabHandler.HandleNetworkPrefabSpawn(baseObject.GlobalObjectIdHash, 0, ref instantiationPayloadWriter, prefabPosition, prefabRotation); + spawnedObject = networkPrefabHandler.HandleNetworkPrefabSpawn(baseObject.GlobalObjectIdHash, 0, prefabPosition, prefabRotation); //Test that something was instantiated Assert.NotNull(spawnedObject); @@ -162,8 +160,7 @@ public void NetworkPrefabHandlerClass([Values] bool distributedAuthority) prefabPosition = new Vector3(6.0f, 4.0f, 1.0f); prefabRotation = new Quaternion(3f, 2f, 4f, 1f); - instantiationPayloadWriter = new BufferSerializer(new BufferSerializerWriter(new FastBufferWriter(16, Collections.Allocator.Temp, int.MaxValue))); - spawnedObject = networkPrefabHandler.HandleNetworkPrefabSpawn(baseObject.GlobalObjectIdHash, 0, ref instantiationPayloadWriter, prefabPosition, prefabRotation); + spawnedObject = networkPrefabHandler.HandleNetworkPrefabSpawn(baseObject.GlobalObjectIdHash, 0, prefabPosition, prefabRotation); //Test that something was instantiated Assert.NotNull(spawnedObject); From 6419ad0643a7f697f19e8f07e42c6bc1ac26f912 Mon Sep 17 00:00:00 2001 From: Extrys Date: Tue, 29 Apr 2025 18:11:37 +0200 Subject: [PATCH 6/8] commented non generic serialization methods --- .../Spawning/INetworkInstantiationPayloadSynchronizer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/INetworkInstantiationPayloadSynchronizer.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/INetworkInstantiationPayloadSynchronizer.cs index ead0c0b21a..d355294b23 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/INetworkInstantiationPayloadSynchronizer.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/INetworkInstantiationPayloadSynchronizer.cs @@ -16,7 +16,7 @@ public interface INetworkInstantiationPayloadSynchronizer /// /// The buffer serializer used to read or write custom instantiation data. void OnSynchronize(ref BufferSerializer serializer) where T : IReaderWriter; - void Serialize(FastBufferWriter writer); - void Deserialize(FastBufferReader reader); + //void Serialize(FastBufferWriter writer); + //void Deserialize(FastBufferReader reader); } } From 4005c50503624802a5dd546694418c711d5be3e4 Mon Sep 17 00:00:00 2001 From: Extrys Date: Tue, 29 Apr 2025 18:19:22 +0200 Subject: [PATCH 7/8] Cleaning diff --- .../Runtime/Spawning/NetworkPrefabHandler.cs | 26 +++++++++---------- .../Runtime/Spawning/NetworkSpawnManager.cs | 1 - 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs index 736f8b7960..5c1a15efb7 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using Unity.Collections; using UnityEngine; namespace Unity.Netcode @@ -227,7 +226,19 @@ public bool RemoveHandler(uint globalObjectIdHash) /// true or false internal bool ContainsHandler(uint networkPrefabHash) => m_PrefabAssetToPrefabHandler.ContainsKey(networkPrefabHash) || m_PrefabInstanceToPrefabAsset.ContainsKey(networkPrefabHash); + /// + /// Check to see if a is registered to an implementation + /// + /// + /// internal bool HasPayloadSynchronizer(uint objectHash) => TryGetPayloadSynchronizer(objectHash, out _); + + /// + /// Returns the implementation for a given + /// + /// + /// + /// internal bool TryGetPayloadSynchronizer(uint objectHash, out INetworkInstantiationPayloadSynchronizer synchronizer) { if (m_PrefabAssetToPrefabHandler.TryGetValue(objectHash, out var prefabInstanceHandler)) @@ -242,19 +253,6 @@ internal bool TryGetPayloadSynchronizer(uint objectHash, out INetworkInstantiati return false; } - internal void ReadInstantiationPayload(uint objectHash, FastBufferReader reader) - { - if (m_PrefabAssetToPrefabHandler.TryGetValue(objectHash, out var prefabInstanceHandler)) - { - if (prefabInstanceHandler is INetworkInstantiationPayloadSynchronizer synchronizer) - { - var instantiationPayloadBufferReader = new BufferSerializer(new BufferSerializerReader(reader)); - synchronizer.OnSynchronize(ref instantiationPayloadBufferReader); - } - } - } - - /// /// Returns the source NetworkPrefab's /// diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 7b5a572d4f..36a6c2abd1 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -748,7 +748,6 @@ internal NetworkObject InstantiateAndSpawnNoParameterChecks(NetworkObject networ // - Distributed authority mode always spawns the override if one exists. if (forceOverride || NetworkManager.IsClient || NetworkManager.DistributedAuthorityMode || NetworkManager.PrefabHandler.ContainsHandler(networkPrefab.GlobalObjectIdHash)) { - var intantiationPayloadWriter = new BufferSerializer(new BufferSerializerWriter(new FastBufferWriter(16, Collections.Allocator.Temp, int.MaxValue))); networkObject = GetNetworkObjectToSpawn(networkPrefab.GlobalObjectIdHash, ownerClientId, position, rotation); } else // Under this case, server instantiate the prefab passed in. From a103e49bdf153d2bb6d8deaf3106ac73a35700e8 Mon Sep 17 00:00:00 2001 From: Extrys Date: Tue, 29 Apr 2025 18:22:27 +0200 Subject: [PATCH 8/8] Cleaning diff --- .../Runtime/Spawning/NetworkSpawnManager.cs | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 36a6c2abd1..19227c82ba 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -807,22 +807,22 @@ internal NetworkObject GetNetworkObjectToSpawn(uint globalObjectIdHash, ulong ow break; case NetworkPrefabOverride.Hash: case NetworkPrefabOverride.Prefab: - { - // When scene management is disabled and this is an in-scene placed NetworkObject, we want to always use the - // SourcePrefabToOverride and not any possible prefab override as a user might want to spawn overrides dynamically - // but might want to use the same source network prefab as an in-scene placed NetworkObject. - // (When scene management is enabled, clients don't delete their in-scene placed NetworkObjects prior to dynamically - // spawning them so the original prefab placed is preserved and this is not needed) - if (inScenePlacedWithNoSceneManagement) - { - networkPrefabReference = networkPrefab.SourcePrefabToOverride ? networkPrefab.SourcePrefabToOverride : networkPrefab.Prefab; - } - else { - networkPrefabReference = NetworkManager.NetworkConfig.Prefabs.NetworkPrefabOverrideLinks[globalObjectIdHash].OverridingTargetPrefab; + // When scene management is disabled and this is an in-scene placed NetworkObject, we want to always use the + // SourcePrefabToOverride and not any possible prefab override as a user might want to spawn overrides dynamically + // but might want to use the same source network prefab as an in-scene placed NetworkObject. + // (When scene management is enabled, clients don't delete their in-scene placed NetworkObjects prior to dynamically + // spawning them so the original prefab placed is preserved and this is not needed) + if (inScenePlacedWithNoSceneManagement) + { + networkPrefabReference = networkPrefab.SourcePrefabToOverride ? networkPrefab.SourcePrefabToOverride : networkPrefab.Prefab; + } + else + { + networkPrefabReference = NetworkManager.NetworkConfig.Prefabs.NetworkPrefabOverrideLinks[globalObjectIdHash].OverridingTargetPrefab; + } + break; } - break; - } } }