Skip to content
Merged
Show file tree
Hide file tree
Changes from 60 commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
3ff9326
feat: Network Object Instantiation Payload
Extrys Apr 27, 2025
7f395ba
Merge branch 'Unity-Technologies:develop-2.0.0' into develop-2.0.0
Extrys Apr 27, 2025
f0d49aa
insight on INetworkInstantiationPayloadSynchronizer in the INetworkPr…
Extrys Apr 27, 2025
3f34e83
cleaning diff
Extrys Apr 27, 2025
47680f5
Some fixes for object instantiated in the server without handler
Extrys Apr 27, 2025
b8765a2
Merge branch 'Unity-Technologies:develop-2.0.0' into develop-2.0.0
Extrys Apr 28, 2025
de9fc05
Simplified approach that reuses createObjectMesage bufferSerializer
Extrys Apr 29, 2025
6419ad0
commented non generic serialization methods
Extrys Apr 29, 2025
1d746fe
Merge branch 'feat/InstantiationPayload' into develop-2.0.0
Extrys Apr 29, 2025
4005c50
Cleaning diff
Extrys Apr 29, 2025
a103e49
Cleaning diff
Extrys Apr 29, 2025
43e8ff4
Review Changes
Extrys Apr 30, 2025
0fab0a6
Some more renamigs for making it more closer to unity's naming
Extrys Apr 30, 2025
36a041c
Merge branch 'develop-2.0.0' into develop-2.0.0
Extrys May 1, 2025
fd787e8
Merge branch 'develop-2.0.0' into develop-2.0.0
Extrys May 2, 2025
96b7af6
Added buffer safety and Tests
Extrys May 5, 2025
e49cb63
Merge remote-tracking branch 'origin/develop-2.0.0' into develop-2.0.0
Extrys May 5, 2025
89eb5c4
Log text change for more clarity
Extrys May 5, 2025
adead9d
Renamed HasInstantiationPayload to HasInstantiationData to mantain na…
Extrys May 5, 2025
e4481b2
CHANGELOG.md entry added
Extrys May 5, 2025
3522fc0
Merge branch 'Unity-Technologies:develop-2.0.0' into develop-2.0.0
Extrys May 5, 2025
6757e63
Solved late-join problems
Extrys May 6, 2025
a5670e3
cleaning diff
Extrys May 6, 2025
426d82c
Updated comment
Extrys May 6, 2025
6899764
Added more Buffer/Synchronization safety changes
Extrys May 6, 2025
c1d6503
Improves handler lookup performance
Extrys May 6, 2025
096c85c
New stateless approach working flawless
Extrys May 8, 2025
7f6c9c7
Test updated
Extrys May 8, 2025
89dd4c0
Test refactor and included late joining test
Extrys May 8, 2025
b992b7c
clean diff
Extrys May 8, 2025
1f27a68
Changelog updated
Extrys May 8, 2025
5b68637
Adds comment for data handling workaround
Extrys May 9, 2025
d42bf95
Cleaning diff, fixed comment accidentally using a chinese version of …
Extrys May 9, 2025
4c7f63d
New requirements fullfilled
Extrys May 12, 2025
2b6befd
removed yet unused method
Extrys May 12, 2025
3dea6aa
extending the interface to directly allow different injection patterns
Extrys May 12, 2025
015e384
Merge branch 'develop-2.0.0' into develop-2.0.0
Extrys May 14, 2025
536642b
Pure stateless, non generic approach for sending data through fastBuf…
Extrys May 16, 2025
fa774e7
Merge branch 'develop-2.0.0' into develop-2.0.0
Extrys May 16, 2025
0d62ea4
renamings from Inject to Set
Extrys May 16, 2025
3fad66c
Inlined functions and removed terniary call
Extrys May 24, 2025
81d574a
Changed instantiation data throw by an error log
Extrys May 24, 2025
36ec35f
Abstract class approach, removed wrapper
Extrys May 24, 2025
7326a83
Removed the BufferSerializer From the GetInstantiationDataReader method
Extrys May 24, 2025
e8a661c
Using Approach A (unsafe block) to avoid allocations for FastBufferRe…
Extrys May 28, 2025
f47f3d4
Most review comments addressed
Extrys May 28, 2025
6b3963f
Renamed Interface in the Changelog
Extrys May 29, 2025
fc43642
Update formatting and use NetcodeIntegrationTest for test class
EmandM May 30, 2025
729f4d6
fixed late join bug
Extrys May 30, 2025
fea9d7d
Merge branch 'develop-2.0.0' into develop-2.0.0
Extrys May 30, 2025
900c7ee
Using BytePacker utilities to optimize the size of the serialized int…
Extrys May 30, 2025
886697e
Updated comment to match new interface name
Extrys Jun 6, 2025
4586a6e
Merge branch 'develop-2.0.0' into develop-2.0.0
Extrys Jun 6, 2025
a34ec5a
Merge remote-tracking branch 'extrys/develop-2.0.0' into feat/sync-in…
EmandM Jun 10, 2025
0d51492
Rework serialization to work with distributed authority
EmandM Jun 11, 2025
050e150
Merge branch 'develop-2.0.0' of https://github.com/Unity-Technologies…
EmandM Jun 11, 2025
a364c99
Add missing code doc
EmandM Jun 11, 2025
1b37f13
Add typeparam
EmandM Jun 11, 2025
e944ede
Add missing documentation to NetworkPrefabInstanceHandlerWithData
EmandM Jun 11, 2025
efa756c
Handle null response from Instatiate call with data
EmandM Jun 12, 2025
8ba2043
Add PrefabHandler shutdown
EmandM Jun 12, 2025
4f79aa2
Merge branch 'develop-2.0.0' into feat/sync-instantiation-data
NoelStephensUnity Jun 12, 2025
19b07a3
Merge branch 'develop-2.0.0' of https://github.com/Unity-Technologies…
EmandM Jun 13, 2025
61474f3
Merge branch 'feat/sync-instantiation-data' of https://github.com/Uni…
EmandM Jun 13, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions com.unity.netcode.gameobjects/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ Additional documentation and release notes are available at [Multiplayer Documen

- Added `SinglePlayerTransport` that provides the ability to start as a host for a single player network session. (#3473)
- 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)
- Added `NetworkPrefabInstanceHandlerWithData<T>`, a variant of `INetworkPrefabInstanceHandler` that provides access to custom instantiation data directly within the `Instantiate()` method. (#3430)

### Fixed

Expand Down
133 changes: 84 additions & 49 deletions com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ public uint PrefabIdHash
}
}

/// <summary>
/// InstantiationData sent during the instantiation process.
/// Available to read as T parameter to <see cref="NetworkPrefabInstanceHandlerWithData{T}.Instantiate(ulong, Vector3, Quaternion, T)"/> for custom handling by user code.
/// </summary>
internal byte[] InstantiationData;

/// <summary>
/// All <see cref="NetworkTransform"/> component instances associated with a <see cref="NetworkObject"/> component instance.
/// </summary>
Expand Down Expand Up @@ -2857,6 +2863,12 @@ public bool SpawnWithObservers
set => ByteUtility.SetBit(ref m_BitField, 10, value);
}

public bool HasInstantiationData
{
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;
Expand Down Expand Up @@ -2884,6 +2896,7 @@ public struct TransformData : INetworkSerializeByMemcpy

public int NetworkSceneHandle;

internal int SynchronizationDataSize;

public void Serialize(FastBufferWriter writer)
{
Expand Down Expand Up @@ -2945,9 +2958,29 @@ public void Serialize(FastBufferWriter writer)
writer.WriteValue(OwnerObject.GetSceneOriginHandle());
}

// write placeholder for serialized data size.
// Can't be bitpacked because we don't know the value until we calculate it later
var positionBeforeSynchronizing = writer.Position;
writer.WriteValueSafe(0);
var sizeToSkipCalculationPosition = writer.Position;

if (HasInstantiationData)
{
writer.WriteValueSafe(OwnerObject.InstantiationData);
}

// Synchronize NetworkVariables and NetworkBehaviours
var bufferSerializer = new BufferSerializer<BufferSerializerWriter>(new BufferSerializerWriter(writer));
OwnerObject.SynchronizeNetworkBehaviours(ref bufferSerializer, TargetClientId);

var currentPosition = writer.Position;
// Write the total number of bytes written for synchronization data.
writer.Seek(positionBeforeSynchronizing);
// We want the size of everything after our size to skip calculation position
var size = currentPosition - sizeToSkipCalculationPosition;
writer.WriteValueSafe(size);
// seek back to the head of the writer.
writer.Seek(currentPosition);
}

public void Deserialize(FastBufferReader reader)
Expand Down Expand Up @@ -3003,6 +3036,10 @@ public void Deserialize(FastBufferReader reader)
// The NetworkSceneHandle is the server-side relative
// scene handle that the NetworkObject resides in.
reader.ReadValue(out NetworkSceneHandle);

// Read the size of the remaining synchronization data
// This data will be read in AddSceneObject()
reader.ReadValueSafe(out SynchronizationDataSize);
}
}

Expand All @@ -3017,12 +3054,7 @@ internal void SynchronizeNetworkBehaviours<T>(ref BufferSerializer<T> serializer
{
if (serializer.IsWriter)
{
// write placeholder int.
// Can't be bitpacked because we don't know the value until we calculate it later
var writer = serializer.GetFastBufferWriter();
var positionBeforeSynchronizing = writer.Position;
writer.WriteValueSafe(0);
var sizeToSkipCalculationPosition = writer.Position;

// Synchronize NetworkVariables
foreach (var behavior in ChildNetworkBehaviours)
Expand All @@ -3048,12 +3080,6 @@ internal void SynchronizeNetworkBehaviours<T>(ref BufferSerializer<T> serializer
}

var currentPosition = writer.Position;
// Write the total number of bytes written for NetworkVariable and NetworkBehaviour
// synchronization.
writer.Seek(positionBeforeSynchronizing);
// We want the size of everything after our size to skip calculation position
var size = currentPosition - sizeToSkipCalculationPosition;
writer.WriteValueSafe(size);
// Write the number of NetworkBehaviours synchronized
writer.Seek(networkBehaviourCountPosition);
writer.WriteValueSafe(synchronizationCount);
Expand All @@ -3063,41 +3089,26 @@ internal void SynchronizeNetworkBehaviours<T>(ref BufferSerializer<T> serializer
}
else
{
var seekToEndOfSynchData = 0;
var reader = serializer.GetFastBufferReader();
try
{
reader.ReadValueSafe(out int sizeOfSynchronizationData);
seekToEndOfSynchData = reader.Position + sizeOfSynchronizationData;

// Apply the network variable synchronization data
foreach (var behaviour in ChildNetworkBehaviours)
{
behaviour.InitializeVariables();
behaviour.SetNetworkVariableData(reader, targetClientId);
}

// Read the number of NetworkBehaviours to synchronize
reader.ReadValueSafe(out byte numberSynchronized);
// Apply the network variable synchronization data
foreach (var behaviour in ChildNetworkBehaviours)
{
behaviour.InitializeVariables();
behaviour.SetNetworkVariableData(reader, targetClientId);
}

// If a NetworkBehaviour writes synchronization data, it will first
// write its NetworkBehaviourId so when deserializing the client-side
// can find the right NetworkBehaviour to deserialize the synchronization data.
for (int i = 0; i < numberSynchronized; i++)
{
reader.ReadValueSafe(out ushort networkBehaviourId);
var networkBehaviour = GetNetworkBehaviourAtOrderIndex(networkBehaviourId);
networkBehaviour.Synchronize(ref serializer, targetClientId);
}
// Read the number of NetworkBehaviours to synchronize
reader.ReadValueSafe(out byte numberSynchronized);

if (seekToEndOfSynchData != reader.Position)
{
Debug.LogWarning($"[Size mismatch] Expected: {seekToEndOfSynchData} Currently At: {reader.Position}!");
}
}
catch
// If a NetworkBehaviour writes synchronization data, it will first
// write its NetworkBehaviourId so when deserializing the client-side
// can find the right NetworkBehaviour to deserialize the synchronization data.
for (int i = 0; i < numberSynchronized; i++)
{
reader.Seek(seekToEndOfSynchData);
reader.ReadValueSafe(out ushort networkBehaviourId);
var networkBehaviour = GetNetworkBehaviourAtOrderIndex(networkBehaviourId);
networkBehaviour.Synchronize(ref serializer, targetClientId);
}
}
}
Expand All @@ -3121,7 +3132,8 @@ internal SceneObject GetMessageSceneObject(ulong targetClientId = NetworkManager
NetworkSceneHandle = NetworkSceneHandle,
Hash = CheckForGlobalObjectIdHashOverride(),
OwnerObject = this,
TargetClientId = targetClientId
TargetClientId = targetClientId,
HasInstantiationData = InstantiationData != null && InstantiationData.Length > 0
};

// Handle Parenting
Expand Down Expand Up @@ -3186,8 +3198,18 @@ internal SceneObject GetMessageSceneObject(ulong targetClientId = NetworkManager
/// <returns>The deserialized NetworkObject or null if deserialization failed</returns>
internal static NetworkObject AddSceneObject(in SceneObject sceneObject, FastBufferReader reader, NetworkManager networkManager, bool invokedByMessage = false)
{
//Attempt to create a local NetworkObject
var networkObject = networkManager.SpawnManager.CreateLocalNetworkObject(sceneObject);
var endOfSynchronizationData = reader.Position + sceneObject.SynchronizationDataSize;

byte[] instantiationData = null;
if (sceneObject.HasInstantiationData)
{
reader.ReadValueSafe(out instantiationData);
}


// Attempt to create a local NetworkObject
var networkObject = networkManager.SpawnManager.CreateLocalNetworkObject(sceneObject, instantiationData);


if (networkObject == null)
{
Expand All @@ -3200,8 +3222,7 @@ internal static NetworkObject AddSceneObject(in SceneObject sceneObject, FastBuf
try
{
// If we failed to load this NetworkObject, then skip past the Network Variable and (if any) synchronization data
reader.ReadValueSafe(out int networkBehaviourSynchronizationDataLength);
reader.Seek(reader.Position + networkBehaviourSynchronizationDataLength);
reader.Seek(endOfSynchronizationData);
}
catch (Exception ex)
{
Expand All @@ -3219,9 +3240,23 @@ internal static NetworkObject AddSceneObject(in SceneObject sceneObject, FastBuf
// Special Case: Invoke NetworkBehaviour.OnPreSpawn methods here before SynchronizeNetworkBehaviours
networkObject.InvokeBehaviourNetworkPreSpawn();

// Synchronize NetworkBehaviours
var bufferSerializer = new BufferSerializer<BufferSerializerReader>(new BufferSerializerReader(reader));
networkObject.SynchronizeNetworkBehaviours(ref bufferSerializer, networkManager.LocalClientId);
// Process the remaining synchronization data from the buffer
try
{
// Synchronize NetworkBehaviours
var bufferSerializer = new BufferSerializer<BufferSerializerReader>(new BufferSerializerReader(reader));
networkObject.SynchronizeNetworkBehaviours(ref bufferSerializer, networkManager.LocalClientId);

if (reader.Position != endOfSynchronizationData)
{
Debug.LogWarning($"[Size mismatch] Expected: {endOfSynchronizationData} Currently At: {reader.Position}!");
reader.Seek(endOfSynchronizationData);
}
}
catch
{
reader.Seek(endOfSynchronizationData);
}

// If we are an in-scene placed NetworkObject and we originally had a parent but when synchronized we are
// being told we do not have a parent, then we want to clear the latest parent so it is not automatically
Expand Down
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like that we moved this into its own file.
👍

Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
using System.Collections.Generic;
using UnityEngine;

namespace Unity.Netcode
{
/// <summary>
/// Interface for customizing, overriding, spawning, and destroying Network Prefabs
/// Used by <see cref="NetworkPrefabHandler"/>
/// </summary>
public interface INetworkPrefabInstanceHandler
{
/// <summary>
/// Client Side Only
/// Once an implementation is registered with the <see cref="NetworkPrefabHandler"/>, this method will be called every time
/// a Network Prefab associated <see cref="NetworkObject"/> is spawned on clients
///
/// Note On Hosts: Use the <see cref="NetworkPrefabHandler.RegisterHostGlobalObjectIdHashValues(GameObject, List{T})"/>
/// method to register all targeted NetworkPrefab overrides manually since the host will be acting as both a server and client.
///
/// Note on Pooling: If you are using a NetworkObject pool, don't forget to make the NetworkObject active
/// via the <see cref="GameObject.SetActive(bool)"/> method.
/// </summary>
/// <remarks>
/// 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 <see cref="NetworkPrefabInstanceHandlerWithData{T}"/> instead.
/// </remarks>
/// <param name="ownerClientId">the owner for the <see cref="NetworkObject"/> to be instantiated</param>
/// <param name="position">the initial/default position for the <see cref="NetworkObject"/> to be instantiated</param>
/// <param name="rotation">the initial/default rotation for the <see cref="NetworkObject"/> to be instantiated</param>
/// <returns>The instantiated NetworkObject instance. Returns null if instantiation fails.</returns>
public NetworkObject Instantiate(ulong ownerClientId, Vector3 position, Quaternion rotation);

/// <summary>
/// Invoked on Client and Server
/// Once an implementation is registered with the <see cref="NetworkPrefabHandler"/>, this method will be called when
/// a Network Prefab associated <see cref="NetworkObject"/> is:
///
/// Server Side: destroyed or despawned with the destroy parameter equal to true
/// If <see cref="NetworkObject.Despawn(bool)"/> is invoked with the default destroy parameter (i.e. false) then this method will NOT be invoked!
///
/// Client Side: destroyed when the client receives a destroy object message from the server or host.
///
/// Note on Pooling: When this method is invoked, you do not need to destroy the NetworkObject as long as you want your pool to persist.
/// The most common approach is to make the <see cref="NetworkObject"/> inactive by calling <see cref="GameObject.SetActive(bool)"/>.
/// </summary>
/// <param name="networkObject">The <see cref="NetworkObject"/> being destroyed</param>
public void Destroy(NetworkObject networkObject);
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading