Skip to content

Commit a34ec5a

Browse files
committed
Merge remote-tracking branch 'extrys/develop-2.0.0' into feat/sync-instantiation-data
2 parents d4a9810 + 4586a6e commit a34ec5a

File tree

8 files changed

+313
-19
lines changed

8 files changed

+313
-19
lines changed

com.unity.netcode.gameobjects/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ Additional documentation and release notes are available at [Multiplayer Documen
2323

2424
- Added `SinglePlayerTransport` that provides the ability to start as a host for a single player network session. (#3473)
2525
- When using UnityTransport >=2.4 and Unity >= 6000.1.0a1, SetConnectionData will accept a fully qualified hostname instead of an IP as a connect address on the client side. (#3441)
26+
- Added `NetworkPrefabInstanceHandlerWithData<T>`, a variant of `INetworkPrefabInstanceHandler` that provides access to custom instantiation data directly within the `Instantiate()` method. (#3430)
2627

2728
### Fixed
2829

com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,12 @@ public uint PrefabIdHash
5757
}
5858
}
5959

60+
/// <summary>
61+
/// InstantiationData sent during the instantiation process.
62+
/// Available to read as T parameter to <see cref="NetworkPrefabInstanceHandlerWithData{T}.Instantiate(ulong, Vector3, Quaternion, T)"/> for custom handling by user code.
63+
/// </summary>
64+
internal byte[] InstantiationData;
65+
6066
/// <summary>
6167
/// All <see cref="NetworkTransform"/> component instances associated with a <see cref="NetworkObject"/> component instance.
6268
/// </summary>
@@ -2837,6 +2843,12 @@ public bool SpawnWithObservers
28372843
set => ByteUtility.SetBit(ref m_BitField, 10, value);
28382844
}
28392845

2846+
public bool HasInstantiationData
2847+
{
2848+
get => ByteUtility.GetBit(m_BitField, 11);
2849+
set => ByteUtility.SetBit(ref m_BitField, 11, value);
2850+
}
2851+
28402852
// When handling the initial synchronization of NetworkObjects,
28412853
// this will be populated with the known observers.
28422854
public ulong[] Observers;
@@ -2925,6 +2937,12 @@ public void Serialize(FastBufferWriter writer)
29252937
writer.WriteValue(OwnerObject.GetSceneOriginHandle());
29262938
}
29272939

2940+
if (HasInstantiationData)
2941+
{
2942+
BytePacker.WriteValuePacked(writer, OwnerObject.InstantiationData.Length);
2943+
writer.WriteBytesSafe(OwnerObject.InstantiationData);
2944+
}
2945+
29282946
// Synchronize NetworkVariables and NetworkBehaviours
29292947
var bufferSerializer = new BufferSerializer<BufferSerializerWriter>(new BufferSerializerWriter(writer));
29302948
OwnerObject.SynchronizeNetworkBehaviours(ref bufferSerializer, TargetClientId);
@@ -3101,7 +3119,8 @@ internal SceneObject GetMessageSceneObject(ulong targetClientId = NetworkManager
31013119
NetworkSceneHandle = NetworkSceneHandle,
31023120
Hash = CheckForGlobalObjectIdHashOverride(),
31033121
OwnerObject = this,
3104-
TargetClientId = targetClientId
3122+
TargetClientId = targetClientId,
3123+
HasInstantiationData = InstantiationData != null && InstantiationData.Length > 0
31053124
};
31063125

31073126
// Handle Parenting
@@ -3166,8 +3185,15 @@ internal SceneObject GetMessageSceneObject(ulong targetClientId = NetworkManager
31663185
/// <returns>The deserialized NetworkObject or null if deserialization failed</returns>
31673186
internal static NetworkObject AddSceneObject(in SceneObject sceneObject, FastBufferReader reader, NetworkManager networkManager, bool invokedByMessage = false)
31683187
{
3188+
var bufferSerializer = new BufferSerializer<BufferSerializerReader>(new BufferSerializerReader(reader));
3189+
3190+
//Synchronize the instantiation data if needed
3191+
FastBufferReader instantiationDataReader = sceneObject.HasInstantiationData ? networkManager.PrefabHandler.GetInstantiationDataReader(sceneObject.Hash, reader) : default;
3192+
31693193
//Attempt to create a local NetworkObject
3170-
var networkObject = networkManager.SpawnManager.CreateLocalNetworkObject(sceneObject);
3194+
var networkObject = networkManager.SpawnManager.CreateLocalNetworkObject(sceneObject, instantiationDataReader);
3195+
3196+
instantiationDataReader.Dispose();
31713197

31723198
if (networkObject == null)
31733199
{
@@ -3200,7 +3226,6 @@ internal static NetworkObject AddSceneObject(in SceneObject sceneObject, FastBuf
32003226
networkObject.InvokeBehaviourNetworkPreSpawn();
32013227

32023228
// Synchronize NetworkBehaviours
3203-
var bufferSerializer = new BufferSerializer<BufferSerializerReader>(new BufferSerializerReader(reader));
32043229
networkObject.SynchronizeNetworkBehaviours(ref bufferSerializer, networkManager.LocalClientId);
32053230

32063231
// If we are an in-scene placed NetworkObject and we originally had a parent but when synchronized we are

com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs

Lines changed: 94 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ public interface INetworkPrefabInstanceHandler
2121
/// Note on Pooling: If you are using a NetworkObject pool, don't forget to make the NetworkObject active
2222
/// via the <see cref="GameObject.SetActive(bool)"/> method.
2323
/// </summary>
24+
/// <remarks>
25+
/// If you need to pass custom data at instantiation time (e.g., selecting a variant, setting initialization parameters, or choosing a pre-instantiated object),
26+
/// implement <see cref="NetworkPrefabInstanceHandlerWithData{T}"/> instead.
27+
/// </remarks>
2428
/// <param name="ownerClientId">the owner for the <see cref="NetworkObject"/> to be instantiated</param>
2529
/// <param name="position">the initial/default position for the <see cref="NetworkObject"/> to be instantiated</param>
2630
/// <param name="rotation">the initial/default rotation for the <see cref="NetworkObject"/> to be instantiated</param>
@@ -57,6 +61,12 @@ public class NetworkPrefabHandler
5761
/// </summary>
5862
private readonly Dictionary<uint, INetworkPrefabInstanceHandler> m_PrefabAssetToPrefabHandler = new Dictionary<uint, INetworkPrefabInstanceHandler>();
5963

64+
/// <summary>
65+
/// Links a network prefab asset to a class with the INetworkPrefabInstanceHandlerWithData interface,
66+
/// used to keep a smaller lookup table than <see cref="m_PrefabAssetToPrefabHandler"/> for faster instantiation data injection into NetworkObject
67+
/// </summary>
68+
private readonly Dictionary<uint, INetworkPrefabInstanceHandlerWithData> m_PrefabAssetToPrefabHandlerWithData = new Dictionary<uint, INetworkPrefabInstanceHandlerWithData>();
69+
6070
/// <summary>
6171
/// Links the custom prefab instance's GlobalNetworkObjectId to the original prefab asset's GlobalNetworkObjectId. (Needed for HandleNetworkPrefabDestroy)
6272
/// [PrefabInstance][PrefabAsset]
@@ -98,12 +108,44 @@ public bool AddHandler(uint globalObjectIdHash, INetworkPrefabInstanceHandler in
98108
if (!m_PrefabAssetToPrefabHandler.ContainsKey(globalObjectIdHash))
99109
{
100110
m_PrefabAssetToPrefabHandler.Add(globalObjectIdHash, instanceHandler);
111+
if (instanceHandler is INetworkPrefabInstanceHandlerWithData instanceHandlerWithData)
112+
{
113+
m_PrefabAssetToPrefabHandlerWithData.Add(globalObjectIdHash, instanceHandlerWithData);
114+
}
101115
return true;
102116
}
103117

104118
return false;
105119
}
106120

121+
public void SetInstantiationData<T>(GameObject gameObject, T instantiationData) where T : struct, INetworkSerializable
122+
{
123+
if (gameObject.TryGetComponent<NetworkObject>(out var networkObject))
124+
{
125+
SetInstantiationData(networkObject, instantiationData);
126+
}
127+
}
128+
public void SetInstantiationData<T>(NetworkObject networkObject, T data) where T : struct, INetworkSerializable
129+
{
130+
if (!TryGetHandlerWithData(networkObject.GlobalObjectIdHash, out var prefabHandler) || !prefabHandler.HandlesDataType<T>())
131+
{
132+
Debug.LogError("[InstantiationData] Cannot inject data: no compatible handler found for the specified data type.");
133+
}
134+
135+
using var writer = new FastBufferWriter(4, Collections.Allocator.Temp, int.MaxValue);
136+
var serializer = new BufferSerializer<BufferSerializerWriter>(new BufferSerializerWriter(writer));
137+
138+
try
139+
{
140+
data.NetworkSerialize(serializer);
141+
networkObject.InstantiationData = writer.ToArray();
142+
}
143+
catch (Exception ex)
144+
{
145+
NetworkLog.LogError($"[InstantiationData] Failed to serialize instantiation data for {nameof(NetworkObject)} '{networkObject.name}': {ex}");
146+
}
147+
}
148+
107149
/// <summary>
108150
/// HOST ONLY!
109151
/// Since a host is unique and is considered both a client and a server, for each source NetworkPrefab you must manually
@@ -199,6 +241,7 @@ public bool RemoveHandler(uint globalObjectIdHash)
199241
m_PrefabInstanceToPrefabAsset.Remove(networkPrefabHashKey);
200242
}
201243

244+
m_PrefabAssetToPrefabHandlerWithData.Remove(globalObjectIdHash);
202245
return m_PrefabAssetToPrefabHandler.Remove(globalObjectIdHash);
203246
}
204247

@@ -223,6 +266,38 @@ public bool RemoveHandler(uint globalObjectIdHash)
223266
/// <returns>true or false</returns>
224267
internal bool ContainsHandler(uint networkPrefabHash) => m_PrefabAssetToPrefabHandler.ContainsKey(networkPrefabHash) || m_PrefabInstanceToPrefabAsset.ContainsKey(networkPrefabHash);
225268

269+
/// <summary>
270+
/// Returns the <see cref="INetworkPrefabInstanceHandlerWithData"/> implementation for a given <see cref="NetworkObject.GlobalObjectIdHash"/>
271+
/// </summary>
272+
/// <param name="objectHash"></param>
273+
/// <param name="handler"></param>
274+
/// <returns></returns>
275+
internal bool TryGetHandlerWithData(uint objectHash, out INetworkPrefabInstanceHandlerWithData handler)
276+
{
277+
return m_PrefabAssetToPrefabHandlerWithData.TryGetValue(objectHash, out handler);
278+
}
279+
280+
/// <summary>
281+
/// Reads the instantiation data for a given <see cref="NetworkObject.GlobalObjectIdHash"/>
282+
/// </summary>
283+
internal FastBufferReader GetInstantiationDataReader(uint objectHash, FastBufferReader fastBufferReader)
284+
{
285+
if (!TryGetHandlerWithData(objectHash, out _))
286+
{
287+
if (NetworkManager.Singleton.LogLevel <= LogLevel.Developer)
288+
{
289+
Debug.LogWarning($"No handler with data found for object hash {objectHash}.");
290+
}
291+
return default;
292+
}
293+
294+
ByteUnpacker.ReadValuePacked(fastBufferReader, out int dataSize);
295+
var position = fastBufferReader.Position;
296+
var dataReader = new FastBufferReader(fastBufferReader, Collections.Allocator.Temp, dataSize, position);
297+
fastBufferReader.Seek(position + dataSize);
298+
return dataReader;
299+
}
300+
226301
/// <summary>
227302
/// Returns the source NetworkPrefab's <see cref="NetworkObject.GlobalObjectIdHash"/>
228303
/// </summary>
@@ -252,23 +327,30 @@ internal uint GetSourceGlobalObjectIdHash(uint networkPrefabHash)
252327
/// <param name="position"></param>
253328
/// <param name="rotation"></param>
254329
/// <returns></returns>
255-
internal NetworkObject HandleNetworkPrefabSpawn(uint networkPrefabAssetHash, ulong ownerClientId, Vector3 position, Quaternion rotation)
330+
internal NetworkObject HandleNetworkPrefabSpawn(uint networkPrefabAssetHash, ulong ownerClientId, Vector3 position, Quaternion rotation, FastBufferReader instantiationDataReader = default)
256331
{
257-
if (m_PrefabAssetToPrefabHandler.TryGetValue(networkPrefabAssetHash, out var prefabInstanceHandler))
332+
NetworkObject networkObjectInstance = null;
333+
if (instantiationDataReader.IsInitialized)
258334
{
259-
var networkObjectInstance = prefabInstanceHandler.Instantiate(ownerClientId, position, rotation);
260-
261-
//Now we must make sure this alternate PrefabAsset spawned in place of the prefab asset with the networkPrefabAssetHash (GlobalObjectIdHash)
262-
//is registered and linked to the networkPrefabAssetHash so during the HandleNetworkPrefabDestroy process we can identify the alternate prefab asset.
263-
if (networkObjectInstance != null && !m_PrefabInstanceToPrefabAsset.ContainsKey(networkObjectInstance.GlobalObjectIdHash))
335+
if (m_PrefabAssetToPrefabHandlerWithData.TryGetValue(networkPrefabAssetHash, out var prefabInstanceHandler))
264336
{
265-
m_PrefabInstanceToPrefabAsset.Add(networkObjectInstance.GlobalObjectIdHash, networkPrefabAssetHash);
337+
networkObjectInstance = prefabInstanceHandler.Instantiate(ownerClientId, position, rotation, instantiationDataReader);
266338
}
267-
268-
return networkObjectInstance;
269339
}
270-
271-
return null;
340+
else
341+
{
342+
if (m_PrefabAssetToPrefabHandler.TryGetValue(networkPrefabAssetHash, out var prefabInstanceHandler))
343+
{
344+
networkObjectInstance = prefabInstanceHandler.Instantiate(ownerClientId, position, rotation);
345+
}
346+
}
347+
//Now we must make sure this alternate PrefabAsset spawned in place of the prefab asset with the networkPrefabAssetHash (GlobalObjectIdHash)
348+
//is registered and linked to the networkPrefabAssetHash so during the HandleNetworkPrefabDestroy process we can identify the alternate prefab asset.
349+
if (networkObjectInstance != null && !m_PrefabInstanceToPrefabAsset.ContainsKey(networkObjectInstance.GlobalObjectIdHash))
350+
{
351+
m_PrefabInstanceToPrefabAsset.Add(networkObjectInstance.GlobalObjectIdHash, networkPrefabAssetHash);
352+
}
353+
return networkObjectInstance;
272354
}
273355

274356
/// <summary>
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
using UnityEngine;
2+
3+
namespace Unity.Netcode
4+
{
5+
/// <summary>
6+
/// Specialized version of <see cref="INetworkPrefabInstanceHandler"/> that receives
7+
/// custom instantiation data injected by the server before spawning.
8+
/// </summary>
9+
public abstract class NetworkPrefabInstanceHandlerWithData<T> : INetworkPrefabInstanceHandlerWithData where T : struct, INetworkSerializable
10+
{
11+
public abstract NetworkObject Instantiate(ulong ownerClientId, Vector3 position, Quaternion rotation, T instantiationData);
12+
13+
public abstract void Destroy(NetworkObject networkObject);
14+
15+
bool INetworkPrefabInstanceHandlerWithData.HandlesDataType<TK>() => typeof(T) == typeof(TK);
16+
17+
NetworkObject INetworkPrefabInstanceHandlerWithData.Instantiate(ulong ownerClientId, Vector3 position, Quaternion rotation, FastBufferReader reader)
18+
{
19+
var startPosition = reader.Position;
20+
reader.ReadValueSafe(out T payload);
21+
var length = reader.Position - startPosition;
22+
23+
NetworkObject networkObject = Instantiate(ownerClientId, position, rotation, payload);
24+
reader.Seek(startPosition);
25+
if (networkObject.InstantiationData == null || networkObject.InstantiationData.Length != length)
26+
{
27+
networkObject.InstantiationData = new byte[length];
28+
}
29+
reader.ReadBytesSafe(ref networkObject.InstantiationData, length);
30+
return networkObject;
31+
}
32+
33+
NetworkObject INetworkPrefabInstanceHandler.Instantiate(ulong ownerClientId, Vector3 position, Quaternion rotation) => Instantiate(ownerClientId, position, rotation, default);
34+
}
35+
36+
internal interface INetworkPrefabInstanceHandlerWithData : INetworkPrefabInstanceHandler
37+
{
38+
bool HandlesDataType<T>();
39+
NetworkObject Instantiate(ulong ownerClientId, Vector3 position, Quaternion rotation, FastBufferReader reader);
40+
}
41+
}

com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabInstanceHandlerWithData.cs.meta

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -817,14 +817,14 @@ internal NetworkObject InstantiateAndSpawnNoParameterChecks(NetworkObject networ
817817
/// Gets the right NetworkObject prefab instance to spawn. If a handler is registered or there is an override assigned to the
818818
/// passed in globalObjectIdHash value, then that is what will be instantiated, spawned, and returned.
819819
/// </summary>
820-
internal NetworkObject GetNetworkObjectToSpawn(uint globalObjectIdHash, ulong ownerId, Vector3? position, Quaternion? rotation, bool isScenePlaced = false)
820+
internal NetworkObject GetNetworkObjectToSpawn(uint globalObjectIdHash, ulong ownerId, Vector3? position, Quaternion? rotation, bool isScenePlaced = false, FastBufferReader instantiationDataReader = default)
821821
{
822822
NetworkObject networkObject = null;
823823
// If the prefab hash has a registered INetworkPrefabInstanceHandler derived class
824824
if (NetworkManager.PrefabHandler.ContainsHandler(globalObjectIdHash))
825825
{
826826
// Let the handler spawn the NetworkObject
827-
networkObject = NetworkManager.PrefabHandler.HandleNetworkPrefabSpawn(globalObjectIdHash, ownerId, position ?? default, rotation ?? default);
827+
networkObject = NetworkManager.PrefabHandler.HandleNetworkPrefabSpawn(globalObjectIdHash, ownerId, position ?? default, rotation ?? default, instantiationDataReader);
828828
networkObject.NetworkManagerOwner = NetworkManager;
829829
}
830830
else
@@ -913,7 +913,7 @@ internal NetworkObject InstantiateNetworkPrefab(GameObject networkPrefab, uint p
913913
/// For most cases this is client-side only, with the exception of when the server
914914
/// is spawning a player.
915915
/// </remarks>
916-
internal NetworkObject CreateLocalNetworkObject(NetworkObject.SceneObject sceneObject)
916+
internal NetworkObject CreateLocalNetworkObject(NetworkObject.SceneObject sceneObject, FastBufferReader instantiationDataReader = default)
917917
{
918918
NetworkObject networkObject = null;
919919
var globalObjectIdHash = sceneObject.Hash;
@@ -926,7 +926,7 @@ internal NetworkObject CreateLocalNetworkObject(NetworkObject.SceneObject sceneO
926926
// If scene management is disabled or the NetworkObject was dynamically spawned
927927
if (!NetworkManager.NetworkConfig.EnableSceneManagement || !sceneObject.IsSceneObject)
928928
{
929-
networkObject = GetNetworkObjectToSpawn(sceneObject.Hash, sceneObject.OwnerClientId, position, rotation, sceneObject.IsSceneObject);
929+
networkObject = GetNetworkObjectToSpawn(sceneObject.Hash, sceneObject.OwnerClientId, position, rotation, sceneObject.IsSceneObject, instantiationDataReader);
930930
}
931931
else // Get the in-scene placed NetworkObject
932932
{

0 commit comments

Comments
 (0)