1 Commits

Author SHA1 Message Date
Unity Technologies
a813ba0dd6 com.unity.netcode.gameobjects@2.0.0-pre.3
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com).

## [2.0.0-pre.3] - 2024-07-23

### Added
- Added: `UnityTransport.GetNetworkDriver` and `UnityTransport.GetLocalEndpoint` methods to expose the driver and local endpoint being used. (#2978)

### Fixed

- Fixed issue where deferred despawn was causing GC allocations when converting an `IEnumerable` to a list. (#2983)
- Fixed issue where the realtime network stats monitor was not able to display RPC traffic in release builds due to those stats being only available in development builds or the editor. (#2979)
- Fixed issue where `NetworkManager.ScenesLoaded` was not being updated if `PostSynchronizationSceneUnloading` was set and any loaded scenes not used during synchronization were unloaded. (#2971)
- Fixed issue where `Rigidbody2d` under Unity 6000.0.11f1 has breaking changes where `velocity` is now `linearVelocity` and `isKinematic` is replaced by `bodyType`. (#2971)
- Fixed issue where `NetworkSpawnManager.InstantiateAndSpawn` and `NetworkObject.InstantiateAndSpawn` were not honoring the ownerClientId parameter when using a client-server network topology. (#2968)
- Fixed issue where internal delta serialization could not have a byte serializer defined when serializing deltas for other types. Added `[GenerateSerializationForType(typeof(byte))]` to both the `NetworkVariable` and `AnticipatedNetworkVariable` classes to assure a byte serializer is defined.(#2962)
- Fixed issue when scene management was disabled and the session owner would still try to synchronize a late joining client. (#2962)
- Fixed issue when using a distributed authority network topology where it would allow a session owner to spawn a `NetworkObject` prior to being approved. Now, an error message is logged and the `NetworkObject` will not be spawned prior to the client being approved.  (#2962)
- Fixed issue where attempting to spawn during `NetworkBehaviour.OnInSceneObjectsSpawned` and `NetworkBehaviour.OnNetworkSessionSynchronized` notifications would throw a collection modified exception.  (#2962)

### Changed

- Changed logic where clients can now set the `NetworkSceneManager` client synchronization mode when using a distributed authority network topology. (#2985)
2024-07-23 00:00:00 +00:00
42 changed files with 910 additions and 308 deletions

View File

@@ -6,6 +6,28 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com). Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com).
## [2.0.0-pre.3] - 2024-07-23
### Added
- Added: `UnityTransport.GetNetworkDriver` and `UnityTransport.GetLocalEndpoint` methods to expose the driver and local endpoint being used. (#2978)
### Fixed
- Fixed issue where deferred despawn was causing GC allocations when converting an `IEnumerable` to a list. (#2983)
- Fixed issue where the realtime network stats monitor was not able to display RPC traffic in release builds due to those stats being only available in development builds or the editor. (#2979)
- Fixed issue where `NetworkManager.ScenesLoaded` was not being updated if `PostSynchronizationSceneUnloading` was set and any loaded scenes not used during synchronization were unloaded. (#2971)
- Fixed issue where `Rigidbody2d` under Unity 6000.0.11f1 has breaking changes where `velocity` is now `linearVelocity` and `isKinematic` is replaced by `bodyType`. (#2971)
- Fixed issue where `NetworkSpawnManager.InstantiateAndSpawn` and `NetworkObject.InstantiateAndSpawn` were not honoring the ownerClientId parameter when using a client-server network topology. (#2968)
- Fixed issue where internal delta serialization could not have a byte serializer defined when serializing deltas for other types. Added `[GenerateSerializationForType(typeof(byte))]` to both the `NetworkVariable` and `AnticipatedNetworkVariable` classes to assure a byte serializer is defined.(#2962)
- Fixed issue when scene management was disabled and the session owner would still try to synchronize a late joining client. (#2962)
- Fixed issue when using a distributed authority network topology where it would allow a session owner to spawn a `NetworkObject` prior to being approved. Now, an error message is logged and the `NetworkObject` will not be spawned prior to the client being approved. (#2962)
- Fixed issue where attempting to spawn during `NetworkBehaviour.OnInSceneObjectsSpawned` and `NetworkBehaviour.OnNetworkSessionSynchronized` notifications would throw a collection modified exception. (#2962)
### Changed
- Changed logic where clients can now set the `NetworkSceneManager` client synchronization mode when using a distributed authority network topology. (#2985)
## [2.0.0-pre.2] - 2024-06-17 ## [2.0.0-pre.2] - 2024-06-17
### Added ### Added

View File

@@ -360,6 +360,10 @@ namespace Unity.Netcode.Components
public override void OnNetworkSpawn() public override void OnNetworkSpawn()
{ {
if (NetworkManager.DistributedAuthorityMode)
{
Debug.LogWarning($"This component is not currently supported in distributed authority.");
}
base.OnNetworkSpawn(); base.OnNetworkSpawn();
m_OutstandingAuthorityChange = true; m_OutstandingAuthorityChange = true;
ApplyAuthoritativeState(); ApplyAuthoritativeState();

View File

@@ -8,7 +8,7 @@ namespace Unity.Netcode.Components
/// Half float precision <see cref="Vector3"/>. /// Half float precision <see cref="Vector3"/>.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// The Vector3T<ushort> values are half float values returned by <see cref="Mathf.FloatToHalf(float)"/> for each /// The Vector3T&lt;ushort&gt; values are half float values returned by <see cref="Mathf.FloatToHalf(float)"/> for each
/// individual axis and the 16 bits of the half float are stored as <see cref="ushort"/> values since C# does not have /// individual axis and the 16 bits of the half float are stored as <see cref="ushort"/> values since C# does not have
/// a half float type. /// a half float type.
/// </remarks> /// </remarks>

View File

@@ -8,7 +8,7 @@ namespace Unity.Netcode.Components
/// Half Precision <see cref="Vector4"/> that can also be used to convert a <see cref="Quaternion"/> to half precision. /// Half Precision <see cref="Vector4"/> that can also be used to convert a <see cref="Quaternion"/> to half precision.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// The Vector4T<ushort> values are half float values returned by <see cref="Mathf.FloatToHalf(float)"/> for each /// The Vector4T&lt;ushort&gt; values are half float values returned by <see cref="Mathf.FloatToHalf(float)"/> for each
/// individual axis and the 16 bits of the half float are stored as <see cref="ushort"/> values since C# does not have /// individual axis and the 16 bits of the half float are stored as <see cref="ushort"/> values since C# does not have
/// a half float type. /// a half float type.
/// </remarks> /// </remarks>

View File

@@ -5,7 +5,7 @@ using UnityEngine;
namespace Unity.Netcode namespace Unity.Netcode
{ {
/// <summary> /// <summary>
/// Solves for incoming values that are jittered /// Solves for incoming values that are jittered.
/// Partially solves for message loss. Unclamped lerping helps hide this, but not completely /// Partially solves for message loss. Unclamped lerping helps hide this, but not completely
/// </summary> /// </summary>
/// <typeparam name="T">The type of interpolated value</typeparam> /// <typeparam name="T">The type of interpolated value</typeparam>

View File

@@ -177,7 +177,11 @@ namespace Unity.Netcode.Components
{ {
if (m_IsRigidbody2D) if (m_IsRigidbody2D)
{ {
#if COM_UNITY_MODULES_PHYSICS2D_LINEAR
m_Rigidbody2D.linearVelocity = linearVelocity;
#else
m_Rigidbody2D.velocity = linearVelocity; m_Rigidbody2D.velocity = linearVelocity;
#endif
} }
else else
{ {
@@ -197,7 +201,11 @@ namespace Unity.Netcode.Components
{ {
if (m_IsRigidbody2D) if (m_IsRigidbody2D)
{ {
#if COM_UNITY_MODULES_PHYSICS2D_LINEAR
return m_Rigidbody2D.linearVelocity;
#else
return m_Rigidbody2D.velocity; return m_Rigidbody2D.velocity;
#endif
} }
else else
{ {
@@ -238,7 +246,7 @@ namespace Unity.Netcode.Components
{ {
if (m_IsRigidbody2D) if (m_IsRigidbody2D)
{ {
return Vector3.forward * m_Rigidbody2D.velocity; return Vector3.forward * m_Rigidbody2D.angularVelocity;
} }
else else
{ {
@@ -481,7 +489,7 @@ namespace Unity.Netcode.Components
{ {
if (m_IsRigidbody2D) if (m_IsRigidbody2D)
{ {
return m_Rigidbody2D.isKinematic; return m_Rigidbody2D.bodyType == RigidbodyType2D.Kinematic;
} }
else else
{ {
@@ -510,7 +518,7 @@ namespace Unity.Netcode.Components
{ {
if (m_IsRigidbody2D) if (m_IsRigidbody2D)
{ {
m_Rigidbody2D.isKinematic = isKinematic; m_Rigidbody2D.bodyType = isKinematic ? RigidbodyType2D.Kinematic : RigidbodyType2D.Dynamic;
} }
else else
{ {
@@ -715,7 +723,11 @@ namespace Unity.Netcode.Components
if (zeroVelocity) if (zeroVelocity)
{ {
#if COM_UNITY_MODULES_PHYSICS2D_LINEAR
m_Rigidbody2D.linearVelocity = Vector2.zero;
#else
m_Rigidbody2D.velocity = Vector2.zero; m_Rigidbody2D.velocity = Vector2.zero;
#endif
m_Rigidbody2D.angularVelocity = 0.0f; m_Rigidbody2D.angularVelocity = 0.0f;
} }

View File

@@ -3535,7 +3535,7 @@ namespace Unity.Netcode.Components
/// </summary> /// </summary>
private void UpdateTransformState() private void UpdateTransformState()
{ {
if (m_CachedNetworkManager.ShutdownInProgress || (m_CachedNetworkManager.DistributedAuthorityMode && m_CachedNetworkObject.Observers.Count - 1 == 0)) if (m_CachedNetworkManager.ShutdownInProgress || (m_CachedNetworkManager.DistributedAuthorityMode && !m_CachedNetworkManager.CMBServiceConnection && m_CachedNetworkObject.Observers.Count - 1 == 0))
{ {
return; return;
} }

View File

@@ -159,9 +159,21 @@ namespace Unity.Netcode
public bool AutoSpawnPlayerPrefabClientSide = true; public bool AutoSpawnPlayerPrefabClientSide = true;
#if MULTIPLAYER_TOOLS #if MULTIPLAYER_TOOLS
/// <summary>
/// Controls whether network messaging metrics will be gathered. (defaults to true)
/// There is a slight performance cost to having this enabled, and can increase in processing time based on network message traffic.
/// </summary>
/// <remarks>
/// The Realtime Network Stats Monitoring tool requires this to be enabled.
/// </remarks>
[Tooltip("Enable (default) if you want to gather messaging metrics. Realtime Network Stats Monitor requires this to be enabled. Disabling this can improve performance in release builds.")]
public bool NetworkMessageMetrics = true; public bool NetworkMessageMetrics = true;
#endif #endif
/// <summary>
/// When enabled (default, this enables network profiling information. This does come with a per message processing cost.
/// Network profiling information is automatically disabled in release builds.
/// </summary>
[Tooltip("Enable (default) if you want to profile network messages with development builds and defaults to being disabled in release builds. When disabled, network messaging profiling will be disabled in development builds.")]
public bool NetworkProfilingMetrics = true; public bool NetworkProfilingMetrics = true;
/// <summary> /// <summary>

View File

@@ -15,7 +15,7 @@ namespace Unity.Netcode
} }
/// <summary> /// <summary>
/// The base class to override to write network code. Inherits MonoBehaviour /// The base class to override to write network code. Inherits MonoBehaviour.
/// </summary> /// </summary>
public abstract class NetworkBehaviour : MonoBehaviour public abstract class NetworkBehaviour : MonoBehaviour
{ {
@@ -27,7 +27,7 @@ namespace Unity.Netcode
// RuntimeAccessModifiersILPP will make this `public` // RuntimeAccessModifiersILPP will make this `public`
internal static readonly Dictionary<Type, Dictionary<uint, RpcReceiveHandler>> __rpc_func_table = new Dictionary<Type, Dictionary<uint, RpcReceiveHandler>>(); internal static readonly Dictionary<Type, Dictionary<uint, RpcReceiveHandler>> __rpc_func_table = new Dictionary<Type, Dictionary<uint, RpcReceiveHandler>>();
#if DEVELOPMENT_BUILD || UNITY_EDITOR #if DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE
// RuntimeAccessModifiersILPP will make this `public` // RuntimeAccessModifiersILPP will make this `public`
internal static readonly Dictionary<Type, Dictionary<uint, string>> __rpc_name_table = new Dictionary<Type, Dictionary<uint, string>>(); internal static readonly Dictionary<Type, Dictionary<uint, string>> __rpc_name_table = new Dictionary<Type, Dictionary<uint, string>>();
#endif #endif
@@ -124,7 +124,7 @@ namespace Unity.Netcode
} }
bufferWriter.Dispose(); bufferWriter.Dispose();
#if DEVELOPMENT_BUILD || UNITY_EDITOR #if DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE
if (__rpc_name_table[GetType()].TryGetValue(rpcMethodId, out var rpcMethodName)) if (__rpc_name_table[GetType()].TryGetValue(rpcMethodId, out var rpcMethodName))
{ {
NetworkManager.NetworkMetrics.TrackRpcSent( NetworkManager.NetworkMetrics.TrackRpcSent(
@@ -252,7 +252,7 @@ namespace Unity.Netcode
} }
bufferWriter.Dispose(); bufferWriter.Dispose();
#if DEVELOPMENT_BUILD || UNITY_EDITOR #if DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE
if (__rpc_name_table[GetType()].TryGetValue(rpcMethodId, out var rpcMethodName)) if (__rpc_name_table[GetType()].TryGetValue(rpcMethodId, out var rpcMethodName))
{ {
if (clientRpcParams.Send.TargetClientIds != null) if (clientRpcParams.Send.TargetClientIds != null)
@@ -410,8 +410,8 @@ namespace Unity.Netcode
} }
/// <summary> /// <summary>
/// Gets the NetworkManager that owns this NetworkBehaviour instance /// Gets the NetworkManager that owns this NetworkBehaviour instance.
/// See note around `NetworkObject` for how there is a chicken / egg problem when we are not initialized /// See `NetworkObject` note for how there is a chicken/egg problem when not initialized.
/// </summary> /// </summary>
public NetworkManager NetworkManager public NetworkManager NetworkManager
{ {
@@ -439,38 +439,40 @@ namespace Unity.Netcode
/// <see cref="Unity.Netcode.RpcTarget.Not(NativeArray{ulong})"/>, /// <see cref="Unity.Netcode.RpcTarget.Not(NativeArray{ulong})"/>,
/// <see cref="Unity.Netcode.RpcTarget.Not(NativeList{ulong})"/>, /// <see cref="Unity.Netcode.RpcTarget.Not(NativeList{ulong})"/>,
/// <see cref="Unity.Netcode.RpcTarget.Not(ulong[])"/>, and /// <see cref="Unity.Netcode.RpcTarget.Not(ulong[])"/>, and
/// <see cref="Unity.Netcode.RpcTarget.Not{T}(T)"/> /// <see cref="Unity.Netcode.RpcTarget.Not{T}(T)"/>.
/// </summary> /// </summary>
#pragma warning restore IDE0001 #pragma warning restore IDE0001
public RpcTarget RpcTarget => NetworkManager.RpcTarget; public RpcTarget RpcTarget => NetworkManager.RpcTarget;
/// <summary> /// <summary>
/// If a NetworkObject is assigned, it will return whether or not this NetworkObject /// If a NetworkObject is assigned, returns whether the NetworkObject
/// is the local player object. If no NetworkObject is assigned it will always return false. /// is the local player object. If no NetworkObject is assigned, returns false.
/// </summary> /// </summary>
public bool IsLocalPlayer { get; private set; } public bool IsLocalPlayer { get; private set; }
/// <summary> /// <summary>
/// Gets if the object is owned by the local player or if the object is the local player object /// Gets whether the object is owned by the local player or if the object is the local player object.
/// </summary> /// </summary>
public bool IsOwner { get; internal set; } public bool IsOwner { get; internal set; }
/// <summary> /// <summary>
/// Gets if we are executing as server /// Gets whether executing as a server.
/// </summary> /// </summary>
public bool IsServer { get; private set; } public bool IsServer { get; private set; }
/// <summary> /// <summary>
/// Determines if the local client has authority over the associated NetworkObject /// Determines if the local client has authority over the associated NetworkObject.
/// Client-Server: This will return true if IsServer or IsHost /// <list type="bullet">
/// Distributed Authority: This will return true if IsOwner /// <item>In client-server contexts: returns true if `IsServer` or `IsHost`.</item>
/// <item>In distributed authority contexts: returns true if `IsOwner`.</item>
/// </list>
/// </summary> /// </summary>
public bool HasAuthority { get; internal set; } public bool HasAuthority { get; internal set; }
internal NetworkClient LocalClient { get; private set; } internal NetworkClient LocalClient { get; private set; }
/// <summary> /// <summary>
/// Gets if the client is the distributed authority mode session owner /// Gets whether the client is the distributed authority mode session owner.
/// </summary> /// </summary>
public bool IsSessionOwner public bool IsSessionOwner
{ {
@@ -486,29 +488,29 @@ namespace Unity.Netcode
} }
/// <summary> /// <summary>
/// Gets if the server (local or remote) is a host - i.e., also a client /// Gets whether the server (local or remote) is a host.
/// </summary> /// </summary>
public bool ServerIsHost { get; private set; } public bool ServerIsHost { get; private set; }
/// <summary> /// <summary>
/// Gets if we are executing as client /// Gets whether executing as a client.
/// </summary> /// </summary>
public bool IsClient { get; private set; } public bool IsClient { get; private set; }
/// <summary> /// <summary>
/// Gets if we are executing as Host, I.E Server and Client /// Gets whether executing as a host (both server and client).
/// </summary> /// </summary>
public bool IsHost { get; private set; } public bool IsHost { get; private set; }
/// <summary> /// <summary>
/// Gets Whether or not the object has a owner /// Gets whether the object has an owner.
/// </summary> /// </summary>
public bool IsOwnedByServer { get; internal set; } public bool IsOwnedByServer { get; internal set; }
/// <summary> /// <summary>
/// Used to determine if it is safe to access NetworkObject and NetworkManager from within a NetworkBehaviour component /// Determines whether it's safe to access a NetworkObject and NetworkManager from within a NetworkBehaviour component.
/// Primarily useful when checking NetworkObject/NetworkManager properties within FixedUpate /// Primarily useful when checking NetworkObject or NetworkManager properties within FixedUpate.
/// </summary> /// </summary>
public bool IsSpawned { get; internal set; } public bool IsSpawned { get; internal set; }
@@ -528,7 +530,7 @@ namespace Unity.Netcode
/// the warning below. This is why IsBehaviourEditable had to be created. Matt was going to re-do /// the warning below. This is why IsBehaviourEditable had to be created. Matt was going to re-do
/// how NetworkObject works but it was close to the release and too risky to change /// how NetworkObject works but it was close to the release and too risky to change
/// <summary> /// <summary>
/// Gets the NetworkObject that owns this NetworkBehaviour instance /// Gets the NetworkObject that owns this NetworkBehaviour instance.
/// </summary> /// </summary>
public NetworkObject NetworkObject public NetworkObject NetworkObject
{ {
@@ -567,19 +569,19 @@ namespace Unity.Netcode
} }
/// <summary> /// <summary>
/// Gets whether or not this NetworkBehaviour instance has a NetworkObject owner. /// Gets whether this NetworkBehaviour instance has a NetworkObject owner.
/// </summary> /// </summary>
public bool HasNetworkObject => NetworkObject != null; public bool HasNetworkObject => NetworkObject != null;
private NetworkObject m_NetworkObject = null; private NetworkObject m_NetworkObject = null;
/// <summary> /// <summary>
/// Gets the NetworkId of the NetworkObject that owns this NetworkBehaviour /// Gets the NetworkId of the NetworkObject that owns this NetworkBehaviour instance.
/// </summary> /// </summary>
public ulong NetworkObjectId { get; internal set; } public ulong NetworkObjectId { get; internal set; }
/// <summary> /// <summary>
/// Gets NetworkId for this NetworkBehaviour from the owner NetworkObject /// Gets NetworkId for this NetworkBehaviour from the owner NetworkObject.
/// </summary> /// </summary>
public ushort NetworkBehaviourId { get; internal set; } public ushort NetworkBehaviourId { get; internal set; }
@@ -589,7 +591,7 @@ namespace Unity.Netcode
internal ushort NetworkBehaviourIdCache = 0; internal ushort NetworkBehaviourIdCache = 0;
/// <summary> /// <summary>
/// Returns a the NetworkBehaviour with a given BehaviourId for the current NetworkObject /// Returns the NetworkBehaviour with a given BehaviourId for the current NetworkObject.
/// </summary> /// </summary>
/// <param name="behaviourId">The behaviourId to return</param> /// <param name="behaviourId">The behaviourId to return</param>
/// <returns>Returns NetworkBehaviour with given behaviourId</returns> /// <returns>Returns NetworkBehaviour with given behaviourId</returns>
@@ -599,7 +601,7 @@ namespace Unity.Netcode
} }
/// <summary> /// <summary>
/// Gets the ClientId that owns the NetworkObject /// Gets the ClientId that owns this NetworkObject.
/// </summary> /// </summary>
public ulong OwnerClientId { get; internal set; } public ulong OwnerClientId { get; internal set; }
@@ -651,28 +653,29 @@ namespace Unity.Netcode
} }
/// <summary> /// <summary>
/// Distributed Authority Mode Only /// Only for use in distributed authority mode.
/// Invoked only on the authority instance when a <see cref="NetworkObject"/> is deferring its despawn on non-authoritative instances. /// Invoked only on the authority instance when a <see cref="NetworkObject"/> is deferring its despawn on non-authoritative instances.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// See also: <see cref="NetworkObject.DeferDespawn(int, bool)"/> /// See also: <see cref="NetworkObject.DeferDespawn(int, bool)"/>
/// </remarks> /// </remarks>
/// <param name="despawnTick">the future network tick that the <see cref="NetworkObject"/> will be despawned on non-authoritative instances</param> /// <param name="despawnTick">The future network tick that the <see cref="NetworkObject"/> will be despawned on non-authoritative instances</param>
public virtual void OnDeferringDespawn(int despawnTick) { } public virtual void OnDeferringDespawn(int despawnTick) { }
/// <summary>
/// Gets called after the <see cref="NetworkObject"/> is spawned. No NetworkBehaviours associated with the NetworkObject will have had <see cref="OnNetworkSpawn"/> invoked yet. /// Gets called after the <see cref="NetworkObject"/> is spawned. No NetworkBehaviours associated with the NetworkObject will have had <see cref="OnNetworkSpawn"/> invoked yet.
/// A reference to <see cref="NetworkManager"/> is passed in as a parameter to determine the context of execution (IsServer/IsClient) /// A reference to <see cref="NetworkManager"/> is passed in as a parameter to determine the context of execution (`IsServer` or `IsClient`).
/// </summary> /// </summary>
/// <remarks>
/// <param name="networkManager">a ref to the <see cref="NetworkManager"/> since this is not yet set on the <see cref="NetworkBehaviour"/></param> /// <param name="networkManager">a ref to the <see cref="NetworkManager"/> since this is not yet set on the <see cref="NetworkBehaviour"/></param>
/// <remarks>
/// The <see cref="NetworkBehaviour"/> will not have anything assigned to it at this point in time. /// The <see cref="NetworkBehaviour"/> will not have anything assigned to it at this point in time.
/// Settings like ownership, NetworkBehaviourId, NetworkManager, and most other spawn related properties will not be set. /// Settings like ownership, NetworkBehaviourId, NetworkManager, and most other spawn-related properties will not be set.
/// This can be used to handle things like initializing/instantiating a NetworkVariable or the like. /// This can be used to handle things like initializing a NetworkVariable.
/// </remarks> /// </remarks>
protected virtual void OnNetworkPreSpawn(ref NetworkManager networkManager) { } protected virtual void OnNetworkPreSpawn(ref NetworkManager networkManager) { }
/// <summary> /// <summary>
/// Gets called when the <see cref="NetworkObject"/> gets spawned, message handlers are ready to be registered and the network is setup. /// Gets called when the <see cref="NetworkObject"/> gets spawned, message handlers are ready to be registered, and the network is set up.
/// </summary> /// </summary>
public virtual void OnNetworkSpawn() { } public virtual void OnNetworkSpawn() { }
@@ -686,28 +689,28 @@ namespace Unity.Netcode
protected virtual void OnNetworkPostSpawn() { } protected virtual void OnNetworkPostSpawn() { }
/// <summary> /// <summary>
/// [Client-Side Only] /// This method is only available client-side.
/// When a new client joins it is synchronized with all spawned NetworkObjects and scenes loaded for the session joined. At the end of the synchronization process, when all /// When a new client joins it's synchronized with all spawned NetworkObjects and scenes loaded for the session joined. At the end of the synchronization process, when all
/// <see cref="NetworkObject"/>s and scenes (if scene management is enabled) have finished synchronizing, all NetworkBehaviour components associated with spawned <see cref="NetworkObject"/>s /// <see cref="NetworkObject"/>s and scenes (if scene management is enabled) have finished synchronizing, all NetworkBehaviour components associated with spawned <see cref="NetworkObject"/>s
/// will have this method invoked. /// will have this method invoked.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// This can be used to handle post synchronization actions where you might need to access a different NetworkObject and/or NetworkBehaviour not local to the current NetworkObject context. /// This can be used to handle post-synchronization actions where you might need to access a different NetworkObject and/or NetworkBehaviour not local to the current NetworkObject context.
/// This is only invoked on clients during a client-server network topology session. /// This is only invoked on clients during a client-server network topology session.
/// </remarks> /// </remarks>
protected virtual void OnNetworkSessionSynchronized() { } protected virtual void OnNetworkSessionSynchronized() { }
/// <summary> /// <summary>
/// [Client & Server Side] /// When a scene is loaded and in-scene placed NetworkObjects are finished spawning, this method is invoked on all of the newly spawned in-scene placed NetworkObjects.
/// When a scene is loaded an in-scene placed NetworkObjects are all spawned, this method is invoked on all of the newly spawned in-scene placed NetworkObjects. /// This method runs both client and server side.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// This can be used to handle post scene loaded actions for in-scene placed NetworkObjcts where you might need to access a different NetworkObject and/or NetworkBehaviour not local to the current NetworkObject context. /// This method can be used to handle post-scene loaded actions for in-scene placed NetworkObjcts where you might need to access a different NetworkObject and/or NetworkBehaviour not local to the current NetworkObject context.
/// </remarks> /// </remarks>
protected virtual void OnInSceneObjectsSpawned() { } protected virtual void OnInSceneObjectsSpawned() { }
/// <summary> /// <summary>
/// Gets called when the <see cref="NetworkObject"/> gets despawned. Is called both on the server and clients. /// Gets called when the <see cref="NetworkObject"/> gets despawned. This method runs both client and server side.
/// </summary> /// </summary>
public virtual void OnNetworkDespawn() { } public virtual void OnNetworkDespawn() { }
@@ -803,7 +806,7 @@ namespace Unity.Netcode
} }
/// <summary> /// <summary>
/// Gets called when the local client gains ownership of this object /// Gets called when the local client gains ownership of this object.
/// </summary> /// </summary>
public virtual void OnGainedOwnership() { } public virtual void OnGainedOwnership() { }
@@ -814,8 +817,8 @@ namespace Unity.Netcode
} }
/// <summary> /// <summary>
/// Invoked on all clients, override this method to be notified of any /// Invoked on all clients. Override this method to be notified of any
/// ownership changes (even if the instance was niether the previous or /// ownership changes (even if the instance was neither the previous or
/// newly assigned current owner). /// newly assigned current owner).
/// </summary> /// </summary>
/// <param name="previous">the previous owner</param> /// <param name="previous">the previous owner</param>
@@ -831,7 +834,7 @@ namespace Unity.Netcode
} }
/// <summary> /// <summary>
/// Gets called when we loose ownership of this object /// Gets called when ownership of this object is lost.
/// </summary> /// </summary>
public virtual void OnLostOwnership() { } public virtual void OnLostOwnership() { }
@@ -842,7 +845,7 @@ namespace Unity.Netcode
} }
/// <summary> /// <summary>
/// Gets called when the parent NetworkObject of this NetworkBehaviour's NetworkObject has changed /// Gets called when the parent NetworkObject of this NetworkBehaviour's NetworkObject has changed.
/// </summary> /// </summary>
/// <param name="parentNetworkObject">the new <see cref="NetworkObject"/> parent</param> /// <param name="parentNetworkObject">the new <see cref="NetworkObject"/> parent</param>
public virtual void OnNetworkObjectParentChanged(NetworkObject parentNetworkObject) { } public virtual void OnNetworkObjectParentChanged(NetworkObject parentNetworkObject) { }
@@ -877,7 +880,7 @@ namespace Unity.Netcode
#pragma warning restore IDE1006 // restore naming rule violation check #pragma warning restore IDE1006 // restore naming rule violation check
{ {
__rpc_func_table[GetType()][hash] = handler; __rpc_func_table[GetType()][hash] = handler;
#if DEVELOPMENT_BUILD || UNITY_EDITOR #if DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE
__rpc_name_table[GetType()][hash] = rpcMethodName; __rpc_name_table[GetType()][hash] = rpcMethodName;
#endif #endif
} }
@@ -903,7 +906,7 @@ namespace Unity.Netcode
if (!__rpc_func_table.ContainsKey(GetType())) if (!__rpc_func_table.ContainsKey(GetType()))
{ {
__rpc_func_table[GetType()] = new Dictionary<uint, RpcReceiveHandler>(); __rpc_func_table[GetType()] = new Dictionary<uint, RpcReceiveHandler>();
#if UNITY_EDITOR || DEVELOPMENT_BUILD #if UNITY_EDITOR || DEVELOPMENT_BUILD || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE
__rpc_name_table[GetType()] = new Dictionary<uint, string>(); __rpc_name_table[GetType()] = new Dictionary<uint, string>();
#endif #endif
__initializeRpcs(); __initializeRpcs();
@@ -1112,31 +1115,38 @@ namespace Unity.Netcode
/// </remarks> /// </remarks>
internal void WriteNetworkVariableData(FastBufferWriter writer, ulong targetClientId) internal void WriteNetworkVariableData(FastBufferWriter writer, ulong targetClientId)
{ {
// Create any values that require accessing the NetworkManager locally (it is expensive to access it in NetworkBehaviour)
var networkManager = NetworkManager; var networkManager = NetworkManager;
if (networkManager.DistributedAuthorityMode) var distributedAuthority = networkManager.DistributedAuthorityMode;
var ensureLengthSafety = networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety;
// Always write the NetworkVariable count even if zero for distributed authority (used by comb server)
if (distributedAuthority)
{ {
writer.WriteValueSafe((ushort)NetworkVariableFields.Count); writer.WriteValueSafe((ushort)NetworkVariableFields.Count);
} }
// Exit early if there are no NetworkVariables
if (NetworkVariableFields.Count == 0) if (NetworkVariableFields.Count == 0)
{ {
return; return;
} }
// DANGO-TODO: Made some modifications here that overlap/won't play nice with EnsureNetworkVariableLenghtSafety.
// Worth either merging or more cleanly separating these codepaths.
for (int j = 0; j < NetworkVariableFields.Count; j++) for (int j = 0; j < NetworkVariableFields.Count; j++)
{ {
// Note: In distributed authority mode, all clients can read // Client-Server: Try to write values only for clients that have read permissions.
// Distributed Authority: All clients have read permissions, always try to write the value.
if (NetworkVariableFields[j].CanClientRead(targetClientId)) if (NetworkVariableFields[j].CanClientRead(targetClientId))
{ {
if (networkManager.DistributedAuthorityMode) // Write additional NetworkVariable information when length safety is enabled or when in distributed authority mode
if (ensureLengthSafety || distributedAuthority)
{ {
writer.WriteValueSafe(NetworkVariableFields[j].Type); // Write the type being serialized for distributed authority (only for comb-server)
} if (distributedAuthority)
{
writer.WriteValueSafe(NetworkVariableFields[j].Type);
}
if (networkManager.DistributedAuthorityMode || networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
{
var writePos = writer.Position; var writePos = writer.Position;
// Note: This value can't be packed because we don't know how large it will be in advance // Note: This value can't be packed because we don't know how large it will be in advance
// we reserve space for it, then write the data, then come back and fill in the space // we reserve space for it, then write the data, then come back and fill in the space
@@ -1148,18 +1158,19 @@ namespace Unity.Netcode
NetworkVariableFields[j].WriteField(writer); NetworkVariableFields[j].WriteField(writer);
var size = writer.Position - startPos; var size = writer.Position - startPos;
writer.Seek(writePos); writer.Seek(writePos);
// Write the NetworkVariable value
writer.WriteValueSafe((ushort)size); writer.WriteValueSafe((ushort)size);
writer.Seek(startPos + size); writer.Seek(startPos + size);
} }
else else // Client-Server Only: Should only ever be invoked when using a client-server NetworkTopology
{ {
// Write the NetworkVariable value
NetworkVariableFields[j].WriteField(writer); NetworkVariableFields[j].WriteField(writer);
} }
} }
else else if (ensureLengthSafety)
{ {
// Only if EnsureNetworkVariableLengthSafety, otherwise just skip // Client-Server Only: If the client cannot read this field, then skip it but write a 0 for this NetworkVariable's position
if (networkManager.DistributedAuthorityMode || networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
{ {
writer.WriteValueSafe((ushort)0); writer.WriteValueSafe((ushort)0);
} }
@@ -1177,75 +1188,78 @@ namespace Unity.Netcode
/// </remarks> /// </remarks>
internal void SetNetworkVariableData(FastBufferReader reader, ulong clientId) internal void SetNetworkVariableData(FastBufferReader reader, ulong clientId)
{ {
// Stack cache any values that requires accessing the NetworkManager (it is expensive to access it in NetworkBehaviour)
var networkManager = NetworkManager; var networkManager = NetworkManager;
if (networkManager.DistributedAuthorityMode) var distributedAuthority = networkManager.DistributedAuthorityMode;
var ensureLengthSafety = networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety;
// Always read the NetworkVariable count when in distributed authority (sanity check if comb-server matches what client has locally)
if (distributedAuthority)
{ {
reader.ReadValueSafe(out ushort variableCount); reader.ReadValueSafe(out ushort variableCount);
if (variableCount != NetworkVariableFields.Count) if (variableCount != NetworkVariableFields.Count)
{ {
Debug.LogError("NetworkVariable count mismatch."); Debug.LogError($"[{name}][NetworkObjectId: {NetworkObjectId}][NetworkBehaviourId: {NetworkBehaviourId}] NetworkVariable count mismatch! (Read: {variableCount} vs. Expected: {NetworkVariableFields.Count})");
return; return;
} }
} }
// Exit early if nothing else to read
if (NetworkVariableFields.Count == 0) if (NetworkVariableFields.Count == 0)
{ {
return; return;
} }
// DANGO-TODO: Made some modifications here that overlap/won't play nice with EnsureNetworkVariableLenghtSafety.
// Worth either merging or more cleanly separating these codepaths.
for (int j = 0; j < NetworkVariableFields.Count; j++) for (int j = 0; j < NetworkVariableFields.Count; j++)
{ {
var varSize = (ushort)0; var varSize = (ushort)0;
var readStartPos = 0; var readStartPos = 0;
if (networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety) // Client-Server: Clients that only have read permissions will try to read the value
// Distributed Authority: All clients have read permissions, always try to read the value
if (NetworkVariableFields[j].CanClientRead(clientId))
{ {
reader.ReadValueSafe(out varSize); if (ensureLengthSafety || distributedAuthority)
if (varSize == 0)
{ {
continue; // Read the type being serialized and discard it (for now) when in a distributed authority network topology (only used by comb-server)
if (distributedAuthority)
{
reader.ReadValueSafe(out NetworkVariableType _);
}
reader.ReadValueSafe(out varSize);
if (varSize == 0)
{
Debug.LogError($"[{name}][NetworkObjectId: {NetworkObjectId}][NetworkBehaviourId: {NetworkBehaviourId}][{NetworkVariableFields[j].Name}] Expected non-zero size readable NetworkVariable! (Skipping)");
continue;
}
readStartPos = reader.Position;
} }
readStartPos = reader.Position;
} }
else // If the client cannot read this field, then skip it else // Client-Server Only: If the client cannot read this field, then skip it
if (!NetworkVariableFields[j].CanClientRead(clientId))
{ {
if (networkManager.DistributedAuthorityMode) // If skipping and length safety, then fill in a 0 size for this one spot
if (ensureLengthSafety)
{ {
reader.ReadValueSafe(out ushort size); reader.ReadValueSafe(out ushort size);
if (size != 0) if (size != 0)
{ {
Debug.LogError("Expected zero size"); Debug.LogError($"[{name}][NetworkObjectId: {NetworkObjectId}][NetworkBehaviourId: {NetworkBehaviourId}][{NetworkVariableFields[j].Name}] Expected zero size for non-readable NetworkVariable when EnsureNetworkVariableLengthSafety is enabled! (Skipping)");
} }
} }
continue; continue;
} }
if (networkManager.DistributedAuthorityMode) // Read the NetworkVarible value
{ NetworkVariableFields[j].ReadField(reader);
// Explicit setting of the NetworkVariableType is only needed for CMB Runtime
reader.ReadValueSafe(out NetworkVariableType _);
reader.ReadValueSafe(out ushort size);
var start_marker = reader.Position;
NetworkVariableFields[j].ReadField(reader);
if (reader.Position - start_marker != size)
{
Debug.LogError("Mismatched network variable size");
}
}
else
{
NetworkVariableFields[j].ReadField(reader);
}
if (networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety) // When EnsureNetworkVariableLengthSafety or DistributedAuthorityMode always do a bounds check
if (ensureLengthSafety || distributedAuthority)
{ {
if (reader.Position > (readStartPos + varSize)) if (reader.Position > (readStartPos + varSize))
{ {
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{ {
NetworkLog.LogWarning($"Var data read too far. {reader.Position - (readStartPos + varSize)} bytes."); NetworkLog.LogWarning($"[{name}][NetworkObjectId: {NetworkObjectId}][NetworkBehaviourId: {NetworkBehaviourId}][{NetworkVariableFields[j].Name}] NetworkVariable data read too big. {reader.Position - (readStartPos + varSize)} bytes.");
} }
reader.Seek(readStartPos + varSize); reader.Seek(readStartPos + varSize);
@@ -1254,7 +1268,7 @@ namespace Unity.Netcode
{ {
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{ {
NetworkLog.LogWarning($"Var data read too little. {(readStartPos + varSize) - reader.Position} bytes."); NetworkLog.LogWarning($"[{name}][NetworkObjectId: {NetworkObjectId}][NetworkBehaviourId: {NetworkBehaviourId}][{NetworkVariableFields[j].Name}] NetworkVariable data read too small. {(readStartPos + varSize) - reader.Position} bytes.");
} }
reader.Seek(readStartPos + varSize); reader.Seek(readStartPos + varSize);
@@ -1264,7 +1278,7 @@ namespace Unity.Netcode
} }
/// <summary> /// <summary>
/// Gets the local instance of a object with a given NetworkId /// Gets the local instance of a NetworkObject with a given NetworkId.
/// </summary> /// </summary>
/// <param name="networkId"></param> /// <param name="networkId"></param>
/// <returns></returns> /// <returns></returns>
@@ -1275,14 +1289,14 @@ namespace Unity.Netcode
/// <summary> /// <summary>
/// Override this method if your derived NetworkBehaviour requires custom synchronization data. /// Override this method if your derived NetworkBehaviour requires custom synchronization data.
/// Note: Use of this method is only for the initial client synchronization of NetworkBehaviours /// Use of this method is only for the initial client synchronization of NetworkBehaviours
/// and will increase the payload size for client synchronization and dynamically spawned /// and will increase the payload size for client synchronization and dynamically spawned
/// <see cref="NetworkObject"/>s. /// <see cref="NetworkObject"/>s.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// When serializing (writing) this will be invoked during the client synchronization period and /// When serializing (writing), this method is invoked during the client synchronization period and
/// when spawning new NetworkObjects. /// when spawning new NetworkObjects.
/// When deserializing (reading), this will be invoked prior to the NetworkBehaviour's associated /// When deserializing (reading), this method is invoked prior to the NetworkBehaviour's associated
/// NetworkObject being spawned. /// NetworkObject being spawned.
/// </remarks> /// </remarks>
/// <param name="serializer">The serializer to use to read and write the data.</param> /// <param name="serializer">The serializer to use to read and write the data.</param>
@@ -1305,10 +1319,10 @@ namespace Unity.Netcode
/// The relative client identifier targeted for the serialization of this <see cref="NetworkBehaviour"/> instance. /// The relative client identifier targeted for the serialization of this <see cref="NetworkBehaviour"/> instance.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// This value will be set prior to <see cref="OnSynchronize{T}(ref BufferSerializer{T})"/> being invoked. /// This value is set prior to <see cref="OnSynchronize{T}(ref BufferSerializer{T})"/> being invoked.
/// For writing (server-side), this is useful to know which client will receive the serialized data. /// For writing (server-side), this is useful to know which client will receive the serialized data.
/// For reading (client-side), this will be the <see cref="NetworkManager.LocalClientId"/>. /// For reading (client-side), this will be the <see cref="NetworkManager.LocalClientId"/>.
/// When synchronization of this instance is complete, this value will be reset to 0 /// When synchronization of this instance is complete, this value is reset to 0.
/// </remarks> /// </remarks>
protected ulong m_TargetIdBeingSynchronized { get; private set; } protected ulong m_TargetIdBeingSynchronized { get; private set; }
@@ -1431,9 +1445,8 @@ namespace Unity.Netcode
/// <summary> /// <summary>
/// Invoked when the <see cref="GameObject"/> the <see cref="NetworkBehaviour"/> is attached to. /// Invoked when the <see cref="GameObject"/> the <see cref="NetworkBehaviour"/> is attached to is destroyed.
/// NOTE: If you override this, you will want to always invoke this base class version of this /// If you override this, you must always invoke the base class version of this <see cref="OnDestroy"/> method.
/// <see cref="OnDestroy"/> method!!
/// </summary> /// </summary>
public virtual void OnDestroy() public virtual void OnDestroy()
{ {

View File

@@ -29,13 +29,40 @@ namespace Unity.Netcode
// RuntimeAccessModifiersILPP will make this `public` // RuntimeAccessModifiersILPP will make this `public`
internal static readonly Dictionary<uint, RpcReceiveHandler> __rpc_func_table = new Dictionary<uint, RpcReceiveHandler>(); internal static readonly Dictionary<uint, RpcReceiveHandler> __rpc_func_table = new Dictionary<uint, RpcReceiveHandler>();
#if DEVELOPMENT_BUILD || UNITY_EDITOR #if DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE
// RuntimeAccessModifiersILPP will make this `public` // RuntimeAccessModifiersILPP will make this `public`
internal static readonly Dictionary<uint, string> __rpc_name_table = new Dictionary<uint, string>(); internal static readonly Dictionary<uint, string> __rpc_name_table = new Dictionary<uint, string>();
#endif #endif
#pragma warning restore IDE1006 // restore naming rule violation check #pragma warning restore IDE1006 // restore naming rule violation check
#if DEVELOPMENT_BUILD || UNITY_EDITOR
private static List<Type> s_SerializedType = new List<Type>();
// This is used to control the serialized type not optimized messaging for integration test purposes
internal static bool DisableNotOptimizedSerializedType;
/// <summary>
/// Until all serialized types are optimized for the distributed authority network topology,
/// this will handle the notification to the user that the type being serialized is not yet
/// optimized but will only log the message once to prevent log spamming.
/// </summary>
internal static void LogSerializedTypeNotOptimized<T>()
{
if (DisableNotOptimizedSerializedType)
{
return;
}
var type = typeof(T);
if (!s_SerializedType.Contains(type))
{
s_SerializedType.Add(type);
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
{
Debug.LogWarning($"[{type.Name}] Serialized type has not been optimized for use with Distributed Authority!");
}
}
}
#endif
internal static bool IsDistributedAuthority; internal static bool IsDistributedAuthority;
/// <summary> /// <summary>
@@ -1062,6 +1089,13 @@ namespace Unity.Netcode
internal void Initialize(bool server) internal void Initialize(bool server)
{ {
#if DEVELOPMENT_BUILD || UNITY_EDITOR
if (!DisableNotOptimizedSerializedType)
{
s_SerializedType.Clear();
}
#endif
#if COM_UNITY_MODULES_PHYSICS #if COM_UNITY_MODULES_PHYSICS
NetworkTransformFixedUpdate.Clear(); NetworkTransformFixedUpdate.Clear();
#endif #endif
@@ -1250,17 +1284,8 @@ namespace Unity.Netcode
{ {
SpawnManager.ServerSpawnSceneObjectsOnStartSweep(); SpawnManager.ServerSpawnSceneObjectsOnStartSweep();
// Notify the server that all in-scnee placed NetworkObjects are spawned at this time.
foreach (var networkObject in SpawnManager.SpawnedObjectsList)
{
networkObject.InternalInSceneNetworkObjectsSpawned();
}
// Notify the server that everything should be synchronized/spawned at this time. // Notify the server that everything should be synchronized/spawned at this time.
foreach (var networkObject in SpawnManager.SpawnedObjectsList) SpawnManager.NotifyNetworkObjectsSynchronized();
{
networkObject.InternalNetworkSessionSynchronized();
}
OnServerStarted?.Invoke(); OnServerStarted?.Invoke();
ConnectionManager.LocalClient.IsApproved = true; ConnectionManager.LocalClient.IsApproved = true;
return true; return true;
@@ -1407,17 +1432,9 @@ namespace Unity.Netcode
} }
SpawnManager.ServerSpawnSceneObjectsOnStartSweep(); SpawnManager.ServerSpawnSceneObjectsOnStartSweep();
// Notify the host that all in-scnee placed NetworkObjects are spawned at this time.
foreach (var networkObject in SpawnManager.SpawnedObjectsList)
{
networkObject.InternalInSceneNetworkObjectsSpawned();
}
// Notify the host that everything should be synchronized/spawned at this time. // Notify the host that everything should be synchronized/spawned at this time.
foreach (var networkObject in SpawnManager.SpawnedObjectsList) SpawnManager.NotifyNetworkObjectsSynchronized();
{
networkObject.InternalNetworkSessionSynchronized();
}
OnServerStarted?.Invoke(); OnServerStarted?.Invoke();
OnClientStarted?.Invoke(); OnClientStarted?.Invoke();

View File

@@ -540,8 +540,8 @@ namespace Unity.Netcode
/// permission failure status codes will be returned via <see cref="OnOwnershipPermissionsFailure"/>. /// permission failure status codes will be returned via <see cref="OnOwnershipPermissionsFailure"/>.
/// <see cref="Locked"/>: The <see cref="NetworkObject"/> is locked and ownership cannot be acquired. /// <see cref="Locked"/>: The <see cref="NetworkObject"/> is locked and ownership cannot be acquired.
/// <see cref="RequestRequired"/>: The <see cref="NetworkObject"/> requires an ownership request via <see cref="RequestOwnership"/>. /// <see cref="RequestRequired"/>: The <see cref="NetworkObject"/> requires an ownership request via <see cref="RequestOwnership"/>.
/// <see cref="RequestInProgress"/>: The <see cref="NetworkObject"/> already is processing an ownership request and ownership cannot be acquired at this time. /// <see cref="RequestInProgress"/>: The <see cref="NetworkObject"/> is already processing an ownership request and ownership cannot be acquired at this time.
/// <see cref="NotTransferrable": The <see cref="NetworkObject"/> does not have the <see cref="OwnershipStatus.Transferable"/> flag set and ownership cannot be acquired. /// <see cref="NotTransferrable"/>: The <see cref="NetworkObject"/> does not have the <see cref="OwnershipStatus.Transferable"/> flag set and ownership cannot be acquired.
/// </summary> /// </summary>
public enum OwnershipPermissionsFailureStatus public enum OwnershipPermissionsFailureStatus
{ {
@@ -1547,6 +1547,11 @@ namespace Unity.Netcode
if (NetworkManager.DistributedAuthorityMode) if (NetworkManager.DistributedAuthorityMode)
{ {
if (NetworkManager.LocalClient == null || !NetworkManager.IsConnectedClient || !NetworkManager.ConnectionManager.LocalClient.IsApproved)
{
Debug.LogError($"Cannot spawn {name} until the client is fully connected to the session!");
return;
}
if (NetworkManager.NetworkConfig.EnableSceneManagement) if (NetworkManager.NetworkConfig.EnableSceneManagement)
{ {
NetworkSceneHandle = NetworkManager.SceneManager.ClientSceneHandleToServerSceneHandle[gameObject.scene.handle]; NetworkSceneHandle = NetworkManager.SceneManager.ClientSceneHandleToServerSceneHandle[gameObject.scene.handle];
@@ -1638,7 +1643,7 @@ namespace Unity.Netcode
return null; return null;
} }
ownerClientId = networkManager.DistributedAuthorityMode ? networkManager.LocalClientId : NetworkManager.ServerClientId; ownerClientId = networkManager.DistributedAuthorityMode ? networkManager.LocalClientId : ownerClientId;
// We only need to check for authority when running in client-server mode // We only need to check for authority when running in client-server mode
if (!networkManager.IsServer && !networkManager.DistributedAuthorityMode) if (!networkManager.IsServer && !networkManager.DistributedAuthorityMode)
{ {
@@ -2436,10 +2441,10 @@ namespace Unity.Netcode
if (NetworkManager.DistributedAuthorityMode) if (NetworkManager.DistributedAuthorityMode)
{ {
var readerPosition = reader.Position; var readerPosition = reader.Position;
reader.ReadValueSafe(out ushort behaviorCount); reader.ReadValueSafe(out ushort behaviourCount);
if (behaviorCount != ChildNetworkBehaviours.Count) if (behaviourCount != ChildNetworkBehaviours.Count)
{ {
Debug.LogError($"Network Behavior Count Mismatch! [{readerPosition}][{reader.Position}]"); Debug.LogError($"[{name}] Network Behavior Count Mismatch! [In: {behaviourCount} vs Local: {ChildNetworkBehaviours.Count}][StartReaderPos: {readerPosition}] CurrentReaderPos: {reader.Position}]");
return false; return false;
} }
} }

View File

@@ -31,7 +31,7 @@ namespace Unity.Netcode
public void Handle(ref NetworkContext context) public void Handle(ref NetworkContext context)
{ {
var networkManager = (NetworkManager)context.SystemOwner; var networkManager = (NetworkManager)context.SystemOwner;
if ((ShouldSynchronize || networkManager.CMBServiceConnection) && networkManager.DistributedAuthorityMode && networkManager.LocalClient.IsSessionOwner) if (ShouldSynchronize && networkManager.NetworkConfig.EnableSceneManagement && networkManager.DistributedAuthorityMode && networkManager.LocalClient.IsSessionOwner)
{ {
networkManager.SceneManager.SynchronizeNetworkObjects(ClientId); networkManager.SceneManager.SynchronizeNetworkObjects(ClientId);
} }

View File

@@ -224,10 +224,7 @@ namespace Unity.Netcode
networkManager.ConnectionManager.InvokeOnClientConnectedCallback(context.SenderId); networkManager.ConnectionManager.InvokeOnClientConnectedCallback(context.SenderId);
// For convenience, notify all NetworkBehaviours that synchronization is complete. // For convenience, notify all NetworkBehaviours that synchronization is complete.
foreach (var networkObject in networkManager.SpawnManager.SpawnedObjectsList) networkManager.SpawnManager.NotifyNetworkObjectsSynchronized();
{
networkObject.InternalNetworkSessionSynchronized();
}
} }
else else
{ {
@@ -240,16 +237,12 @@ namespace Unity.Netcode
if (!IsRestoredSession) if (!IsRestoredSession)
{ {
// Synchronize the service with the initial session owner's loaded scenes and spawned objects
networkManager.SceneManager.SynchronizeNetworkObjects(NetworkManager.ServerClientId);
// Spawn any in-scene placed NetworkObjects // Spawn any in-scene placed NetworkObjects
networkManager.SpawnManager.ServerSpawnSceneObjectsOnStartSweep(); networkManager.SpawnManager.ServerSpawnSceneObjectsOnStartSweep();
// With scene management enabled and since the session owner doesn't send a Synchronize scene event synchronize itself,
// we need to notify the session owner that all in-scnee placed NetworkObjects are spawned at this time.
foreach (var networkObject in networkManager.SpawnManager.SpawnedObjectsList)
{
networkObject.InternalInSceneNetworkObjectsSpawned();
}
// Spawn the local player of the session owner // Spawn the local player of the session owner
if (networkManager.AutoSpawnPlayerPrefabClientSide) if (networkManager.AutoSpawnPlayerPrefabClientSide)
{ {
@@ -261,10 +254,7 @@ namespace Unity.Netcode
// With scene management enabled and since the session owner doesn't send a Synchronize scene event synchronize itself, // With scene management enabled and since the session owner doesn't send a Synchronize scene event synchronize itself,
// we need to notify the session owner that everything should be synchronized/spawned at this time. // we need to notify the session owner that everything should be synchronized/spawned at this time.
foreach (var networkObject in networkManager.SpawnManager.SpawnedObjectsList) networkManager.SpawnManager.NotifyNetworkObjectsSynchronized();
{
networkObject.InternalNetworkSessionSynchronized();
}
// When scene management is enabled and since the session owner is synchronizing the service (i.e. acting like host), // When scene management is enabled and since the session owner is synchronizing the service (i.e. acting like host),
// we need to locallyh invoke the OnClientConnected callback at this point in time. // we need to locallyh invoke the OnClientConnected callback at this point in time.

View File

@@ -41,7 +41,7 @@ namespace Unity.Netcode
payload = new FastBufferReader(reader.GetUnsafePtrAtCurrentPosition(), Allocator.None, reader.Length - reader.Position); payload = new FastBufferReader(reader.GetUnsafePtrAtCurrentPosition(), Allocator.None, reader.Length - reader.Position);
#if DEVELOPMENT_BUILD || UNITY_EDITOR #if DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE
if (NetworkBehaviour.__rpc_name_table[networkBehaviour.GetType()].TryGetValue(metadata.NetworkRpcMethodId, out var rpcMethodName)) if (NetworkBehaviour.__rpc_name_table[networkBehaviour.GetType()].TryGetValue(metadata.NetworkRpcMethodId, out var rpcMethodName))
{ {
networkManager.NetworkMetrics.TrackRpcReceived( networkManager.NetworkMetrics.TrackRpcReceived(

View File

@@ -36,12 +36,12 @@ namespace Unity.Netcode
private protected void SendMessageToClient(NetworkBehaviour behaviour, ulong clientId, ref RpcMessage message, NetworkDelivery delivery) private protected void SendMessageToClient(NetworkBehaviour behaviour, ulong clientId, ref RpcMessage message, NetworkDelivery delivery)
{ {
#if DEVELOPMENT_BUILD || UNITY_EDITOR #if DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE
var size = var size =
#endif #endif
behaviour.NetworkManager.MessageManager.SendMessage(ref message, delivery, clientId); behaviour.NetworkManager.MessageManager.SendMessage(ref message, delivery, clientId);
#if DEVELOPMENT_BUILD || UNITY_EDITOR #if DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE
if (NetworkBehaviour.__rpc_name_table[behaviour.GetType()].TryGetValue(message.Metadata.NetworkRpcMethodId, out var rpcMethodName)) if (NetworkBehaviour.__rpc_name_table[behaviour.GetType()].TryGetValue(message.Metadata.NetworkRpcMethodId, out var rpcMethodName))
{ {
behaviour.NetworkManager.NetworkMetrics.TrackRpcSent( behaviour.NetworkManager.NetworkMetrics.TrackRpcSent(

View File

@@ -46,7 +46,7 @@ namespace Unity.Netcode
message.Handle(ref context); message.Handle(ref context);
length = tempBuffer.Length; length = tempBuffer.Length;
} }
#if DEVELOPMENT_BUILD || UNITY_EDITOR #if DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE
if (NetworkBehaviour.__rpc_name_table[behaviour.GetType()].TryGetValue(message.Metadata.NetworkRpcMethodId, out var rpcMethodName)) if (NetworkBehaviour.__rpc_name_table[behaviour.GetType()].TryGetValue(message.Metadata.NetworkRpcMethodId, out var rpcMethodName))
{ {
networkManager.NetworkMetrics.TrackRpcSent( networkManager.NetworkMetrics.TrackRpcSent(

View File

@@ -18,12 +18,12 @@ namespace Unity.Netcode
internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, NetworkDelivery delivery, RpcParams rpcParams) internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, NetworkDelivery delivery, RpcParams rpcParams)
{ {
var proxyMessage = new ProxyMessage { Delivery = delivery, TargetClientIds = TargetClientIds.AsArray(), WrappedMessage = message }; var proxyMessage = new ProxyMessage { Delivery = delivery, TargetClientIds = TargetClientIds.AsArray(), WrappedMessage = message };
#if DEVELOPMENT_BUILD || UNITY_EDITOR #if DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE
var size = var size =
#endif #endif
behaviour.NetworkManager.MessageManager.SendMessage(ref proxyMessage, delivery, NetworkManager.ServerClientId); behaviour.NetworkManager.MessageManager.SendMessage(ref proxyMessage, delivery, NetworkManager.ServerClientId);
#if DEVELOPMENT_BUILD || UNITY_EDITOR #if DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE
if (NetworkBehaviour.__rpc_name_table[behaviour.GetType()].TryGetValue(message.Metadata.NetworkRpcMethodId, out var rpcMethodName)) if (NetworkBehaviour.__rpc_name_table[behaviour.GetType()].TryGetValue(message.Metadata.NetworkRpcMethodId, out var rpcMethodName))
{ {
foreach (var clientId in TargetClientIds) foreach (var clientId in TargetClientIds)

View File

@@ -51,6 +51,7 @@ namespace Unity.Netcode
#pragma warning restore IDE0001 #pragma warning restore IDE0001
[Serializable] [Serializable]
[GenerateSerializationForGenericParameter(0)] [GenerateSerializationForGenericParameter(0)]
[GenerateSerializationForType(typeof(byte))]
public class AnticipatedNetworkVariable<T> : NetworkVariableBase public class AnticipatedNetworkVariable<T> : NetworkVariableBase
{ {
[SerializeField] [SerializeField]

View File

@@ -94,18 +94,18 @@ namespace Unity.Netcode
{ {
case NetworkListEvent<T>.EventType.Add: case NetworkListEvent<T>.EventType.Add:
{ {
NetworkVariableSerialization<T>.Write(writer, ref element.Value); NetworkVariableSerialization<T>.Serializer.Write(writer, ref element.Value);
} }
break; break;
case NetworkListEvent<T>.EventType.Insert: case NetworkListEvent<T>.EventType.Insert:
{ {
BytePacker.WriteValueBitPacked(writer, element.Index); BytePacker.WriteValueBitPacked(writer, element.Index);
NetworkVariableSerialization<T>.Write(writer, ref element.Value); NetworkVariableSerialization<T>.Serializer.Write(writer, ref element.Value);
} }
break; break;
case NetworkListEvent<T>.EventType.Remove: case NetworkListEvent<T>.EventType.Remove:
{ {
NetworkVariableSerialization<T>.Write(writer, ref element.Value); NetworkVariableSerialization<T>.Serializer.Write(writer, ref element.Value);
} }
break; break;
case NetworkListEvent<T>.EventType.RemoveAt: case NetworkListEvent<T>.EventType.RemoveAt:
@@ -116,7 +116,7 @@ namespace Unity.Netcode
case NetworkListEvent<T>.EventType.Value: case NetworkListEvent<T>.EventType.Value:
{ {
BytePacker.WriteValueBitPacked(writer, element.Index); BytePacker.WriteValueBitPacked(writer, element.Index);
NetworkVariableSerialization<T>.Write(writer, ref element.Value); NetworkVariableSerialization<T>.Serializer.Write(writer, ref element.Value);
} }
break; break;
case NetworkListEvent<T>.EventType.Clear: case NetworkListEvent<T>.EventType.Clear:
@@ -133,13 +133,13 @@ namespace Unity.Netcode
{ {
if (m_NetworkManager.DistributedAuthorityMode) if (m_NetworkManager.DistributedAuthorityMode)
{ {
writer.WriteValueSafe(NetworkVariableSerialization<T>.Type); writer.WriteValueSafe(NetworkVariableSerialization<T>.Serializer.Type);
if (NetworkVariableSerialization<T>.Type == CollectionItemType.Unmanaged) if (NetworkVariableSerialization<T>.Serializer.Type == NetworkVariableType.Unmanaged)
{ {
// Write the size of the unmanaged serialized type as it has a fixed size. This allows the CMB runtime to correctly read the unmanged type. // Write the size of the unmanaged serialized type as it has a fixed size. This allows the CMB runtime to correctly read the unmanged type.
var placeholder = new T(); var placeholder = new T();
var startPos = writer.Position; var startPos = writer.Position;
NetworkVariableSerialization<T>.Write(writer, ref placeholder); NetworkVariableSerialization<T>.Serializer.Write(writer, ref placeholder);
var size = writer.Position - startPos; var size = writer.Position - startPos;
writer.Seek(startPos); writer.Seek(startPos);
BytePacker.WriteValueBitPacked(writer, size); BytePacker.WriteValueBitPacked(writer, size);
@@ -148,7 +148,7 @@ namespace Unity.Netcode
writer.WriteValueSafe((ushort)m_List.Length); writer.WriteValueSafe((ushort)m_List.Length);
for (int i = 0; i < m_List.Length; i++) for (int i = 0; i < m_List.Length; i++)
{ {
NetworkVariableSerialization<T>.Write(writer, ref m_List.ElementAt(i)); NetworkVariableSerialization<T>.Serializer.Write(writer, ref m_List.ElementAt(i));
} }
} }
@@ -158,9 +158,9 @@ namespace Unity.Netcode
m_List.Clear(); m_List.Clear();
if (m_NetworkManager.DistributedAuthorityMode) if (m_NetworkManager.DistributedAuthorityMode)
{ {
// Collection item type is used by the CMB rust service, drop value here. SerializationTools.ReadType(reader, NetworkVariableSerialization<T>.Serializer);
reader.ReadValueSafe(out CollectionItemType type); // Collection item type is used by the DA server, drop value here.
if (type == CollectionItemType.Unmanaged) if (NetworkVariableSerialization<T>.Serializer.Type == NetworkVariableType.Unmanaged)
{ {
ByteUnpacker.ReadValueBitPacked(reader, out int _); ByteUnpacker.ReadValueBitPacked(reader, out int _);
} }
@@ -169,7 +169,7 @@ namespace Unity.Netcode
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{ {
var value = new T(); var value = new T();
NetworkVariableSerialization<T>.Read(reader, ref value); NetworkVariableSerialization<T>.Serializer.Read(reader, ref value);
m_List.Add(value); m_List.Add(value);
} }
} }
@@ -186,7 +186,7 @@ namespace Unity.Netcode
case NetworkListEvent<T>.EventType.Add: case NetworkListEvent<T>.EventType.Add:
{ {
var value = new T(); var value = new T();
NetworkVariableSerialization<T>.Read(reader, ref value); NetworkVariableSerialization<T>.Serializer.Read(reader, ref value);
m_List.Add(value); m_List.Add(value);
if (OnListChanged != null) if (OnListChanged != null)
@@ -215,7 +215,7 @@ namespace Unity.Netcode
{ {
ByteUnpacker.ReadValueBitPacked(reader, out int index); ByteUnpacker.ReadValueBitPacked(reader, out int index);
var value = new T(); var value = new T();
NetworkVariableSerialization<T>.Read(reader, ref value); NetworkVariableSerialization<T>.Serializer.Read(reader, ref value);
if (index < m_List.Length) if (index < m_List.Length)
{ {
@@ -252,7 +252,7 @@ namespace Unity.Netcode
case NetworkListEvent<T>.EventType.Remove: case NetworkListEvent<T>.EventType.Remove:
{ {
var value = new T(); var value = new T();
NetworkVariableSerialization<T>.Read(reader, ref value); NetworkVariableSerialization<T>.Serializer.Read(reader, ref value);
int index = m_List.IndexOf(value); int index = m_List.IndexOf(value);
if (index == -1) if (index == -1)
{ {
@@ -315,7 +315,7 @@ namespace Unity.Netcode
{ {
ByteUnpacker.ReadValueBitPacked(reader, out int index); ByteUnpacker.ReadValueBitPacked(reader, out int index);
var value = new T(); var value = new T();
NetworkVariableSerialization<T>.Read(reader, ref value); NetworkVariableSerialization<T>.Serializer.Read(reader, ref value);
if (index >= m_List.Length) if (index >= m_List.Length)
{ {
throw new Exception("Shouldn't be here, index is higher than list length"); throw new Exception("Shouldn't be here, index is higher than list length");

View File

@@ -9,6 +9,7 @@ namespace Unity.Netcode
/// <typeparam name="T">the unmanaged type for <see cref="NetworkVariable{T}"/> </typeparam> /// <typeparam name="T">the unmanaged type for <see cref="NetworkVariable{T}"/> </typeparam>
[Serializable] [Serializable]
[GenerateSerializationForGenericParameter(0)] [GenerateSerializationForGenericParameter(0)]
[GenerateSerializationForType(typeof(byte))]
public class NetworkVariable<T> : NetworkVariableBase public class NetworkVariable<T> : NetworkVariableBase
{ {
/// <summary> /// <summary>

View File

@@ -35,7 +35,7 @@ namespace Unity.Netcode
private NetworkManager m_InternalNetworkManager; private NetworkManager m_InternalNetworkManager;
internal virtual NetworkVariableType Type => NetworkVariableType.Custom; internal virtual NetworkVariableType Type => NetworkVariableType.Unknown;
private protected NetworkManager m_NetworkManager private protected NetworkManager m_NetworkManager
{ {
@@ -311,7 +311,6 @@ namespace Unity.Netcode
/// </summary> /// </summary>
/// <param name="reader">The stream to read the state from</param> /// <param name="reader">The stream to read the state from</param>
public abstract void ReadField(FastBufferReader reader); public abstract void ReadField(FastBufferReader reader);
/// <summary> /// <summary>
/// Reads delta from the reader and applies them to the internal value /// Reads delta from the reader and applies them to the internal value
/// </summary> /// </summary>
@@ -327,47 +326,4 @@ namespace Unity.Netcode
m_InternalNetworkManager = null; m_InternalNetworkManager = null;
} }
} }
/// <summary>
/// Enum representing the different types of Network Variables.
/// </summary>
public enum NetworkVariableType : byte
{
/// <summary>
/// Value
/// Used for all of the basic NetworkVariables that contain a single value
/// </summary>
Value = 0,
/// <summary>
/// Custom
/// For any custom implemented extension of the NetworkVariableBase
/// </summary>
Custom = 1,
/// <summary>
/// NetworkList
/// </summary>
NetworkList = 2
}
public enum CollectionItemType : byte
{
/// <summary>
/// For any type that is not valid inside a NetworkVariable collection
/// </summary>
Unknown = 0,
/// <summary>
/// The following types are valid types inside of NetworkVariable collections
/// </summary>
Short = 1,
UShort = 2,
Int = 3,
UInt = 4,
Long = 5,
ULong = 6,
Unmanaged = 7,
}
} }

View File

@@ -0,0 +1,40 @@
#if UNITY_EDITOR
#endif
namespace Unity.Netcode
{
/// <summary>
/// Enum representing the different types of Network Variables that can be sent over the network.
/// The values cannot be changed, as they are used to serialize and deserialize variables on the DA server.
/// Adding new variables should be done by adding new values to the end of the enum
/// using the next free value.
/// </summary>
/// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
/// Add any new Variable types to this table at the END with incremented index value
/// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
internal enum NetworkVariableType : byte
{
/// <summary>
/// Value
/// Used for all of the basic NetworkVariables that contain a single value
/// </summary>
Value = 0,
/// <summary>
/// For any type that is not known at runtime
/// </summary>
Unknown = 1,
/// <summary>
/// NetworkList
/// </summary>
NetworkList = 2,
// The following types are valid types inside of NetworkVariable collections
Short = 11,
UShort = 12,
Int = 13,
UInt = 14,
Long = 15,
ULong = 16,
Unmanaged = 17,
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: df4a4005f1c842669f94a404019400ed
timeCreated: 1718292058

View File

@@ -14,6 +14,9 @@ namespace Unity.Netcode
/// <typeparam name="T"></typeparam> /// <typeparam name="T"></typeparam>
internal class FallbackSerializer<T> : INetworkVariableSerializer<T> internal class FallbackSerializer<T> : INetworkVariableSerializer<T>
{ {
public NetworkVariableType Type => NetworkVariableType.Unknown;
public bool IsDistributedAuthorityOptimized => true;
private void ThrowArgumentError() private void ThrowArgumentError()
{ {
throw new ArgumentException($"Serialization has not been generated for type {typeof(T).FullName}. This can be addressed by adding a [{nameof(GenerateSerializationForGenericParameterAttribute)}] to your generic class that serializes this value (if you are using one), adding [{nameof(GenerateSerializationForTypeAttribute)}(typeof({typeof(T).FullName})] to the class or method that is attempting to serialize it, or creating a field on a {nameof(NetworkBehaviour)} of type {nameof(NetworkVariable<T>)}. If this error continues to appear after doing one of those things and this is a type you can change, then either implement {nameof(INetworkSerializable)} or mark it as serializable by memcpy by adding {nameof(INetworkSerializeByMemcpy)} to its interface list to enable automatic serialization generation. If not, assign serialization code to {nameof(UserNetworkVariableSerialization<T>)}.{nameof(UserNetworkVariableSerialization<T>.WriteValue)}, {nameof(UserNetworkVariableSerialization<T>)}.{nameof(UserNetworkVariableSerialization<T>.ReadValue)}, and {nameof(UserNetworkVariableSerialization<T>)}.{nameof(UserNetworkVariableSerialization<T>.DuplicateValue)}, or if it's serializable by memcpy (contains no pointers), wrap it in {typeof(ForceNetworkSerializeByMemcpy<>).Name}."); throw new ArgumentException($"Serialization has not been generated for type {typeof(T).FullName}. This can be addressed by adding a [{nameof(GenerateSerializationForGenericParameterAttribute)}] to your generic class that serializes this value (if you are using one), adding [{nameof(GenerateSerializationForTypeAttribute)}(typeof({typeof(T).FullName})] to the class or method that is attempting to serialize it, or creating a field on a {nameof(NetworkBehaviour)} of type {nameof(NetworkVariable<T>)}. If this error continues to appear after doing one of those things and this is a type you can change, then either implement {nameof(INetworkSerializable)} or mark it as serializable by memcpy by adding {nameof(INetworkSerializeByMemcpy)} to its interface list to enable automatic serialization generation. If not, assign serialization code to {nameof(UserNetworkVariableSerialization<T>)}.{nameof(UserNetworkVariableSerialization<T>.WriteValue)}, {nameof(UserNetworkVariableSerialization<T>)}.{nameof(UserNetworkVariableSerialization<T>.ReadValue)}, and {nameof(UserNetworkVariableSerialization<T>)}.{nameof(UserNetworkVariableSerialization<T>.DuplicateValue)}, or if it's serializable by memcpy (contains no pointers), wrap it in {typeof(ForceNetworkSerializeByMemcpy<>).Name}.");
@@ -79,6 +82,11 @@ namespace Unity.Netcode
} }
UserNetworkVariableSerialization<T>.DuplicateValue(value, ref duplicatedValue); UserNetworkVariableSerialization<T>.DuplicateValue(value, ref duplicatedValue);
} }
public void WriteDistributedAuthority(FastBufferWriter writer, ref T value) => ThrowArgumentError();
public void ReadDistributedAuthority(FastBufferReader reader, ref T value) => ThrowArgumentError();
public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref T value, ref T previousValue) => ThrowArgumentError();
public void ReadDeltaDistributedAuthority(FastBufferReader reader, ref T value) => ThrowArgumentError();
} }
// RuntimeAccessModifiersILPP will make this `public` // RuntimeAccessModifiersILPP will make this `public`

View File

@@ -3,11 +3,27 @@ using Unity.Collections;
namespace Unity.Netcode namespace Unity.Netcode
{ {
/// <summary> /// <summary>
/// Interface used by NetworkVariables to serialize them /// Interface used by NetworkVariables to serialize them with additional information for the DA runtime
/// </summary> /// </summary>
/// ///
/// <typeparam name="T"></typeparam> /// <typeparam name="T"></typeparam>
internal interface INetworkVariableSerializer<T> internal interface IDistributedAuthoritySerializer<T>
{
/// <summary>
/// The Type tells the DA server how to parse this type.
/// The user should never be able to override this value, as it is meaningful for the DA server
/// </summary>
public NetworkVariableType Type { get; }
public bool IsDistributedAuthorityOptimized { get; }
public void WriteDistributedAuthority(FastBufferWriter writer, ref T value);
public void ReadDistributedAuthority(FastBufferReader reader, ref T value);
public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref T value, ref T previousValue);
public void ReadDeltaDistributedAuthority(FastBufferReader reader, ref T value);
}
/// <typeparam name="T"></typeparam>
internal interface INetworkVariableSerializer<T> : IDistributedAuthoritySerializer<T>
{ {
// Write has to be taken by ref here because of INetworkSerializable // Write has to be taken by ref here because of INetworkSerializable
// Open Instance Delegates (pointers to methods without an instance attached to them) // Open Instance Delegates (pointers to methods without an instance attached to them)

View File

@@ -16,12 +16,6 @@ namespace Unity.Netcode
internal static bool IsDistributedAuthority => NetworkManager.IsDistributedAuthority; internal static bool IsDistributedAuthority => NetworkManager.IsDistributedAuthority;
/// <summary>
/// The collection item type tells the CMB server how to read the bytes of each item in the collection
/// </summary>
/// DANGO-EXP TODO: Determine if this is distributed authority only and impacts of this in client-server
internal static CollectionItemType Type = CollectionItemType.Unknown;
/// <summary> /// <summary>
/// A callback to check if two values are equal. /// A callback to check if two values are equal.
/// </summary> /// </summary>
@@ -59,7 +53,20 @@ namespace Unity.Netcode
/// <param name="value"></param> /// <param name="value"></param>
public static void Write(FastBufferWriter writer, ref T value) public static void Write(FastBufferWriter writer, ref T value)
{ {
Serializer.Write(writer, ref value); if (IsDistributedAuthority)
{
#if DEVELOPMENT_BUILD || UNITY_EDITOR
if (!NetworkManager.DisableNotOptimizedSerializedType && !Serializer.IsDistributedAuthorityOptimized)
{
NetworkManager.LogSerializedTypeNotOptimized<T>();
}
#endif
Serializer.WriteDistributedAuthority(writer, ref value);
}
else
{
Serializer.Write(writer, ref value);
}
} }
/// <summary> /// <summary>
@@ -84,7 +91,14 @@ namespace Unity.Netcode
/// <param name="value"></param> /// <param name="value"></param>
public static void Read(FastBufferReader reader, ref T value) public static void Read(FastBufferReader reader, ref T value)
{ {
Serializer.Read(reader, ref value); if (IsDistributedAuthority)
{
Serializer.ReadDistributedAuthority(reader, ref value);
}
else
{
Serializer.Read(reader, ref value);
}
} }
/// <summary> /// <summary>
@@ -106,7 +120,20 @@ namespace Unity.Netcode
/// <param name="value"></param> /// <param name="value"></param>
public static void WriteDelta(FastBufferWriter writer, ref T value, ref T previousValue) public static void WriteDelta(FastBufferWriter writer, ref T value, ref T previousValue)
{ {
Serializer.WriteDelta(writer, ref value, ref previousValue); if (IsDistributedAuthority)
{
#if DEVELOPMENT_BUILD || UNITY_EDITOR
if (!NetworkManager.DisableNotOptimizedSerializedType && !Serializer.IsDistributedAuthorityOptimized)
{
NetworkManager.LogSerializedTypeNotOptimized<T>();
}
#endif
Serializer.WriteDeltaDistributedAuthority(writer, ref value, ref previousValue);
}
else
{
Serializer.WriteDelta(writer, ref value, ref previousValue);
}
} }
/// <summary> /// <summary>
@@ -131,7 +158,14 @@ namespace Unity.Netcode
/// <param name="value"></param> /// <param name="value"></param>
public static void ReadDelta(FastBufferReader reader, ref T value) public static void ReadDelta(FastBufferReader reader, ref T value)
{ {
Serializer.ReadDelta(reader, ref value); if (IsDistributedAuthority)
{
Serializer.ReadDeltaDistributedAuthority(reader, ref value);
}
else
{
Serializer.ReadDelta(reader, ref value);
}
} }
/// <summary> /// <summary>

View File

@@ -38,14 +38,6 @@ namespace Unity.Netcode
NetworkVariableSerialization<long>.AreEqual = NetworkVariableEquality<long>.ValueEquals; NetworkVariableSerialization<long>.AreEqual = NetworkVariableEquality<long>.ValueEquals;
NetworkVariableSerialization<ulong>.Serializer = new UlongSerializer(); NetworkVariableSerialization<ulong>.Serializer = new UlongSerializer();
NetworkVariableSerialization<ulong>.AreEqual = NetworkVariableEquality<ulong>.ValueEquals; NetworkVariableSerialization<ulong>.AreEqual = NetworkVariableEquality<ulong>.ValueEquals;
// DANGO-EXP TODO: Determine if this is distributed authority only and impacts of this in client-server
NetworkVariableSerialization<short>.Type = CollectionItemType.Short;
NetworkVariableSerialization<ushort>.Type = CollectionItemType.UShort;
NetworkVariableSerialization<int>.Type = CollectionItemType.Int;
NetworkVariableSerialization<uint>.Type = CollectionItemType.UInt;
NetworkVariableSerialization<long>.Type = CollectionItemType.Long;
NetworkVariableSerialization<ulong>.Type = CollectionItemType.ULong;
} }
/// <summary> /// <summary>
@@ -55,8 +47,6 @@ namespace Unity.Netcode
public static void InitializeSerializer_UnmanagedByMemcpy<T>() where T : unmanaged public static void InitializeSerializer_UnmanagedByMemcpy<T>() where T : unmanaged
{ {
NetworkVariableSerialization<T>.Serializer = new UnmanagedTypeSerializer<T>(); NetworkVariableSerialization<T>.Serializer = new UnmanagedTypeSerializer<T>();
// DANGO-EXP TODO: Determine if this is distributed authority only and impacts of this in client-server
NetworkVariableSerialization<T>.Type = CollectionItemType.Unmanaged;
} }
/// <summary> /// <summary>

View File

@@ -10,6 +10,21 @@ namespace Unity.Netcode
/// </summary> /// </summary>
internal class ShortSerializer : INetworkVariableSerializer<short> internal class ShortSerializer : INetworkVariableSerializer<short>
{ {
public NetworkVariableType Type => NetworkVariableType.Short;
public bool IsDistributedAuthorityOptimized => true;
public void WriteDistributedAuthority(FastBufferWriter writer, ref short value)
{
Write(writer, ref value);
}
public void ReadDistributedAuthority(FastBufferReader reader, ref short value)
{
Read(reader, ref value);
}
public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref short value, ref short previousValue) => Write(writer, ref value);
public void ReadDeltaDistributedAuthority(FastBufferReader reader, ref short value) => Read(reader, ref value);
public void Write(FastBufferWriter writer, ref short value) public void Write(FastBufferWriter writer, ref short value)
{ {
BytePacker.WriteValueBitPacked(writer, value); BytePacker.WriteValueBitPacked(writer, value);
@@ -46,6 +61,20 @@ namespace Unity.Netcode
/// </summary> /// </summary>
internal class UshortSerializer : INetworkVariableSerializer<ushort> internal class UshortSerializer : INetworkVariableSerializer<ushort>
{ {
public NetworkVariableType Type => NetworkVariableType.UShort;
public bool IsDistributedAuthorityOptimized => true;
public void WriteDistributedAuthority(FastBufferWriter writer, ref ushort value)
{
Write(writer, ref value);
}
public void ReadDistributedAuthority(FastBufferReader reader, ref ushort value)
{
Read(reader, ref value);
}
public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref ushort value, ref ushort previousValue) => Write(writer, ref value);
public void ReadDeltaDistributedAuthority(FastBufferReader reader, ref ushort value) => Read(reader, ref value);
public void Write(FastBufferWriter writer, ref ushort value) public void Write(FastBufferWriter writer, ref ushort value)
{ {
BytePacker.WriteValueBitPacked(writer, value); BytePacker.WriteValueBitPacked(writer, value);
@@ -82,6 +111,20 @@ namespace Unity.Netcode
/// </summary> /// </summary>
internal class IntSerializer : INetworkVariableSerializer<int> internal class IntSerializer : INetworkVariableSerializer<int>
{ {
public NetworkVariableType Type => NetworkVariableType.Int;
public bool IsDistributedAuthorityOptimized => true;
public void WriteDistributedAuthority(FastBufferWriter writer, ref int value)
{
Write(writer, ref value);
}
public void ReadDistributedAuthority(FastBufferReader reader, ref int value)
{
Read(reader, ref value);
}
public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref int value, ref int previousValue) => Write(writer, ref value);
public void ReadDeltaDistributedAuthority(FastBufferReader reader, ref int value) => Read(reader, ref value);
public void Write(FastBufferWriter writer, ref int value) public void Write(FastBufferWriter writer, ref int value)
{ {
BytePacker.WriteValueBitPacked(writer, value); BytePacker.WriteValueBitPacked(writer, value);
@@ -118,6 +161,20 @@ namespace Unity.Netcode
/// </summary> /// </summary>
internal class UintSerializer : INetworkVariableSerializer<uint> internal class UintSerializer : INetworkVariableSerializer<uint>
{ {
public NetworkVariableType Type => NetworkVariableType.UInt;
public bool IsDistributedAuthorityOptimized => true;
public void WriteDistributedAuthority(FastBufferWriter writer, ref uint value)
{
Write(writer, ref value);
}
public void ReadDistributedAuthority(FastBufferReader reader, ref uint value)
{
Read(reader, ref value);
}
public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref uint value, ref uint previousValue) => Write(writer, ref value);
public void ReadDeltaDistributedAuthority(FastBufferReader reader, ref uint value) => Read(reader, ref value);
public void Write(FastBufferWriter writer, ref uint value) public void Write(FastBufferWriter writer, ref uint value)
{ {
BytePacker.WriteValueBitPacked(writer, value); BytePacker.WriteValueBitPacked(writer, value);
@@ -154,6 +211,20 @@ namespace Unity.Netcode
/// </summary> /// </summary>
internal class LongSerializer : INetworkVariableSerializer<long> internal class LongSerializer : INetworkVariableSerializer<long>
{ {
public NetworkVariableType Type => NetworkVariableType.Long;
public bool IsDistributedAuthorityOptimized => true;
public void WriteDistributedAuthority(FastBufferWriter writer, ref long value)
{
Write(writer, ref value);
}
public void ReadDistributedAuthority(FastBufferReader reader, ref long value)
{
Read(reader, ref value);
}
public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref long value, ref long previousValue) => Write(writer, ref value);
public void ReadDeltaDistributedAuthority(FastBufferReader reader, ref long value) => Read(reader, ref value);
public void Write(FastBufferWriter writer, ref long value) public void Write(FastBufferWriter writer, ref long value)
{ {
BytePacker.WriteValueBitPacked(writer, value); BytePacker.WriteValueBitPacked(writer, value);
@@ -190,6 +261,21 @@ namespace Unity.Netcode
/// </summary> /// </summary>
internal class UlongSerializer : INetworkVariableSerializer<ulong> internal class UlongSerializer : INetworkVariableSerializer<ulong>
{ {
public NetworkVariableType Type => NetworkVariableType.ULong;
public bool IsDistributedAuthorityOptimized => true;
public void WriteDistributedAuthority(FastBufferWriter writer, ref ulong value)
{
Write(writer, ref value);
}
public void ReadDistributedAuthority(FastBufferReader reader, ref ulong value)
{
Read(reader, ref value);
}
public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref ulong value, ref ulong previousValue) => Write(writer, ref value);
public void ReadDeltaDistributedAuthority(FastBufferReader reader, ref ulong value) => Read(reader, ref value);
public void Write(FastBufferWriter writer, ref ulong value) public void Write(FastBufferWriter writer, ref ulong value)
{ {
BytePacker.WriteValueBitPacked(writer, value); BytePacker.WriteValueBitPacked(writer, value);
@@ -231,6 +317,21 @@ namespace Unity.Netcode
/// <typeparam name="T"></typeparam> /// <typeparam name="T"></typeparam>
internal class UnmanagedTypeSerializer<T> : INetworkVariableSerializer<T> where T : unmanaged internal class UnmanagedTypeSerializer<T> : INetworkVariableSerializer<T> where T : unmanaged
{ {
public NetworkVariableType Type => NetworkVariableType.Unmanaged;
public bool IsDistributedAuthorityOptimized => false;
public void WriteDistributedAuthority(FastBufferWriter writer, ref T value)
{
Write(writer, ref value);
}
public void ReadDistributedAuthority(FastBufferReader reader, ref T value)
{
Read(reader, ref value);
}
public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref T value, ref T previousValue) => Write(writer, ref value);
public void ReadDeltaDistributedAuthority(FastBufferReader reader, ref T value) => Read(reader, ref value);
public void Write(FastBufferWriter writer, ref T value) public void Write(FastBufferWriter writer, ref T value)
{ {
writer.WriteUnmanagedSafe(value); writer.WriteUnmanagedSafe(value);
@@ -264,6 +365,20 @@ namespace Unity.Netcode
internal class ListSerializer<T> : INetworkVariableSerializer<List<T>> internal class ListSerializer<T> : INetworkVariableSerializer<List<T>>
{ {
public NetworkVariableType Type => NetworkVariableType.Value;
public bool IsDistributedAuthorityOptimized => false;
public void WriteDistributedAuthority(FastBufferWriter writer, ref List<T> value)
{
Write(writer, ref value);
}
public void ReadDistributedAuthority(FastBufferReader reader, ref List<T> value)
{
Read(reader, ref value);
}
public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref List<T> value, ref List<T> previousValue) => Write(writer, ref value);
public void ReadDeltaDistributedAuthority(FastBufferReader reader, ref List<T> value) => Read(reader, ref value);
public void Write(FastBufferWriter writer, ref List<T> value) public void Write(FastBufferWriter writer, ref List<T> value)
{ {
var isNull = value == null; var isNull = value == null;
@@ -350,6 +465,20 @@ namespace Unity.Netcode
internal class HashSetSerializer<T> : INetworkVariableSerializer<HashSet<T>> where T : IEquatable<T> internal class HashSetSerializer<T> : INetworkVariableSerializer<HashSet<T>> where T : IEquatable<T>
{ {
public NetworkVariableType Type => NetworkVariableType.Value;
public bool IsDistributedAuthorityOptimized => false;
public void WriteDistributedAuthority(FastBufferWriter writer, ref HashSet<T> value)
{
Write(writer, ref value);
}
public void ReadDistributedAuthority(FastBufferReader reader, ref HashSet<T> value)
{
Read(reader, ref value);
}
public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref HashSet<T> value, ref HashSet<T> previousValue) => Write(writer, ref value);
public void ReadDeltaDistributedAuthority(FastBufferReader reader, ref HashSet<T> value) => Read(reader, ref value);
public void Write(FastBufferWriter writer, ref HashSet<T> value) public void Write(FastBufferWriter writer, ref HashSet<T> value)
{ {
var isNull = value == null; var isNull = value == null;
@@ -428,6 +557,20 @@ namespace Unity.Netcode
internal class DictionarySerializer<TKey, TVal> : INetworkVariableSerializer<Dictionary<TKey, TVal>> internal class DictionarySerializer<TKey, TVal> : INetworkVariableSerializer<Dictionary<TKey, TVal>>
where TKey : IEquatable<TKey> where TKey : IEquatable<TKey>
{ {
public NetworkVariableType Type => NetworkVariableType.Value;
public bool IsDistributedAuthorityOptimized => false;
public void WriteDistributedAuthority(FastBufferWriter writer, ref Dictionary<TKey, TVal> value)
{
Write(writer, ref value);
}
public void ReadDistributedAuthority(FastBufferReader reader, ref Dictionary<TKey, TVal> value)
{
Read(reader, ref value);
}
public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref Dictionary<TKey, TVal> value, ref Dictionary<TKey, TVal> previousValue) => Write(writer, ref value);
public void ReadDeltaDistributedAuthority(FastBufferReader reader, ref Dictionary<TKey, TVal> value) => Read(reader, ref value);
public void Write(FastBufferWriter writer, ref Dictionary<TKey, TVal> value) public void Write(FastBufferWriter writer, ref Dictionary<TKey, TVal> value)
{ {
var isNull = value == null; var isNull = value == null;
@@ -505,6 +648,20 @@ namespace Unity.Netcode
internal class UnmanagedArraySerializer<T> : INetworkVariableSerializer<NativeArray<T>> where T : unmanaged internal class UnmanagedArraySerializer<T> : INetworkVariableSerializer<NativeArray<T>> where T : unmanaged
{ {
public NetworkVariableType Type => NetworkVariableType.Value;
public bool IsDistributedAuthorityOptimized => false;
public void WriteDistributedAuthority(FastBufferWriter writer, ref NativeArray<T> value)
{
Write(writer, ref value);
}
public void ReadDistributedAuthority(FastBufferReader reader, ref NativeArray<T> value)
{
Read(reader, ref value);
}
public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref NativeArray<T> value, ref NativeArray<T> previousValue) => Write(writer, ref value);
public void ReadDeltaDistributedAuthority(FastBufferReader reader, ref NativeArray<T> value) => Read(reader, ref value);
public void Write(FastBufferWriter writer, ref NativeArray<T> value) public void Write(FastBufferWriter writer, ref NativeArray<T> value)
{ {
writer.WriteUnmanagedSafe(value); writer.WriteUnmanagedSafe(value);
@@ -550,6 +707,20 @@ namespace Unity.Netcode
#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT #if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT
internal class UnmanagedListSerializer<T> : INetworkVariableSerializer<NativeList<T>> where T : unmanaged internal class UnmanagedListSerializer<T> : INetworkVariableSerializer<NativeList<T>> where T : unmanaged
{ {
public NetworkVariableType Type => NetworkVariableType.Value;
public bool IsDistributedAuthorityOptimized => false;
public void WriteDistributedAuthority(FastBufferWriter writer, ref NativeList<T> value)
{
Write(writer, ref value);
}
public void ReadDistributedAuthority(FastBufferReader reader, ref NativeList<T> value)
{
Read(reader, ref value);
}
public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref NativeList<T> value, ref NativeList<T> previousValue) => Write(writer, ref value);
public void ReadDeltaDistributedAuthority(FastBufferReader reader, ref NativeList<T> value) => Read(reader, ref value);
public void Write(FastBufferWriter writer, ref NativeList<T> value) public void Write(FastBufferWriter writer, ref NativeList<T> value)
{ {
writer.WriteUnmanagedSafe(value); writer.WriteUnmanagedSafe(value);
@@ -593,6 +764,21 @@ namespace Unity.Netcode
internal class NativeHashSetSerializer<T> : INetworkVariableSerializer<NativeHashSet<T>> where T : unmanaged, IEquatable<T> internal class NativeHashSetSerializer<T> : INetworkVariableSerializer<NativeHashSet<T>> where T : unmanaged, IEquatable<T>
{ {
public NetworkVariableType Type => NetworkVariableType.Value;
public bool IsDistributedAuthorityOptimized => false;
public void WriteDistributedAuthority(FastBufferWriter writer, ref NativeHashSet<T> value)
{
Write(writer, ref value);
}
public void ReadDistributedAuthority(FastBufferReader reader, ref NativeHashSet<T> value)
{
Read(reader, ref value);
}
public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref NativeHashSet<T> value, ref NativeHashSet<T> previousValue) => Write(writer, ref value);
public void ReadDeltaDistributedAuthority(FastBufferReader reader, ref NativeHashSet<T> value) => Read(reader, ref value);
public void Write(FastBufferWriter writer, ref NativeHashSet<T> value) public void Write(FastBufferWriter writer, ref NativeHashSet<T> value)
{ {
writer.WriteValueSafe(value); writer.WriteValueSafe(value);
@@ -638,6 +824,21 @@ namespace Unity.Netcode
where TKey : unmanaged, IEquatable<TKey> where TKey : unmanaged, IEquatable<TKey>
where TVal : unmanaged where TVal : unmanaged
{ {
public NetworkVariableType Type => NetworkVariableType.Value;
public bool IsDistributedAuthorityOptimized => false;
public void WriteDistributedAuthority(FastBufferWriter writer, ref NativeHashMap<TKey, TVal> value)
{
Write(writer, ref value);
}
public void ReadDistributedAuthority(FastBufferReader reader, ref NativeHashMap<TKey, TVal> value)
{
Read(reader, ref value);
}
public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref NativeHashMap<TKey, TVal> value, ref NativeHashMap<TKey, TVal> previousValue) => Write(writer, ref value);
public void ReadDeltaDistributedAuthority(FastBufferReader reader, ref NativeHashMap<TKey, TVal> value) => Read(reader, ref value);
public void Write(FastBufferWriter writer, ref NativeHashMap<TKey, TVal> value) public void Write(FastBufferWriter writer, ref NativeHashMap<TKey, TVal> value)
{ {
writer.WriteValueSafe(value); writer.WriteValueSafe(value);
@@ -680,12 +881,25 @@ namespace Unity.Netcode
#endif #endif
/// <summary> /// <summary>
/// Serializer for FixedStrings /// Serializer for FixedStrings
/// </summary> /// </summary>
/// <typeparam name="T"></typeparam> /// <typeparam name="T"></typeparam>
internal class FixedStringSerializer<T> : INetworkVariableSerializer<T> where T : unmanaged, INativeList<byte>, IUTF8Bytes internal class FixedStringSerializer<T> : INetworkVariableSerializer<T> where T : unmanaged, INativeList<byte>, IUTF8Bytes
{ {
// The item type can only be bytes for fixedStrings, so the DA runtime doesn't need details on it public NetworkVariableType Type => NetworkVariableType.Value;
public bool IsDistributedAuthorityOptimized => false;
public void WriteDistributedAuthority(FastBufferWriter writer, ref T value)
{
Write(writer, ref value);
}
public void ReadDistributedAuthority(FastBufferReader reader, ref T value)
{
Read(reader, ref value);
}
public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref T value, ref T previousValue) => Write(writer, ref value);
public void ReadDeltaDistributedAuthority(FastBufferReader reader, ref T value) => Read(reader, ref value);
public void Write(FastBufferWriter writer, ref T value) public void Write(FastBufferWriter writer, ref T value)
{ {
@@ -731,7 +945,7 @@ namespace Unity.Netcode
return; return;
} }
writer.WriteByte(0); // Flag that we're sending a delta writer.WriteByteSafe(0); // Flag that we're sending a delta
BytePacker.WriteValuePacked(writer, value.Length); BytePacker.WriteValuePacked(writer, value.Length);
writer.WriteValueSafe(changes); writer.WriteValueSafe(changes);
var ptr = value.GetUnsafePtr(); var ptr = value.GetUnsafePtr();
@@ -779,7 +993,7 @@ namespace Unity.Netcode
{ {
if (changes.IsSet(i)) if (changes.IsSet(i))
{ {
reader.ReadByte(out ptr[i]); reader.ReadByteSafe(out ptr[i]);
} }
} }
} }
@@ -802,6 +1016,20 @@ namespace Unity.Netcode
/// <typeparam name="T"></typeparam> /// <typeparam name="T"></typeparam>
internal class FixedStringArraySerializer<T> : INetworkVariableSerializer<NativeArray<T>> where T : unmanaged, INativeList<byte>, IUTF8Bytes internal class FixedStringArraySerializer<T> : INetworkVariableSerializer<NativeArray<T>> where T : unmanaged, INativeList<byte>, IUTF8Bytes
{ {
public NetworkVariableType Type => NetworkVariableType.Value;
public bool IsDistributedAuthorityOptimized => false;
public void WriteDistributedAuthority(FastBufferWriter writer, ref NativeArray<T> value)
{
Write(writer, ref value);
}
public void ReadDistributedAuthority(FastBufferReader reader, ref NativeArray<T> value)
{
Read(reader, ref value);
}
public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref NativeArray<T> value, ref NativeArray<T> previousValue) => Write(writer, ref value);
public void ReadDeltaDistributedAuthority(FastBufferReader reader, ref NativeArray<T> value) => Read(reader, ref value);
public void Write(FastBufferWriter writer, ref NativeArray<T> value) public void Write(FastBufferWriter writer, ref NativeArray<T> value)
{ {
writer.WriteValueSafe(value); writer.WriteValueSafe(value);
@@ -852,6 +1080,21 @@ namespace Unity.Netcode
/// <typeparam name="T"></typeparam> /// <typeparam name="T"></typeparam>
internal class FixedStringListSerializer<T> : INetworkVariableSerializer<NativeList<T>> where T : unmanaged, INativeList<byte>, IUTF8Bytes internal class FixedStringListSerializer<T> : INetworkVariableSerializer<NativeList<T>> where T : unmanaged, INativeList<byte>, IUTF8Bytes
{ {
public NetworkVariableType Type => NetworkVariableType.Value;
public bool IsDistributedAuthorityOptimized => false;
public void WriteDistributedAuthority(FastBufferWriter writer, ref NativeList<T> value)
{
Write(writer, ref value);
}
public void ReadDistributedAuthority(FastBufferReader reader, ref NativeList<T> value)
{
Read(reader, ref value);
}
public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref NativeList<T> value, ref NativeList<T> previousValue) => Write(writer, ref value);
public void ReadDeltaDistributedAuthority(FastBufferReader reader, ref NativeList<T> value) => Read(reader, ref value);
public void Write(FastBufferWriter writer, ref NativeList<T> value) public void Write(FastBufferWriter writer, ref NativeList<T> value)
{ {
writer.WriteValueSafe(value); writer.WriteValueSafe(value);
@@ -899,6 +1142,21 @@ namespace Unity.Netcode
/// <typeparam name="T"></typeparam> /// <typeparam name="T"></typeparam>
internal class UnmanagedNetworkSerializableSerializer<T> : INetworkVariableSerializer<T> where T : unmanaged, INetworkSerializable internal class UnmanagedNetworkSerializableSerializer<T> : INetworkVariableSerializer<T> where T : unmanaged, INetworkSerializable
{ {
public NetworkVariableType Type => NetworkVariableType.Value;
public bool IsDistributedAuthorityOptimized => false;
public void WriteDistributedAuthority(FastBufferWriter writer, ref T value)
{
Write(writer, ref value);
}
public void ReadDistributedAuthority(FastBufferReader reader, ref T value)
{
Read(reader, ref value);
}
public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref T value, ref T previousValue) => Write(writer, ref value);
public void ReadDeltaDistributedAuthority(FastBufferReader reader, ref T value) => Read(reader, ref value);
public void Write(FastBufferWriter writer, ref T value) public void Write(FastBufferWriter writer, ref T value)
{ {
var bufferSerializer = new BufferSerializer<BufferSerializerWriter>(new BufferSerializerWriter(writer)); var bufferSerializer = new BufferSerializer<BufferSerializerWriter>(new BufferSerializerWriter(writer));
@@ -951,6 +1209,20 @@ namespace Unity.Netcode
/// <typeparam name="T"></typeparam> /// <typeparam name="T"></typeparam>
internal class UnmanagedNetworkSerializableArraySerializer<T> : INetworkVariableSerializer<NativeArray<T>> where T : unmanaged, INetworkSerializable internal class UnmanagedNetworkSerializableArraySerializer<T> : INetworkVariableSerializer<NativeArray<T>> where T : unmanaged, INetworkSerializable
{ {
public NetworkVariableType Type => NetworkVariableType.Value;
public bool IsDistributedAuthorityOptimized => false;
public void WriteDistributedAuthority(FastBufferWriter writer, ref NativeArray<T> value)
{
Write(writer, ref value);
}
public void ReadDistributedAuthority(FastBufferReader reader, ref NativeArray<T> value)
{
Read(reader, ref value);
}
public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref NativeArray<T> value, ref NativeArray<T> previousValue) => Write(writer, ref value);
public void ReadDeltaDistributedAuthority(FastBufferReader reader, ref NativeArray<T> value) => Read(reader, ref value);
public void Write(FastBufferWriter writer, ref NativeArray<T> value) public void Write(FastBufferWriter writer, ref NativeArray<T> value)
{ {
writer.WriteNetworkSerializable(value); writer.WriteNetworkSerializable(value);
@@ -1001,6 +1273,21 @@ namespace Unity.Netcode
/// <typeparam name="T"></typeparam> /// <typeparam name="T"></typeparam>
internal class UnmanagedNetworkSerializableListSerializer<T> : INetworkVariableSerializer<NativeList<T>> where T : unmanaged, INetworkSerializable internal class UnmanagedNetworkSerializableListSerializer<T> : INetworkVariableSerializer<NativeList<T>> where T : unmanaged, INetworkSerializable
{ {
public NetworkVariableType Type => NetworkVariableType.Value;
public bool IsDistributedAuthorityOptimized => false;
public void WriteDistributedAuthority(FastBufferWriter writer, ref NativeList<T> value)
{
Write(writer, ref value);
}
public void ReadDistributedAuthority(FastBufferReader reader, ref NativeList<T> value)
{
Read(reader, ref value);
}
public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref NativeList<T> value, ref NativeList<T> previousValue) => Write(writer, ref value);
public void ReadDeltaDistributedAuthority(FastBufferReader reader, ref NativeList<T> value) => Read(reader, ref value);
public void Write(FastBufferWriter writer, ref NativeList<T> value) public void Write(FastBufferWriter writer, ref NativeList<T> value)
{ {
writer.WriteNetworkSerializable(value); writer.WriteNetworkSerializable(value);
@@ -1048,6 +1335,20 @@ namespace Unity.Netcode
/// <typeparam name="T"></typeparam> /// <typeparam name="T"></typeparam>
internal class ManagedNetworkSerializableSerializer<T> : INetworkVariableSerializer<T> where T : class, INetworkSerializable, new() internal class ManagedNetworkSerializableSerializer<T> : INetworkVariableSerializer<T> where T : class, INetworkSerializable, new()
{ {
public NetworkVariableType Type => NetworkVariableType.Value;
public bool IsDistributedAuthorityOptimized => false;
public void WriteDistributedAuthority(FastBufferWriter writer, ref T value)
{
Write(writer, ref value);
}
public void ReadDistributedAuthority(FastBufferReader reader, ref T value)
{
Read(reader, ref value);
}
public void WriteDeltaDistributedAuthority(FastBufferWriter writer, ref T value, ref T previousValue) => Write(writer, ref value);
public void ReadDeltaDistributedAuthority(FastBufferReader reader, ref T value) => Read(reader, ref value);
public void Write(FastBufferWriter writer, ref T value) public void Write(FastBufferWriter writer, ref T value)
{ {
var bufferSerializer = new BufferSerializer<BufferSerializerWriter>(new BufferSerializerWriter(writer)); var bufferSerializer = new BufferSerializer<BufferSerializerWriter>(new BufferSerializerWriter(writer));

View File

@@ -0,0 +1,54 @@
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
namespace Unity.Netcode
{
internal static class SerializationTools
{
public delegate void WriteDelegate<T>(FastBufferWriter writer, ref T value);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteWithSize<T>(WriteDelegate<T> writeMethod, FastBufferWriter writer, ref T value)
{
var writePos = writer.Position;
// Note: This value can't be packed because we don't know how large it will be in advance
// we reserve space for it, then write the data, then come back and fill in the space
// to pack here, we'd have to write data to a temporary buffer and copy it in - which
// isn't worth possibly saving one byte if and only if the data is less than 63 bytes long...
// The way we do packing, any value > 63 in a ushort will use the full 2 bytes to represent.
writer.WriteValueSafe((ushort)0);
var startPos = writer.Position;
writeMethod(writer, ref value);
var size = writer.Position - startPos;
writer.Seek(writePos);
writer.WriteValueSafe((ushort)size);
writer.Seek(startPos + size);
}
public delegate void ReadDelegate<T>(FastBufferReader writer, ref T value);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ReadWithSize<T>(ReadDelegate<T> readMethod, FastBufferReader reader, ref T value)
{
reader.ReadValueSafe(out ushort _);
readMethod(reader, ref value);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteType(FastBufferWriter writer, NetworkVariableType type) => writer.WriteValueSafe(type);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ReadType<T>(FastBufferReader reader, INetworkVariableSerializer<T> serializer)
{
reader.ReadValueSafe(out NetworkVariableType type);
if (type != serializer.Type)
{
throw new SerializationException();
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 52a4ce368df54b0a8887c08f3402bcd3
timeCreated: 1718300602

View File

@@ -227,6 +227,11 @@ namespace Unity.Netcode
foreach (var sceneToUnload in m_ScenesToUnload) foreach (var sceneToUnload in m_ScenesToUnload)
{ {
SceneManager.UnloadSceneAsync(sceneToUnload); SceneManager.UnloadSceneAsync(sceneToUnload);
// Update the ScenesLoaded when we unload scenes
if (sceneManager.ScenesLoaded.ContainsKey(sceneToUnload.handle))
{
sceneManager.ScenesLoaded.Remove(sceneToUnload.handle);
}
} }
} }
@@ -328,8 +333,9 @@ namespace Unity.Netcode
public void SetClientSynchronizationMode(ref NetworkManager networkManager, LoadSceneMode mode) public void SetClientSynchronizationMode(ref NetworkManager networkManager, LoadSceneMode mode)
{ {
var sceneManager = networkManager.SceneManager; var sceneManager = networkManager.SceneManager;
// Don't let non-authority set this value // In client-server, we don't let client's set this value.
if ((!networkManager.DistributedAuthorityMode && !networkManager.IsServer) || (networkManager.DistributedAuthorityMode && !networkManager.LocalClient.IsSessionOwner)) // In distributed authority, since session owner can be promoted clients can set this value
if (!networkManager.DistributedAuthorityMode && !networkManager.IsServer)
{ {
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{ {
@@ -338,7 +344,7 @@ namespace Unity.Netcode
return; return;
} }
else // Warn users if they are changing this after there are clients already connected and synchronized else // Warn users if they are changing this after there are clients already connected and synchronized
if (networkManager.ConnectedClientsIds.Count > (networkManager.IsHost ? 1 : 0) && sceneManager.ClientSynchronizationMode != mode) if (!networkManager.DistributedAuthorityMode && networkManager.ConnectedClientsIds.Count > (networkManager.IsHost ? 1 : 0) && sceneManager.ClientSynchronizationMode != mode)
{ {
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{ {

View File

@@ -774,6 +774,7 @@ namespace Unity.Netcode
/// <summary> /// <summary>
/// This setting changes how clients handle scene loading when initially synchronizing with the server.<br /> /// This setting changes how clients handle scene loading when initially synchronizing with the server.<br />
/// The server or host should set this value as clients will automatically be synchronized with the server (or host) side. /// The server or host should set this value as clients will automatically be synchronized with the server (or host) side.
/// </summary>
/// <remarks> /// <remarks>
/// <b>LoadSceneMode.Single:</b> All currently loaded scenes on the client will be unloaded and the /// <b>LoadSceneMode.Single:</b> All currently loaded scenes on the client will be unloaded and the
/// server's currently active scene will be loaded in single mode on the client unless it was already /// server's currently active scene will be loaded in single mode on the client unless it was already
@@ -2406,16 +2407,6 @@ namespace Unity.Netcode
NetworkManager.ConnectionManager.CreateAndSpawnPlayer(NetworkManager.LocalClientId); NetworkManager.ConnectionManager.CreateAndSpawnPlayer(NetworkManager.LocalClientId);
} }
// Client is now synchronized and fully "connected". This also means the client can send "RPCs" at this time
NetworkManager.ConnectionManager.InvokeOnClientConnectedCallback(NetworkManager.LocalClientId);
// Notify the client that they have finished synchronizing
OnSceneEvent?.Invoke(new SceneEvent()
{
SceneEventType = sceneEventData.SceneEventType,
ClientId = NetworkManager.LocalClientId, // Client sent this to the server
});
// Process any SceneEventType.ObjectSceneChanged messages that // Process any SceneEventType.ObjectSceneChanged messages that
// were deferred while synchronizing and migrate the associated // were deferred while synchronizing and migrate the associated
// NetworkObjects to their newly assigned scenes. // NetworkObjects to their newly assigned scenes.
@@ -2429,6 +2420,16 @@ namespace Unity.Netcode
SceneManagerHandler.UnloadUnassignedScenes(NetworkManager); SceneManagerHandler.UnloadUnassignedScenes(NetworkManager);
} }
// Client is now synchronized and fully "connected". This also means the client can send "RPCs" at this time
NetworkManager.ConnectionManager.InvokeOnClientConnectedCallback(NetworkManager.LocalClientId);
// Notify the client that they have finished synchronizing
OnSceneEvent?.Invoke(new SceneEvent()
{
SceneEventType = sceneEventData.SceneEventType,
ClientId = NetworkManager.LocalClientId, // Client sent this to the server
});
OnSynchronizeComplete?.Invoke(NetworkManager.LocalClientId); OnSynchronizeComplete?.Invoke(NetworkManager.LocalClientId);
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer) if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
@@ -2436,10 +2437,7 @@ namespace Unity.Netcode
NetworkLog.LogInfo($"[Client-{NetworkManager.LocalClientId}][Scene Management Enabled] Synchronization complete!"); NetworkLog.LogInfo($"[Client-{NetworkManager.LocalClientId}][Scene Management Enabled] Synchronization complete!");
} }
// For convenience, notify all NetworkBehaviours that synchronization is complete. // For convenience, notify all NetworkBehaviours that synchronization is complete.
foreach (var networkObject in NetworkManager.SpawnManager.SpawnedObjectsList) NetworkManager.SpawnManager.NotifyNetworkObjectsSynchronized();
{
networkObject.InternalNetworkSessionSynchronized();
}
if (NetworkManager.DistributedAuthorityMode && HasSceneAuthority() && IsRestoringSession) if (NetworkManager.DistributedAuthorityMode && HasSceneAuthority() && IsRestoringSession)
{ {

View File

@@ -643,7 +643,7 @@ namespace Unity.Netcode
return null; return null;
} }
ownerClientId = NetworkManager.DistributedAuthorityMode ? NetworkManager.LocalClientId : NetworkManager.ServerClientId; ownerClientId = NetworkManager.DistributedAuthorityMode ? NetworkManager.LocalClientId : ownerClientId;
// We only need to check for authority when running in client-server mode // We only need to check for authority when running in client-server mode
if (!NetworkManager.IsServer && !NetworkManager.DistributedAuthorityMode) if (!NetworkManager.IsServer && !NetworkManager.DistributedAuthorityMode)
{ {
@@ -1820,11 +1820,14 @@ namespace Unity.Netcode
return; return;
} }
var currentTick = serverTime.Tick; var currentTick = serverTime.Tick;
var deferredCallbackObjects = DeferredDespawnObjects.Where((c) => c.HasDeferredDespawnCheck); var deferredCallbackCount = DeferredDespawnObjects.Count();
var deferredCallbackCount = deferredCallbackObjects.Count(); for (int i = 0; i < deferredCallbackCount; i++)
for (int i = 0; i < deferredCallbackCount - 1; i++)
{ {
var deferredObjectEntry = deferredCallbackObjects.ElementAt(i); var deferredObjectEntry = DeferredDespawnObjects[i];
if (!deferredObjectEntry.HasDeferredDespawnCheck)
{
continue;
}
var networkObject = SpawnedObjects[deferredObjectEntry.NetworkObjectId]; var networkObject = SpawnedObjects[deferredObjectEntry.NetworkObjectId];
// Double check to make sure user did not remove the callback // Double check to make sure user did not remove the callback
if (networkObject.OnDeferredDespawnComplete != null) if (networkObject.OnDeferredDespawnComplete != null)
@@ -1849,9 +1852,15 @@ namespace Unity.Netcode
} }
} }
var despawnObjects = DeferredDespawnObjects.Where((c) => c.TickToDespawn < currentTick).ToList(); // Parse backwards so we can remove objects as we parse through them
foreach (var deferredObjectEntry in despawnObjects) for (int i = DeferredDespawnObjects.Count - 1; i >= 0; i--)
{ {
var deferredObjectEntry = DeferredDespawnObjects[i];
if (deferredObjectEntry.TickToDespawn >= currentTick)
{
continue;
}
if (!SpawnedObjects.ContainsKey(deferredObjectEntry.NetworkObjectId)) if (!SpawnedObjects.ContainsKey(deferredObjectEntry.NetworkObjectId))
{ {
DeferredDespawnObjects.Remove(deferredObjectEntry); DeferredDespawnObjects.Remove(deferredObjectEntry);
@@ -1863,5 +1872,16 @@ namespace Unity.Netcode
DeferredDespawnObjects.Remove(deferredObjectEntry); DeferredDespawnObjects.Remove(deferredObjectEntry);
} }
} }
internal void NotifyNetworkObjectsSynchronized()
{
// Users could spawn NetworkObjects during these notifications.
// Create a separate list from the hashset to avoid list modification errors.
var spawnedObjects = SpawnedObjectsList.ToList();
foreach (var networkObject in spawnedObjects)
{
networkObject.InternalNetworkSessionSynchronized();
}
}
} }
} }

View File

@@ -428,6 +428,31 @@ namespace Unity.Netcode.Transports.UTP
protected NetworkDriver m_Driver; protected NetworkDriver m_Driver;
/// <summary>
/// Gets a reference to the <see cref="Networking.Transport.NetworkDriver"/>.
/// </summary>
/// <returns>ref <see cref="Networking.Transport.NetworkDriver"/></returns>
public ref NetworkDriver GetNetworkDriver()
{
return ref m_Driver;
}
/// <summary>
/// Gets the local sytem's <see cref="NetworkEndpoint"/> that is assigned for the current network session.
/// </summary>
/// <remarks>
/// If the driver is not created it will return an invalid <see cref="NetworkEndpoint"/>.
/// </remarks>
/// <returns><see cref="NetworkEndpoint"/></returns>
public NetworkEndpoint GetLocalEndpoint()
{
if (m_Driver.IsCreated)
{
return m_Driver.GetLocalEndpoint();
}
return new NetworkEndpoint();
}
private PacketLossCache m_PacketLossCache = new PacketLossCache(); private PacketLossCache m_PacketLossCache = new PacketLossCache();
private State m_State = State.Disconnected; private State m_State = State.Disconnected;
@@ -450,7 +475,10 @@ namespace Unity.Netcode.Transports.UTP
private RelayServerData m_RelayServerData; private RelayServerData m_RelayServerData;
internal NetworkManager NetworkManager; /// <summary>
/// NetworkManager associated to this transport instance
/// </summary>
protected NetworkManager m_NetworkManager;
private IRealTimeProvider m_RealTimeProvider; private IRealTimeProvider m_RealTimeProvider;
@@ -795,10 +823,10 @@ namespace Unity.Netcode.Transports.UTP
} }
var mtu = 0; var mtu = 0;
if (NetworkManager) if (m_NetworkManager)
{ {
var ngoClientId = NetworkManager.ConnectionManager.TransportIdToClientId(sendTarget.ClientId); var ngoClientId = m_NetworkManager.ConnectionManager.TransportIdToClientId(sendTarget.ClientId);
mtu = NetworkManager.GetPeerMTU(ngoClientId); mtu = m_NetworkManager.GetPeerMTU(ngoClientId);
} }
new SendBatchedMessagesJob new SendBatchedMessagesJob
@@ -947,7 +975,7 @@ namespace Unity.Netcode.Transports.UTP
} }
#if MULTIPLAYER_TOOLS_1_0_0_PRE_7 #if MULTIPLAYER_TOOLS_1_0_0_PRE_7
if (NetworkManager) if (m_NetworkManager)
{ {
ExtractNetworkMetrics(); ExtractNetworkMetrics();
} }
@@ -963,16 +991,16 @@ namespace Unity.Netcode.Transports.UTP
#if MULTIPLAYER_TOOLS_1_0_0_PRE_7 #if MULTIPLAYER_TOOLS_1_0_0_PRE_7
private void ExtractNetworkMetrics() private void ExtractNetworkMetrics()
{ {
if (NetworkManager.IsServer) if (m_NetworkManager.IsServer)
{ {
var ngoConnectionIds = NetworkManager.ConnectedClients.Keys; var ngoConnectionIds = m_NetworkManager.ConnectedClients.Keys;
foreach (var ngoConnectionId in ngoConnectionIds) foreach (var ngoConnectionId in ngoConnectionIds)
{ {
if (ngoConnectionId == 0 && NetworkManager.IsHost) if (ngoConnectionId == 0 && m_NetworkManager.IsHost)
{ {
continue; continue;
} }
var transportClientId = NetworkManager.ConnectionManager.ClientIdToTransportId(ngoConnectionId); var transportClientId = m_NetworkManager.ConnectionManager.ClientIdToTransportId(ngoConnectionId);
ExtractNetworkMetricsForClient(transportClientId); ExtractNetworkMetricsForClient(transportClientId);
} }
} }
@@ -992,10 +1020,10 @@ namespace Unity.Netcode.Transports.UTP
ExtractNetworkMetricsFromPipeline(m_UnreliableSequencedFragmentedPipeline, networkConnection); ExtractNetworkMetricsFromPipeline(m_UnreliableSequencedFragmentedPipeline, networkConnection);
ExtractNetworkMetricsFromPipeline(m_ReliableSequencedPipeline, networkConnection); ExtractNetworkMetricsFromPipeline(m_ReliableSequencedPipeline, networkConnection);
var rttValue = NetworkManager.IsServer ? 0 : ExtractRtt(networkConnection); var rttValue = m_NetworkManager.IsServer ? 0 : ExtractRtt(networkConnection);
NetworkMetrics.UpdateRttToServer(rttValue); NetworkMetrics.UpdateRttToServer(rttValue);
var packetLoss = NetworkManager.IsServer ? 0 : ExtractPacketLoss(networkConnection); var packetLoss = m_NetworkManager.IsServer ? 0 : ExtractPacketLoss(networkConnection);
NetworkMetrics.UpdatePacketLoss(packetLoss); NetworkMetrics.UpdatePacketLoss(packetLoss);
} }
@@ -1199,9 +1227,9 @@ namespace Unity.Netcode.Transports.UTP
// use the transport client ID) or from a user (which will be using the NGO client ID). // use the transport client ID) or from a user (which will be using the NGO client ID).
// So we just try both cases (ExtractRtt returns 0 for invalid connections). // So we just try both cases (ExtractRtt returns 0 for invalid connections).
if (NetworkManager != null) if (m_NetworkManager != null)
{ {
var transportId = NetworkManager.ConnectionManager.ClientIdToTransportId(clientId); var transportId = m_NetworkManager.ConnectionManager.ClientIdToTransportId(clientId);
var rtt = ExtractRtt(ParseClientId(transportId)); var rtt = ExtractRtt(ParseClientId(transportId));
if (rtt > 0) if (rtt > 0)
@@ -1221,14 +1249,14 @@ namespace Unity.Netcode.Transports.UTP
{ {
Debug.Assert(sizeof(ulong) == UnsafeUtility.SizeOf<NetworkConnection>(), "Netcode connection id size does not match UTP connection id size"); Debug.Assert(sizeof(ulong) == UnsafeUtility.SizeOf<NetworkConnection>(), "Netcode connection id size does not match UTP connection id size");
NetworkManager = networkManager; m_NetworkManager = networkManager;
if (NetworkManager && NetworkManager.PortOverride.Overidden) if (m_NetworkManager && m_NetworkManager.PortOverride.Overidden)
{ {
ConnectionData.Port = NetworkManager.PortOverride.Value; ConnectionData.Port = m_NetworkManager.PortOverride.Value;
} }
m_RealTimeProvider = NetworkManager ? NetworkManager.RealTimeProvider : new RealTimeProvider(); m_RealTimeProvider = m_NetworkManager ? m_NetworkManager.RealTimeProvider : new RealTimeProvider();
m_NetworkSettings = new NetworkSettings(Allocator.Persistent); m_NetworkSettings = new NetworkSettings(Allocator.Persistent);
@@ -1322,7 +1350,7 @@ namespace Unity.Netcode.Transports.UTP
// provide any reliability guarantees anymore. Disconnect the client since at // provide any reliability guarantees anymore. Disconnect the client since at
// this point they're bound to become desynchronized. // this point they're bound to become desynchronized.
var ngoClientId = NetworkManager?.ConnectionManager.TransportIdToClientId(clientId) ?? clientId; var ngoClientId = m_NetworkManager?.ConnectionManager.TransportIdToClientId(clientId) ?? clientId;
Debug.LogError($"Couldn't add payload of size {payload.Count} to reliable send queue. " + Debug.LogError($"Couldn't add payload of size {payload.Count} to reliable send queue. " +
$"Closing connection {ngoClientId} as reliability guarantees can't be maintained."); $"Closing connection {ngoClientId} as reliability guarantees can't be maintained.");
@@ -1479,7 +1507,7 @@ namespace Unity.Netcode.Transports.UTP
protected override NetworkTopologyTypes OnCurrentTopology() protected override NetworkTopologyTypes OnCurrentTopology()
{ {
return NetworkManager != null ? NetworkManager.NetworkConfig.NetworkTopology : NetworkTopologyTypes.ClientServer; return m_NetworkManager != null ? m_NetworkManager.NetworkConfig.NetworkTopology : NetworkTopologyTypes.ClientServer;
} }
private string m_ServerPrivateKey; private string m_ServerPrivateKey;
@@ -1555,7 +1583,7 @@ namespace Unity.Netcode.Transports.UTP
heartbeatTimeoutMS: transport.m_HeartbeatTimeoutMS); heartbeatTimeoutMS: transport.m_HeartbeatTimeoutMS);
#if UNITY_WEBGL && !UNITY_EDITOR #if UNITY_WEBGL && !UNITY_EDITOR
if (NetworkManager.IsServer && m_ProtocolType != ProtocolType.RelayUnityTransport) if (m_NetworkManager.IsServer && m_ProtocolType != ProtocolType.RelayUnityTransport)
{ {
throw new Exception("WebGL as a server is not supported by Unity Transport, outside the Editor."); throw new Exception("WebGL as a server is not supported by Unity Transport, outside the Editor.");
} }
@@ -1577,7 +1605,7 @@ namespace Unity.Netcode.Transports.UTP
} }
else else
{ {
if (NetworkManager.IsServer) if (m_NetworkManager.IsServer)
{ {
if (string.IsNullOrEmpty(m_ServerCertificate) || string.IsNullOrEmpty(m_ServerPrivateKey)) if (string.IsNullOrEmpty(m_ServerCertificate) || string.IsNullOrEmpty(m_ServerPrivateKey))
{ {

View File

@@ -72,6 +72,11 @@
"name": "com.unity.services.multiplayer", "name": "com.unity.services.multiplayer",
"expression": "0.2.0", "expression": "0.2.0",
"define": "MULTIPLAYER_SERVICES_SDK_INSTALLED" "define": "MULTIPLAYER_SERVICES_SDK_INSTALLED"
},
{
"name": "Unity",
"expression": "6000.0.11f1",
"define": "COM_UNITY_MODULES_PHYSICS2D_LINEAR"
} }
], ],
"noEngineReferences": false "noEngineReferences": false

View File

@@ -677,6 +677,11 @@ namespace Unity.Netcode.TestHelpers.Runtime
foreach (var sceneToUnload in m_ScenesToUnload) foreach (var sceneToUnload in m_ScenesToUnload)
{ {
SceneManager.UnloadSceneAsync(sceneToUnload.Key); SceneManager.UnloadSceneAsync(sceneToUnload.Key);
// Update the ScenesLoaded when we unload scenes
if (sceneManager.ScenesLoaded.ContainsKey(sceneToUnload.Key.handle))
{
sceneManager.ScenesLoaded.Remove(sceneToUnload.Key.handle);
}
} }
} }
@@ -795,8 +800,9 @@ namespace Unity.Netcode.TestHelpers.Runtime
var sceneManager = networkManager.SceneManager; var sceneManager = networkManager.SceneManager;
// Don't let client's set this value // In client-server, we don't let client's set this value.
if (!networkManager.IsServer) // In dsitributed authority, since session owner can be promoted clients can set this value
if (!networkManager.DistributedAuthorityMode && !networkManager.IsServer)
{ {
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{ {
@@ -804,7 +810,7 @@ namespace Unity.Netcode.TestHelpers.Runtime
} }
return; return;
} }
else if (networkManager.ConnectedClientsIds.Count > (networkManager.IsHost ? 1 : 0) && sceneManager.ClientSynchronizationMode != mode) else if (!networkManager.DistributedAuthorityMode && networkManager.ConnectedClientsIds.Count > (networkManager.IsHost ? 1 : 0) && sceneManager.ClientSynchronizationMode != mode)
{ {
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{ {

View File

@@ -303,6 +303,11 @@ namespace Unity.Netcode.TestHelpers.Runtime
OnOneTimeSetup(); OnOneTimeSetup();
VerboseDebug($"Exiting {nameof(OneTimeSetup)}"); VerboseDebug($"Exiting {nameof(OneTimeSetup)}");
#if DEVELOPMENT_BUILD || UNITY_EDITOR
// Default to not log the serialized type not optimized warning message when testing.
NetworkManager.DisableNotOptimizedSerializedType = true;
#endif
} }
/// <summary> /// <summary>

View File

@@ -620,7 +620,7 @@ namespace Unity.Netcode.TestHelpers.Runtime
/// Similar to WaitForClientConnected, this waits for multiple clients to be connected. /// Similar to WaitForClientConnected, this waits for multiple clients to be connected.
/// </summary> /// </summary>
/// <param name="clients">The clients to be connected</param> /// <param name="clients">The clients to be connected</param>
/// <param name="result">The result. If null, it will automatically assert<</param> /// <param name="result">The result. If null, it will automatically assert</param>
/// <param name="maxFrames">The max frames to wait for</param> /// <param name="maxFrames">The max frames to wait for</param>
/// <returns></returns> /// <returns></returns>
public static IEnumerator WaitForClientsConnected(NetworkManager[] clients, ResultWrapper<bool> result = null, float timeout = DefaultTimeout) public static IEnumerator WaitForClientsConnected(NetworkManager[] clients, ResultWrapper<bool> result = null, float timeout = DefaultTimeout)

View File

@@ -5,11 +5,13 @@ namespace Unity.Netcode.TestHelpers.Runtime
{ {
/// <summary> /// <summary>
/// Will automatically register for the NetworkVariable OnValueChanged /// Will automatically register for the NetworkVariable OnValueChanged
/// delegate handler. It then will expose that single delegate invocation /// delegate handler. It then will expose that single delegate invocation
/// to anything that registers for this NetworkVariableHelper's instance's OnValueChanged event. /// to anything that registers for this NetworkVariableHelper's instance's OnValueChanged event.
/// This allows us to register any NetworkVariable type as well as there are basically two "types of types": /// This allows us to register any NetworkVariable type as well as there are basically two "types of types":
/// IEquatable<T> /// <list type="bullet">
/// ValueType /// <item>IEquatable&lt;T&gt;</item>
/// <item>ValueType</item>
/// </list>
/// From both we can then at least determine if the value indeed changed /// From both we can then at least determine if the value indeed changed
/// </summary> /// </summary>
/// <typeparam name="T"></typeparam> /// <typeparam name="T"></typeparam>
@@ -20,7 +22,7 @@ namespace Unity.Netcode.TestHelpers.Runtime
public event OnMyValueChangedDelegateHandler OnValueChanged; public event OnMyValueChangedDelegateHandler OnValueChanged;
/// <summary> /// <summary>
/// IEquatable<T> Equals Check /// IEquatable&lt;T&gt; Equals Check
/// </summary> /// </summary>
private void CheckVariableChanged(IEquatable<T> previous, IEquatable<T> next) private void CheckVariableChanged(IEquatable<T> previous, IEquatable<T> next)
{ {

View File

@@ -38,6 +38,7 @@ namespace Unity.Netcode.RuntimeTests
internal class TestNetworkComponent : NetworkBehaviour internal class TestNetworkComponent : NetworkBehaviour
{ {
public NetworkList<int> MyNetworkList = new NetworkList<int>(new List<int> { 1, 2, 3 }); public NetworkList<int> MyNetworkList = new NetworkList<int>(new List<int> { 1, 2, 3 });
public NetworkVariable<int> MyNetworkVar = new NetworkVariable<int>(3);
[Rpc(SendTo.NotAuthority)] [Rpc(SendTo.NotAuthority)]
public void TestNotAuthorityRpc(byte[] _) public void TestNotAuthorityRpc(byte[] _)
@@ -87,7 +88,7 @@ namespace Unity.Netcode.RuntimeTests
Client.LogLevel = LogLevel.Developer; Client.LogLevel = LogLevel.Developer;
// Validate we are in distributed authority mode with client side spawning and using CMB Service // Validate we are in distributed authority mode with client side spawning and using CMB Service
Assert.True(Client.DistributedAuthorityMode, "Distributed authority is not set!"); Assert.True(Client.NetworkConfig.NetworkTopology == NetworkTopologyTypes.DistributedAuthority, "Distributed authority topology is not set!");
Assert.True(Client.AutoSpawnPlayerPrefabClientSide, "Client side spawning is not set!"); Assert.True(Client.AutoSpawnPlayerPrefabClientSide, "Client side spawning is not set!");
Assert.True(Client.CMBServiceConnection, "CMBServiceConnection is not set!"); Assert.True(Client.CMBServiceConnection, "CMBServiceConnection is not set!");
@@ -101,6 +102,9 @@ namespace Unity.Netcode.RuntimeTests
protected override IEnumerator OnStartedServerAndClients() protected override IEnumerator OnStartedServerAndClients()
{ {
// Validate the NetworkManager are in distributed authority mode
Assert.True(Client.DistributedAuthorityMode, "Distributed authority is not set!");
// Register hooks after starting clients and server (in this case just the one client) // Register hooks after starting clients and server (in this case just the one client)
// We do this at this point in time because the MessageManager exists (happens within the same call stack when starting NetworkManagers) // We do this at this point in time because the MessageManager exists (happens within the same call stack when starting NetworkManagers)
m_ClientCodecHook = new CodecTestHooks(); m_ClientCodecHook = new CodecTestHooks();
@@ -218,6 +222,47 @@ namespace Unity.Netcode.RuntimeTests
yield return SendMessage(ref message); yield return SendMessage(ref message);
} }
[UnityTest]
public IEnumerator NetworkVariableDelta_WithValueUpdate()
{
var networkObj = CreateNetworkObjectPrefab("TestObject");
networkObj.AddComponent<TestNetworkComponent>();
var instance = SpawnObject(networkObj, Client);
yield return m_ClientCodecHook.WaitForMessageReceived<CreateObjectMessage>();
var component = instance.GetComponent<TestNetworkComponent>();
var newValue = 5;
component.MyNetworkVar.Value = newValue;
yield return m_ClientCodecHook.WaitForMessageReceived<NetworkVariableDeltaMessage>();
Assert.AreEqual(newValue, component.MyNetworkVar.Value);
}
[UnityTest]
public IEnumerator NetworkListDelta_WithValueUpdate()
{
var networkObj = CreateNetworkObjectPrefab("TestObject");
networkObj.AddComponent<TestNetworkComponent>();
var instance = SpawnObject(networkObj, Client);
yield return m_ClientCodecHook.WaitForMessageReceived<CreateObjectMessage>();
var component = instance.GetComponent<TestNetworkComponent>();
component.MyNetworkList.Add(5);
yield return m_ClientCodecHook.WaitForMessageReceived<NetworkVariableDeltaMessage>();
component.MyNetworkList.Add(6);
component.MyNetworkList.Add(7);
yield return m_ClientCodecHook.WaitForMessageReceived<NetworkVariableDeltaMessage>();
component.MyNetworkList.Insert(1, 8);
yield return m_ClientCodecHook.WaitForMessageReceived<NetworkVariableDeltaMessage>();
component.MyNetworkList.Insert(8, 11);
yield return m_ClientCodecHook.WaitForMessageReceived<NetworkVariableDeltaMessage>();
component.MyNetworkList.Remove(6);
yield return m_ClientCodecHook.WaitForMessageReceived<NetworkVariableDeltaMessage>();
component.MyNetworkList.RemoveAt(2);
yield return m_ClientCodecHook.WaitForMessageReceived<NetworkVariableDeltaMessage>();
component.MyNetworkList.Clear();
yield return m_ClientCodecHook.WaitForMessageReceived<NetworkVariableDeltaMessage>();
}
[UnityTest] [UnityTest]
public IEnumerator NotAuthorityRpc() public IEnumerator NotAuthorityRpc()
{ {

View File

@@ -1053,7 +1053,8 @@ namespace Unity.Netcode.RuntimeTests
} }
[Test] [Test]
public void TestUnsupportedManagedTypesWithUserSerializationDoNotThrowExceptions() // Exceptions should be thrown in DA mode with UserNetworkVariableSerialization
public void TestUnsupportedManagedTypesWithUserSerializationDoNotThrowExceptionsInClientServerMode()
{ {
var variable = new NetworkVariable<string>(); var variable = new NetworkVariable<string>();
UserNetworkVariableSerialization<string>.ReadValue = (FastBufferReader reader, out string value) => UserNetworkVariableSerialization<string>.ReadValue = (FastBufferReader reader, out string value) =>
@@ -1079,6 +1080,10 @@ namespace Unity.Netcode.RuntimeTests
variable.ReadField(reader); variable.ReadField(reader);
Assert.AreEqual("012345", variable.Value); Assert.AreEqual("012345", variable.Value);
} }
catch (Exception)
{
Assert.True(NetworkVariableSerialization<UserNetworkVariableSerialization<string>>.IsDistributedAuthority);
}
finally finally
{ {
UserNetworkVariableSerialization<string>.ReadValue = null; UserNetworkVariableSerialization<string>.ReadValue = null;

View File

@@ -2,23 +2,23 @@
"name": "com.unity.netcode.gameobjects", "name": "com.unity.netcode.gameobjects",
"displayName": "Netcode for GameObjects", "displayName": "Netcode for GameObjects",
"description": "Netcode for GameObjects is a high-level netcode SDK that provides networking capabilities to GameObject/MonoBehaviour workflows within Unity and sits on top of underlying transport layer.", "description": "Netcode for GameObjects is a high-level netcode SDK that provides networking capabilities to GameObject/MonoBehaviour workflows within Unity and sits on top of underlying transport layer.",
"version": "2.0.0-pre.2", "version": "2.0.0-pre.3",
"unity": "6000.0", "unity": "6000.0",
"dependencies": { "dependencies": {
"com.unity.nuget.mono-cecil": "1.11.4", "com.unity.nuget.mono-cecil": "1.11.4",
"com.unity.transport": "2.2.1" "com.unity.transport": "2.3.0"
}, },
"_upm": { "_upm": {
"changelog": "### Added\n\n- Added `AnticipatedNetworkVariable<T>`, which adds support for client anticipation of `NetworkVariable` values, allowing for more responsive gameplay. (#2957)\n- Added `AnticipatedNetworkTransform`, which adds support for client anticipation of NetworkTransforms. (#2957)\n- Added `NetworkVariableBase.ExceedsDirtinessThreshold` to allow network variables to throttle updates by only sending updates when the difference between the current and previous values exceeds a threshold. (This is exposed in `NetworkVariable<T>` with the callback `NetworkVariable<T>.CheckExceedsDirtinessThreshold`). (#2957)\n- Added `NetworkVariableUpdateTraits`, which add additional throttling support: `MinSecondsBetweenUpdates` will prevent the `NetworkVariable` from sending updates more often than the specified time period (even if it exceeds the dirtiness threshold), while `MaxSecondsBetweenUpdates` will force a dirty `NetworkVariable` to send an update after the specified time period even if it has not yet exceeded the dirtiness threshold. (#2957)\n- Added virtual method `NetworkVariableBase.OnInitialize` which can be used by `NetworkVariable` subclasses to add initialization code. (#2957)\n- Added `NetworkTime.TickWithPartial`, which represents the current tick as a double that includes the fractional/partial tick value. (#2957)\n- Added `NetworkTickSystem.AnticipationTick`, which can be helpful with implementation of client anticipation. This value represents the tick the current local client was at at the beginning of the most recent network round trip, which enables it to correlate server update ticks with the client tick that may have triggered them. (#2957)\n- Added event `NetworkManager.OnSessionOwnerPromoted` that is invoked when a new session owner promotion occurs. (#2948)\n- Added `NetworkRigidBodyBase.GetLinearVelocity` and `NetworkRigidBodyBase.SetLinearVelocity` convenience/helper methods. (#2948)\n- Added `NetworkRigidBodyBase.GetAngularVelocity` and `NetworkRigidBodyBase.SetAngularVelocity` convenience/helper methods. (#2948)\n\n### Fixed\n\n- Fixed issue when `NetworkTransform` half float precision is enabled and ownership changes the current base position was not being synchronized. (#2948)\n- Fixed issue where `OnClientConnected` not being invoked on the session owner when connecting to a new distributed authority session. (#2948)\n- Fixed issue where Rigidbody micro-motion (i.e. relatively small velocities) would result in non-authority instances slightly stuttering as the body would come to a rest (i.e. no motion). Now, the threshold value can increase at higher velocities and can decrease slightly below the provided threshold to account for this. (#2948)\n\n### Changed\n\n- Changed `NetworkAnimator` no longer requires the `Animator` component to exist on the same `GameObject`. (#2957)\n- Changed `NetworkObjectReference` and `NetworkBehaviourReference` to allow null references when constructing and serializing. (#2957)\n- Changed the client's owned objects is now returned (`NetworkClient` and `NetworkSpawnManager`) as an array as opposed to a list for performance purposes. (#2948)\n- Changed `NetworkTransfrom.TryCommitTransformToServer` to be internal as it will be removed by the final 2.0.0 release. (#2948)\n- Changed `NetworkTransformEditor.OnEnable` to a virtual method to be able to customize a `NetworkTransform` derived class by creating a derived editor control from `NetworkTransformEditor`. (#2948)" "changelog": "### Added\n- Added: `UnityTransport.GetNetworkDriver` and `UnityTransport.GetLocalEndpoint` methods to expose the driver and local endpoint being used. (#2978)\n\n### Fixed\n\n- Fixed issue where deferred despawn was causing GC allocations when converting an `IEnumerable` to a list. (#2983)\n- Fixed issue where the realtime network stats monitor was not able to display RPC traffic in release builds due to those stats being only available in development builds or the editor. (#2979)\n- Fixed issue where `NetworkManager.ScenesLoaded` was not being updated if `PostSynchronizationSceneUnloading` was set and any loaded scenes not used during synchronization were unloaded. (#2971)\n- Fixed issue where `Rigidbody2d` under Unity 6000.0.11f1 has breaking changes where `velocity` is now `linearVelocity` and `isKinematic` is replaced by `bodyType`. (#2971)\n- Fixed issue where `NetworkSpawnManager.InstantiateAndSpawn` and `NetworkObject.InstantiateAndSpawn` were not honoring the ownerClientId parameter when using a client-server network topology. (#2968)\n- Fixed issue where internal delta serialization could not have a byte serializer defined when serializing deltas for other types. Added `[GenerateSerializationForType(typeof(byte))]` to both the `NetworkVariable` and `AnticipatedNetworkVariable` classes to assure a byte serializer is defined.(#2962)\n- Fixed issue when scene management was disabled and the session owner would still try to synchronize a late joining client. (#2962)\n- Fixed issue when using a distributed authority network topology where it would allow a session owner to spawn a `NetworkObject` prior to being approved. Now, an error message is logged and the `NetworkObject` will not be spawned prior to the client being approved. (#2962)\n- Fixed issue where attempting to spawn during `NetworkBehaviour.OnInSceneObjectsSpawned` and `NetworkBehaviour.OnNetworkSessionSynchronized` notifications would throw a collection modified exception. (#2962)\n\n### Changed\n\n- Changed logic where clients can now set the `NetworkSceneManager` client synchronization mode when using a distributed authority network topology. (#2985)"
}, },
"upmCi": { "upmCi": {
"footprint": "dffe7543c19c2670755cc7da27e1d4522c9414a9" "footprint": "fbae2629229fb08020f4b9cef5656e6fdf517c3d"
}, },
"documentationUrl": "https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@2.0/manual/index.html", "documentationUrl": "https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@2.0/manual/index.html",
"repository": { "repository": {
"url": "https://github.com/Unity-Technologies/com.unity.netcode.gameobjects.git", "url": "https://github.com/Unity-Technologies/com.unity.netcode.gameobjects.git",
"type": "git", "type": "git",
"revision": "2ecbd14351cac29ab3a18cc159c0a82513e9d241" "revision": "8575c902227d221f987d9cb869d501749f8631b4"
}, },
"samples": [ "samples": [
{ {