com.unity.netcode.gameobjects@2.0.0-exp.2
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com). ## [2.0.0-exp.2] - 2024-04-02 ### Added - Added updates to all internal messages to account for a distributed authority network session connection. (#2863) - Added `NetworkRigidbodyBase` that provides users with a more customizable network rigidbody, handles both `Rigidbody` and `Rigidbody2D`, and provides an option to make `NetworkTransform` use the rigid body for motion. (#2863) - For a customized `NetworkRigidbodyBase` class: - `NetworkRigidbodyBase.AutoUpdateKinematicState` provides control on whether the kinematic setting will be automatically set or not when ownership changes. - `NetworkRigidbodyBase.AutoSetKinematicOnDespawn` provides control on whether isKinematic will automatically be set to true when the associated `NetworkObject` is despawned. - `NetworkRigidbodyBase.Initialize` is a protected method that, when invoked, will initialize the instance. This includes options to: - Set whether using a `RigidbodyTypes.Rigidbody` or `RigidbodyTypes.Rigidbody2D`. - Includes additional optional parameters to set the `NetworkTransform`, `Rigidbody`, and `Rigidbody2d` to use. - Provides additional public methods: - `NetworkRigidbodyBase.GetPosition` to return the position of the `Rigidbody` or `Rigidbody2d` (depending upon its initialized setting). - `NetworkRigidbodyBase.GetRotation` to return the rotation of the `Rigidbody` or `Rigidbody2d` (depending upon its initialized setting). - `NetworkRigidbodyBase.MovePosition` to move to the position of the `Rigidbody` or `Rigidbody2d` (depending upon its initialized setting). - `NetworkRigidbodyBase.MoveRotation` to move to the rotation of the `Rigidbody` or `Rigidbody2d` (depending upon its initialized setting). - `NetworkRigidbodyBase.Move` to move to the position and rotation of the `Rigidbody` or `Rigidbody2d` (depending upon its initialized setting). - `NetworkRigidbodyBase.Move` to move to the position and rotation of the `Rigidbody` or `Rigidbody2d` (depending upon its initialized setting). - `NetworkRigidbodyBase.SetPosition` to set the position of the `Rigidbody` or `Rigidbody2d` (depending upon its initialized setting). - `NetworkRigidbodyBase.SetRotation` to set the rotation of the `Rigidbody` or `Rigidbody2d` (depending upon its initialized setting). - `NetworkRigidbodyBase.ApplyCurrentTransform` to set the position and rotation of the `Rigidbody` or `Rigidbody2d` based on the associated `GameObject` transform (depending upon its initialized setting). - `NetworkRigidbodyBase.WakeIfSleeping` to wake up the rigid body if sleeping. - `NetworkRigidbodyBase.SleepRigidbody` to put the rigid body to sleep. - `NetworkRigidbodyBase.IsKinematic` to determine if the `Rigidbody` or `Rigidbody2d` (depending upon its initialized setting) is currently kinematic. - `NetworkRigidbodyBase.SetIsKinematic` to set the `Rigidbody` or `Rigidbody2d` (depending upon its initialized setting) current kinematic state. - `NetworkRigidbodyBase.ResetInterpolation` to reset the `Rigidbody` or `Rigidbody2d` (depending upon its initialized setting) back to its original interpolation value when initialized. - Now includes a `MonoBehaviour.FixedUpdate` implementation that will update the assigned `NetworkTransform` when `NetworkRigidbodyBase.UseRigidBodyForMotion` is true. (#2863) - Added `RigidbodyContactEventManager` that provides a more optimized way to process collision enter and collision stay events as opposed to the `Monobehaviour` approach. (#2863) - Can be used in client-server and distributed authority modes, but is particularly useful in distributed authority. - Added rigid body motion updates to `NetworkTransform` which allows users to set interolation on rigid bodies. (#2863) - Extrapolation is only allowed on authoritative instances, but custom class derived from `NetworkRigidbodyBase` or `NetworkRigidbody` or `NetworkRigidbody2D` automatically switches non-authoritative instances to interpolation if set to extrapolation. - Added distributed authority mode support to `NetworkAnimator`. (#2863) - Added session mode selection to `NetworkManager` inspector view. (#2863) - Added distributed authority permissions feature. (#2863) - Added distributed authority mode specific `NetworkObject` permissions flags (Distributable, Transferable, and RequestRequired). (#2863) - Added distributed authority mode specific `NetworkObject.SetOwnershipStatus` method that applies one or more `NetworkObject` instance's ownership flags. If updated when spawned, the ownership permission changes are synchronized with the other connected clients. (#2863) - Added distributed authority mode specific `NetworkObject.RemoveOwnershipStatus` method that removes one or more `NetworkObject` instance's ownership flags. If updated when spawned, the ownership permission changes are synchronized with the other connected clients. (#2863) - Added distributed authority mode specific `NetworkObject.HasOwnershipStatus` method that will return (true or false) whether one or more ownership flags is set. (#2863) - Added distributed authority mode specific `NetworkObject.SetOwnershipLock` method that locks ownership of a `NetworkObject` to prevent ownership from changing until the current owner releases the lock. (#2863) - Added distributed authority mode specific `NetworkObject.RequestOwnership` method that sends an ownership request to the current owner of a spawned `NetworkObject` instance. (#2863) - Added distributed authority mode specific `NetworkObject.OnOwnershipRequested` callback handler that is invoked on the owner/authoritative side when a non-owner requests ownership. Depending upon the boolean returned value depends upon whether the request is approved or denied. (#2863) - Added distributed authority mode specific `NetworkObject.OnOwnershipRequestResponse` callback handler that is invoked when a non-owner's request has been processed. This callback includes a `NetworkObjet.OwnershipRequestResponseStatus` response parameter that describes whether the request was approved or the reason why it was not approved. (#2863) - Added distributed authority mode specific `NetworkObject.DeferDespawn` method that defers the despawning of `NetworkObject` instances on non-authoritative clients based on the tick offset parameter. (#2863) - Added distributed authority mode specific `NetworkObject.OnDeferredDespawnComplete` callback handler that can be used to further control when deferring the despawning of a `NetworkObject` on non-authoritative instances. (#2863) - Added `NetworkClient.SessionModeType` as one way to determine the current session mode of the network session a client is connected to. (#2863) - Added distributed authority mode specific `NetworkClient.IsSessionOwner` property to determine if the current local client is the current session owner of a distributed authority session. (#2863) - Added distributed authority mode specific client side spawning capabilities. When running in distributed authority mode, clients can instantiate and spawn `NetworkObject` instances (the local client is authomatically the owner of the spawned object). (#2863) - This is useful to better visually synchronize owner authoritative motion models and newly spawned `NetworkObject` instances (i.e. projectiles for example). - Added distributed authority mode specific client side player spawning capabilities. Clients will automatically spawn their associated player object locally. (#2863) - Added distributed authority mode specific `NetworkConfig.AutoSpawnPlayerPrefabClientSide` property (default is true) to provide control over the automatic spawning of player prefabs on the local client side. (#2863) - Added distributed authority mode specific `NetworkManager.OnFetchLocalPlayerPrefabToSpawn` callback that, when assigned, will allow the local client to provide the player prefab to be spawned for the local client. (#2863) - This is only invoked if the `NetworkConfig.AutoSpawnPlayerPrefabClientSide` property is set to true. - Added distributed authority mode specific `NetworkBehaviour.HasAuthority` property that determines if the local client has authority over the associated `NetworkObject` instance (typical use case is within a `NetworkBehaviour` script much like that of `IsServer` or `IsClient`). (#2863) - Added distributed authority mode specific `NetworkBehaviour.IsSessionOwner` property that determines if the local client is the session owner (typical use case would be to determine if the local client can has scene management authority within a `NetworkBehaviour` script). (#2863) - Added support for distributed authority mode scene management where the currently assigned session owner can start scene events (i.e. scene loading and scene unloading). (#2863) ### Fixed - Fixed issue where the host was not invoking `OnClientDisconnectCallback` for its own local client when internally shutting down. (#2822) - Fixed issue where NetworkTransform could potentially attempt to "unregister" a named message prior to it being registered. (#2807) - Fixed issue where in-scene placed `NetworkObject`s with complex nested children `NetworkObject`s (more than one child in depth) would not synchronize properly if WorldPositionStays was set to true. (#2796) ### Changed - Changed client side awareness of other clients is now the same as a server or host. (#2863) - Changed `NetworkManager.ConnectedClients` can now be accessed by both server and clients. (#2863) - Changed `NetworkManager.ConnectedClientsList` can now be accessed by both server and clients. (#2863) - Changed `NetworkTransform` defaults to owner authoritative when connected to a distributed authority session. (#2863) - Changed `NetworkVariable` defaults to owner write and everyone read permissions when connected to a distributed authority session (even if declared with server read or write permissions). (#2863) - Changed `NetworkObject` no longer implements the `MonoBehaviour.Update` method in order to determine whether a `NetworkObject` instance has been migrated to a different scene. Instead, only `NetworkObjects` with the `SceneMigrationSynchronization` property set will be updated internally during the `NetworkUpdateStage.PostLateUpdate` by `NetworkManager`. (#2863) - Changed `NetworkManager` inspector view layout where properties are now organized by category. (#2863) - Changed `NetworkTransform` to now use `NetworkTransformMessage` as opposed to named messages for NetworkTransformState updates. (#2810) - Changed `CustomMessageManager` so it no longer attempts to register or "unregister" a null or empty string and will log an error if this condition occurs. (#2807)
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Unity.Collections;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
@@ -151,18 +152,14 @@ namespace Unity.Netcode
|
||||
// We dont know what size to use. Try every (more collision prone)
|
||||
if (m_NamedMessageHandlers32.TryGetValue(hash, out HandleNamedMessageDelegate messageHandler32))
|
||||
{
|
||||
// handler can remove itself, cache the name for metrics
|
||||
string messageName = m_MessageHandlerNameLookup32[hash];
|
||||
messageHandler32(sender, reader);
|
||||
m_NetworkManager.NetworkMetrics.TrackNamedMessageReceived(sender, messageName, bytesCount);
|
||||
m_NetworkManager.NetworkMetrics.TrackNamedMessageReceived(sender, m_MessageHandlerNameLookup32[hash], bytesCount);
|
||||
}
|
||||
|
||||
if (m_NamedMessageHandlers64.TryGetValue(hash, out HandleNamedMessageDelegate messageHandler64))
|
||||
{
|
||||
// handler can remove itself, cache the name for metrics
|
||||
string messageName = m_MessageHandlerNameLookup64[hash];
|
||||
messageHandler64(sender, reader);
|
||||
m_NetworkManager.NetworkMetrics.TrackNamedMessageReceived(sender, messageName, bytesCount);
|
||||
m_NetworkManager.NetworkMetrics.TrackNamedMessageReceived(sender, m_MessageHandlerNameLookup64[hash], bytesCount);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -173,19 +170,15 @@ namespace Unity.Netcode
|
||||
case HashSize.VarIntFourBytes:
|
||||
if (m_NamedMessageHandlers32.TryGetValue(hash, out HandleNamedMessageDelegate messageHandler32))
|
||||
{
|
||||
// handler can remove itself, cache the name for metrics
|
||||
string messageName = m_MessageHandlerNameLookup32[hash];
|
||||
messageHandler32(sender, reader);
|
||||
m_NetworkManager.NetworkMetrics.TrackNamedMessageReceived(sender, messageName, bytesCount);
|
||||
m_NetworkManager.NetworkMetrics.TrackNamedMessageReceived(sender, m_MessageHandlerNameLookup32[hash], bytesCount);
|
||||
}
|
||||
break;
|
||||
case HashSize.VarIntEightBytes:
|
||||
if (m_NamedMessageHandlers64.TryGetValue(hash, out HandleNamedMessageDelegate messageHandler64))
|
||||
{
|
||||
// handler can remove itself, cache the name for metrics
|
||||
string messageName = m_MessageHandlerNameLookup64[hash];
|
||||
messageHandler64(sender, reader);
|
||||
m_NetworkManager.NetworkMetrics.TrackNamedMessageReceived(sender, messageName, bytesCount);
|
||||
m_NetworkManager.NetworkMetrics.TrackNamedMessageReceived(sender, m_MessageHandlerNameLookup64[hash], bytesCount);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -199,6 +192,14 @@ namespace Unity.Netcode
|
||||
/// <param name="callback">The callback to run when a named message is received.</param>
|
||||
public void RegisterNamedMessageHandler(string name, HandleNamedMessageDelegate callback)
|
||||
{
|
||||
if (string.IsNullOrEmpty(name))
|
||||
{
|
||||
if (m_NetworkManager.LogLevel <= LogLevel.Error)
|
||||
{
|
||||
Debug.LogError($"[{nameof(RegisterNamedMessageHandler)}] Cannot register a named message of type null or empty!");
|
||||
}
|
||||
return;
|
||||
}
|
||||
var hash32 = XXHash.Hash32(name);
|
||||
var hash64 = XXHash.Hash64(name);
|
||||
|
||||
@@ -215,6 +216,15 @@ namespace Unity.Netcode
|
||||
/// <param name="name">The name of the message.</param>
|
||||
public void UnregisterNamedMessageHandler(string name)
|
||||
{
|
||||
if (string.IsNullOrEmpty(name))
|
||||
{
|
||||
if (m_NetworkManager.LogLevel <= LogLevel.Error)
|
||||
{
|
||||
Debug.LogError($"[{nameof(UnregisterNamedMessageHandler)}] Cannot unregister a named message of type null or empty!");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var hash32 = XXHash.Hash32(name);
|
||||
var hash64 = XXHash.Hash64(name);
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ namespace Unity.Netcode
|
||||
}
|
||||
protected struct TriggerInfo
|
||||
{
|
||||
public string MessageType;
|
||||
public float Expiry;
|
||||
public NativeList<TriggerData> TriggerData;
|
||||
}
|
||||
@@ -36,7 +37,7 @@ namespace Unity.Netcode
|
||||
/// There is a one second maximum lifetime of triggers to avoid memory leaks. After one second has passed
|
||||
/// without the requested object ID being spawned, the triggers for it are automatically deleted.
|
||||
/// </summary>
|
||||
public virtual unsafe void DeferMessage(IDeferredNetworkMessageManager.TriggerType trigger, ulong key, FastBufferReader reader, ref NetworkContext context)
|
||||
public virtual unsafe void DeferMessage(IDeferredNetworkMessageManager.TriggerType trigger, ulong key, FastBufferReader reader, ref NetworkContext context, string messageType)
|
||||
{
|
||||
if (!m_Triggers.TryGetValue(trigger, out var triggers))
|
||||
{
|
||||
@@ -48,6 +49,7 @@ namespace Unity.Netcode
|
||||
{
|
||||
triggerInfo = new TriggerInfo
|
||||
{
|
||||
MessageType = messageType,
|
||||
Expiry = m_NetworkManager.RealTimeProvider.RealTimeSinceStartup + m_NetworkManager.NetworkConfig.SpawnTimeout,
|
||||
TriggerData = new NativeList<TriggerData>(Allocator.Persistent)
|
||||
};
|
||||
@@ -90,11 +92,29 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used for testing purposes
|
||||
/// </summary>
|
||||
internal static bool IncludeMessageType = true;
|
||||
|
||||
private string GetWarningMessage(IDeferredNetworkMessageManager.TriggerType triggerType, ulong key, TriggerInfo triggerInfo, float spawnTimeout)
|
||||
{
|
||||
if (IncludeMessageType)
|
||||
{
|
||||
return $"[Deferred {triggerType}] Messages were received for a trigger of type {triggerInfo.MessageType} associated with id ({key}), but the {nameof(NetworkObject)} was not received within the timeout period {spawnTimeout} second(s).";
|
||||
}
|
||||
else
|
||||
{
|
||||
return $"Deferred messages were received for a trigger of type {triggerType} associated with id ({key}), but the {nameof(NetworkObject)} was not received within the timeout period {spawnTimeout} second(s).";
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void PurgeTrigger(IDeferredNetworkMessageManager.TriggerType triggerType, ulong key, TriggerInfo triggerInfo)
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
var logLevel = m_NetworkManager.DistributedAuthorityMode ? LogLevel.Developer : LogLevel.Normal;
|
||||
if (NetworkLog.CurrentLogLevel <= logLevel)
|
||||
{
|
||||
NetworkLog.LogWarning($"Deferred messages were received for a trigger of type {triggerType} with key {key}, but that trigger was not received within within {m_NetworkManager.NetworkConfig.SpawnTimeout} second(s).");
|
||||
NetworkLog.LogWarning(GetWarningMessage(triggerType, key, triggerInfo, m_NetworkManager.NetworkConfig.SpawnTimeout));
|
||||
}
|
||||
|
||||
foreach (var data in triggerInfo.TriggerData)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
internal interface IDeferredNetworkMessageManager
|
||||
@@ -17,7 +18,7 @@ namespace Unity.Netcode
|
||||
/// There is a one second maximum lifetime of triggers to avoid memory leaks. After one second has passed
|
||||
/// without the requested object ID being spawned, the triggers for it are automatically deleted.
|
||||
/// </summary>
|
||||
void DeferMessage(TriggerType trigger, ulong key, FastBufferReader reader, ref NetworkContext context);
|
||||
void DeferMessage(TriggerType trigger, ulong key, FastBufferReader reader, ref NetworkContext context, string messageType = null);
|
||||
|
||||
/// <summary>
|
||||
/// Cleans up any trigger that's existed for more than a second.
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
internal struct ChangeOwnershipMessage : INetworkMessage, INetworkSerializeByMemcpy
|
||||
@@ -6,11 +7,146 @@ namespace Unity.Netcode
|
||||
|
||||
public ulong NetworkObjectId;
|
||||
public ulong OwnerClientId;
|
||||
// DANGOEXP TODO: Remove these notes or change their format
|
||||
// SERVICE NOTES:
|
||||
// When forwarding the message to clients on the CMB Service side,
|
||||
// you can set the ClientIdCount to 0 and skip writing the ClientIds.
|
||||
// See the NetworkObjet.OwnershipRequest for more potential service side additions
|
||||
|
||||
/// <summary>
|
||||
/// When requesting, RequestClientId is the requestor.
|
||||
/// When approving, RequestClientId is the owner that approved.
|
||||
/// When responding (only for denied), RequestClientId is the requestor
|
||||
/// </summary>
|
||||
internal ulong RequestClientId;
|
||||
internal int ClientIdCount;
|
||||
internal ulong[] ClientIds;
|
||||
internal bool DistributedAuthorityMode;
|
||||
internal ushort OwnershipFlags;
|
||||
internal byte OwnershipRequestResponseStatus;
|
||||
private byte m_OwnershipMessageTypeFlags;
|
||||
|
||||
private const byte k_OwnershipChanging = 0x01;
|
||||
private const byte k_OwnershipFlagsUpdate = 0x02;
|
||||
private const byte k_RequestOwnership = 0x04;
|
||||
private const byte k_RequestApproved = 0x08;
|
||||
private const byte k_RequestDenied = 0x10;
|
||||
|
||||
// If no flags are set, then ownership is changing
|
||||
internal bool OwnershipIsChanging
|
||||
{
|
||||
get
|
||||
{
|
||||
return GetFlag(k_OwnershipChanging);
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
SetFlag(value, k_OwnershipChanging);
|
||||
}
|
||||
}
|
||||
|
||||
internal bool OwnershipFlagsUpdate
|
||||
{
|
||||
get
|
||||
{
|
||||
return GetFlag(k_OwnershipFlagsUpdate);
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
SetFlag(value, k_OwnershipFlagsUpdate);
|
||||
}
|
||||
}
|
||||
|
||||
internal bool RequestOwnership
|
||||
{
|
||||
get
|
||||
{
|
||||
return GetFlag(k_RequestOwnership);
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
SetFlag(value, k_RequestOwnership);
|
||||
}
|
||||
}
|
||||
|
||||
internal bool RequestApproved
|
||||
{
|
||||
get
|
||||
{
|
||||
return GetFlag(k_RequestApproved);
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
SetFlag(value, k_RequestApproved);
|
||||
}
|
||||
}
|
||||
|
||||
internal bool RequestDenied
|
||||
{
|
||||
get
|
||||
{
|
||||
return GetFlag(k_RequestDenied);
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
SetFlag(value, k_RequestDenied);
|
||||
}
|
||||
}
|
||||
|
||||
private bool GetFlag(int flag)
|
||||
{
|
||||
return (m_OwnershipMessageTypeFlags & flag) != 0;
|
||||
}
|
||||
|
||||
private void SetFlag(bool set, byte flag)
|
||||
{
|
||||
if (set) { m_OwnershipMessageTypeFlags = (byte)(m_OwnershipMessageTypeFlags | flag); }
|
||||
else { m_OwnershipMessageTypeFlags = (byte)(m_OwnershipMessageTypeFlags & ~flag); }
|
||||
}
|
||||
|
||||
public void Serialize(FastBufferWriter writer, int targetVersion)
|
||||
{
|
||||
BytePacker.WriteValueBitPacked(writer, NetworkObjectId);
|
||||
BytePacker.WriteValueBitPacked(writer, OwnerClientId);
|
||||
if (DistributedAuthorityMode)
|
||||
{
|
||||
BytePacker.WriteValueBitPacked(writer, ClientIdCount);
|
||||
if (ClientIdCount > 0)
|
||||
{
|
||||
if (ClientIdCount != ClientIds.Length)
|
||||
{
|
||||
throw new System.Exception($"[{nameof(ChangeOwnershipMessage)}] ClientIdCount is {ClientIdCount} but the ClientIds length is {ClientIds.Length}!");
|
||||
}
|
||||
foreach (var clientId in ClientIds)
|
||||
{
|
||||
BytePacker.WriteValueBitPacked(writer, clientId);
|
||||
}
|
||||
}
|
||||
|
||||
writer.WriteValueSafe(m_OwnershipMessageTypeFlags);
|
||||
if (OwnershipFlagsUpdate || OwnershipIsChanging)
|
||||
{
|
||||
writer.WriteValueSafe(OwnershipFlags);
|
||||
}
|
||||
|
||||
// When requesting, it is the requestor
|
||||
// When approving, it is the owner that approved
|
||||
// When denied, it is the requestor
|
||||
if (RequestOwnership || RequestApproved || RequestDenied)
|
||||
{
|
||||
writer.WriteValueSafe(RequestClientId);
|
||||
|
||||
if (RequestDenied)
|
||||
{
|
||||
writer.WriteValueSafe(OwnershipRequestResponseStatus);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion)
|
||||
@@ -22,46 +158,241 @@ namespace Unity.Netcode
|
||||
}
|
||||
ByteUnpacker.ReadValueBitPacked(reader, out NetworkObjectId);
|
||||
ByteUnpacker.ReadValueBitPacked(reader, out OwnerClientId);
|
||||
if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(NetworkObjectId))
|
||||
|
||||
if (networkManager.DistributedAuthorityMode)
|
||||
{
|
||||
networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnSpawn, NetworkObjectId, reader, ref context);
|
||||
return false;
|
||||
ByteUnpacker.ReadValueBitPacked(reader, out ClientIdCount);
|
||||
if (ClientIdCount > 0)
|
||||
{
|
||||
ClientIds = new ulong[ClientIdCount];
|
||||
var clientId = (ulong)0;
|
||||
for (int i = 0; i < ClientIdCount; i++)
|
||||
{
|
||||
ByteUnpacker.ReadValueBitPacked(reader, out clientId);
|
||||
ClientIds[i] = clientId;
|
||||
}
|
||||
}
|
||||
|
||||
reader.ReadValueSafe(out m_OwnershipMessageTypeFlags);
|
||||
if (OwnershipFlagsUpdate || OwnershipIsChanging)
|
||||
{
|
||||
reader.ReadValueSafe(out OwnershipFlags);
|
||||
}
|
||||
|
||||
// When requesting, it is the requestor
|
||||
// When approving, it is the owner that approved
|
||||
// When denied, it is the requestor
|
||||
if (RequestOwnership || RequestApproved || RequestDenied)
|
||||
{
|
||||
reader.ReadValueSafe(out RequestClientId);
|
||||
|
||||
if (RequestDenied)
|
||||
{
|
||||
reader.ReadValueSafe(out OwnershipRequestResponseStatus);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we are not a DAHost instance and the NetworkObject does not exist then defer it as it very likely is not spawned yet.
|
||||
// Otherwise if we are the DAHost and it does not exist then we want to forward this message because when the NetworkObject
|
||||
// is made visible again, the ownership flags and owner information will be synchronized with the DAHost by the current
|
||||
// authority of the NetworkObject in question.
|
||||
if (!networkManager.DAHost && !networkManager.SpawnManager.SpawnedObjects.ContainsKey(NetworkObjectId))
|
||||
{
|
||||
networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnSpawn, NetworkObjectId, reader, ref context, GetType().Name);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Handle(ref NetworkContext context)
|
||||
{
|
||||
var networkManager = (NetworkManager)context.SystemOwner;
|
||||
var networkObject = networkManager.SpawnManager.SpawnedObjects[NetworkObjectId];
|
||||
var originalOwner = networkObject.OwnerClientId;
|
||||
|
||||
// If we are the DAHost then forward this message
|
||||
if (networkManager.DAHost)
|
||||
{
|
||||
var clientList = ClientIdCount > 0 ? ClientIds : networkManager.ConnectedClientsIds;
|
||||
|
||||
var message = new ChangeOwnershipMessage()
|
||||
{
|
||||
NetworkObjectId = NetworkObjectId,
|
||||
OwnerClientId = OwnerClientId,
|
||||
DistributedAuthorityMode = true,
|
||||
OwnershipFlags = OwnershipFlags,
|
||||
RequestClientId = RequestClientId,
|
||||
ClientIdCount = 0,
|
||||
m_OwnershipMessageTypeFlags = m_OwnershipMessageTypeFlags,
|
||||
};
|
||||
|
||||
if (RequestDenied)
|
||||
{
|
||||
// If the local DAHost's client is not the target, then forward to the target
|
||||
if (RequestClientId != networkManager.LocalClientId)
|
||||
{
|
||||
message.OwnershipRequestResponseStatus = OwnershipRequestResponseStatus;
|
||||
networkManager.ConnectionManager.SendMessage(ref message, NetworkDelivery.Reliable, RequestClientId);
|
||||
// We don't want the local DAHost's client to process this message, so exit early
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (RequestOwnership)
|
||||
{
|
||||
// If the DAHost client is not authority, just forward the message to the authority
|
||||
if (OwnerClientId != networkManager.LocalClientId)
|
||||
{
|
||||
networkManager.ConnectionManager.SendMessage(ref message, NetworkDelivery.Reliable, OwnerClientId);
|
||||
// We don't want the local DAHost's client to process this message, so exit early
|
||||
return;
|
||||
}
|
||||
// Otherwise, fall through and process the request.
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var clientId in clientList)
|
||||
{
|
||||
if (clientId == networkManager.LocalClientId)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// If ownership is changing and this is not an ownership request approval then ignore the OnwerClientId
|
||||
// If it is just updating flags then ignore sending to the owner
|
||||
// If it is a request or approving request, then ignore the RequestClientId
|
||||
if ((OwnershipIsChanging && !RequestApproved && OwnerClientId == clientId) || (OwnershipFlagsUpdate && clientId == OwnerClientId)
|
||||
|| ((RequestOwnership || RequestApproved) && clientId == RequestClientId))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
networkManager.ConnectionManager.SendMessage(ref message, NetworkDelivery.Reliable, clientId);
|
||||
}
|
||||
}
|
||||
// If the NetworkObject is not visible to the DAHost client, then exit early
|
||||
if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(NetworkObjectId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If ownership is changing, then run through the ownershipd changed sequence
|
||||
// Note: There is some extended ownership script at the bottom of HandleOwnershipChange
|
||||
// If not in distributed authority mode, then always go straight to HandleOwnershipChange
|
||||
if (OwnershipIsChanging || !networkManager.DistributedAuthorityMode)
|
||||
{
|
||||
HandleOwnershipChange(ref context);
|
||||
}
|
||||
else if (networkManager.DistributedAuthorityMode)
|
||||
{
|
||||
// Otherwise, we handle and extended ownership update
|
||||
HandleExtendedOwnershipUpdate(ref context);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle the
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
private void HandleExtendedOwnershipUpdate(ref NetworkContext context)
|
||||
{
|
||||
var networkManager = (NetworkManager)context.SystemOwner;
|
||||
|
||||
// Handle the extended ownership message types
|
||||
var networkObject = networkManager.SpawnManager.SpawnedObjects[NetworkObjectId];
|
||||
|
||||
if (OwnershipFlagsUpdate)
|
||||
{
|
||||
// Just update the ownership flags
|
||||
networkObject.Ownership = (NetworkObject.OwnershipStatus)OwnershipFlags;
|
||||
}
|
||||
else if (RequestOwnership)
|
||||
{
|
||||
// Requesting ownership, if allowed it will automatically send the ownership change message
|
||||
networkObject.OwnershipRequest(RequestClientId);
|
||||
}
|
||||
else if (RequestDenied)
|
||||
{
|
||||
networkObject.OwnershipRequestResponse((NetworkObject.OwnershipRequestResponseStatus)OwnershipRequestResponseStatus);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle the traditional change in ownership message type logic
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
private void HandleOwnershipChange(ref NetworkContext context)
|
||||
{
|
||||
var networkManager = (NetworkManager)context.SystemOwner;
|
||||
var networkObject = networkManager.SpawnManager.SpawnedObjects[NetworkObjectId];
|
||||
|
||||
// DANGO-TODO: This probably shouldn't be allowed to happen.
|
||||
if (networkObject.OwnerClientId == OwnerClientId)
|
||||
{
|
||||
UnityEngine.Debug.LogWarning($"Unnecessary ownership changed message for {NetworkObjectId}");
|
||||
}
|
||||
|
||||
var originalOwner = networkObject.OwnerClientId;
|
||||
networkObject.OwnerClientId = OwnerClientId;
|
||||
|
||||
// We are current owner.
|
||||
if (originalOwner == networkManager.LocalClientId)
|
||||
if (networkManager.DistributedAuthorityMode)
|
||||
{
|
||||
networkObject.Ownership = (NetworkObject.OwnershipStatus)OwnershipFlags;
|
||||
}
|
||||
|
||||
// We are current owner (client-server) or running in distributed authority mode
|
||||
if (originalOwner == networkManager.LocalClientId || networkManager.DistributedAuthorityMode)
|
||||
{
|
||||
networkObject.InvokeBehaviourOnLostOwnership();
|
||||
}
|
||||
|
||||
// We are new owner.
|
||||
if (OwnerClientId == networkManager.LocalClientId)
|
||||
// We are new owner or (client-server) or running in distributed authority mode
|
||||
if (OwnerClientId == networkManager.LocalClientId || networkManager.DistributedAuthorityMode)
|
||||
{
|
||||
networkObject.InvokeBehaviourOnGainedOwnership();
|
||||
}
|
||||
|
||||
// For all other clients that are neither the former or current owner, update the behaviours' properties
|
||||
if (OwnerClientId != networkManager.LocalClientId && originalOwner != networkManager.LocalClientId)
|
||||
// If in distributed authority mode
|
||||
if (networkManager.DistributedAuthorityMode)
|
||||
{
|
||||
// Always update the network properties in distributed authority mode
|
||||
for (int i = 0; i < networkObject.ChildNetworkBehaviours.Count; i++)
|
||||
{
|
||||
networkObject.ChildNetworkBehaviours[i].UpdateNetworkProperties();
|
||||
}
|
||||
}
|
||||
else // Otherwise update properties like we would in client-server
|
||||
{
|
||||
// For all other clients that are neither the former or current owner, update the behaviours' properties
|
||||
if (OwnerClientId != networkManager.LocalClientId && originalOwner != networkManager.LocalClientId)
|
||||
{
|
||||
for (int i = 0; i < networkObject.ChildNetworkBehaviours.Count; i++)
|
||||
{
|
||||
networkObject.ChildNetworkBehaviours[i].UpdateNetworkProperties();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Always invoke ownership change notifications
|
||||
networkObject.InvokeOwnershipChanged(originalOwner, OwnerClientId);
|
||||
|
||||
// If this change was requested, then notify that the request was approved (doing this last so all ownership
|
||||
// changes have already been applied if the callback is invoked)
|
||||
if (networkManager.DistributedAuthorityMode && networkManager.LocalClientId == OwnerClientId)
|
||||
{
|
||||
if (RequestApproved)
|
||||
{
|
||||
networkObject.OwnershipRequestResponse(NetworkObject.OwnershipRequestResponseStatus.Approved);
|
||||
}
|
||||
|
||||
// If the NetworkObject changed ownership and the Requested flag was set (i.e. it was an ownership request),
|
||||
// then the new owner granted ownership removes the Requested flag and sends out an ownership status update.
|
||||
if (networkObject.HasExtendedOwnershipStatus(NetworkObject.OwnershipStatusExtended.Requested))
|
||||
{
|
||||
networkObject.RemoveOwnershipExtended(NetworkObject.OwnershipStatusExtended.Requested);
|
||||
networkObject.SendOwnershipStatusUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
networkManager.NetworkMetrics.TrackOwnershipChangeReceived(context.SenderId, networkObject, context.MessageSize);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,9 +6,13 @@ namespace Unity.Netcode
|
||||
|
||||
public ulong ClientId;
|
||||
|
||||
public bool ShouldSynchronize;
|
||||
|
||||
|
||||
public void Serialize(FastBufferWriter writer, int targetVersion)
|
||||
{
|
||||
BytePacker.WriteValueBitPacked(writer, ClientId);
|
||||
writer.WriteValueSafe(ShouldSynchronize);
|
||||
}
|
||||
|
||||
public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion)
|
||||
@@ -19,17 +23,44 @@ namespace Unity.Netcode
|
||||
return false;
|
||||
}
|
||||
ByteUnpacker.ReadValueBitPacked(reader, out ClientId);
|
||||
reader.ReadValueSafe(out ShouldSynchronize);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Handle(ref NetworkContext context)
|
||||
{
|
||||
var networkManager = (NetworkManager)context.SystemOwner;
|
||||
networkManager.ConnectionManager.ConnectedClientIds.Add(ClientId);
|
||||
if ((ShouldSynchronize || networkManager.CMBServiceConnection) && networkManager.DistributedAuthorityMode && networkManager.LocalClient.IsSessionOwner)
|
||||
{
|
||||
networkManager.SceneManager.SynchronizeNetworkObjects(ClientId);
|
||||
}
|
||||
else
|
||||
{
|
||||
// All modes support adding NetworkClients
|
||||
networkManager.ConnectionManager.AddClient(ClientId);
|
||||
}
|
||||
if (!networkManager.ConnectionManager.ConnectedClientIds.Contains(ClientId))
|
||||
{
|
||||
networkManager.ConnectionManager.ConnectedClientIds.Add(ClientId);
|
||||
}
|
||||
if (networkManager.IsConnectedClient)
|
||||
{
|
||||
networkManager.ConnectionManager.InvokeOnPeerConnectedCallback(ClientId);
|
||||
}
|
||||
|
||||
// DANGO-TODO: Remove the session owner object distribution check once the service handles object distribution
|
||||
if (networkManager.DistributedAuthorityMode && networkManager.CMBServiceConnection && !networkManager.NetworkConfig.EnableSceneManagement)
|
||||
{
|
||||
// Don't redistribute for the local instance
|
||||
if (ClientId != networkManager.LocalClientId)
|
||||
{
|
||||
// We defer redistribution to the end of the NetworkUpdateStage.PostLateUpdate
|
||||
networkManager.RedistributeToClient = true;
|
||||
networkManager.ClientToRedistribute = ClientId;
|
||||
networkManager.TickToRedistribute = networkManager.ServerTime.Tick + 20;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,8 @@ namespace Unity.Netcode
|
||||
public void Handle(ref NetworkContext context)
|
||||
{
|
||||
var networkManager = (NetworkManager)context.SystemOwner;
|
||||
// All modes support removing NetworkClients
|
||||
networkManager.ConnectionManager.RemoveClient(ClientId);
|
||||
networkManager.ConnectionManager.ConnectedClientIds.Remove(ClientId);
|
||||
if (networkManager.IsConnectedClient)
|
||||
{
|
||||
|
||||
@@ -10,6 +10,11 @@ namespace Unity.Netcode
|
||||
|
||||
public ulong OwnerClientId;
|
||||
public int NetworkTick;
|
||||
// The cloud state service should set this if we are restoring a session
|
||||
public bool IsRestoredSession;
|
||||
public ulong CurrentSessionOwner;
|
||||
// Not serialized
|
||||
public bool IsDistributedAuthority;
|
||||
|
||||
// Not serialized, held as references to serialize NetworkVariable data
|
||||
public HashSet<NetworkObject> SpawnedObjectsList;
|
||||
@@ -38,6 +43,11 @@ namespace Unity.Netcode
|
||||
|
||||
BytePacker.WriteValueBitPacked(writer, OwnerClientId);
|
||||
BytePacker.WriteValueBitPacked(writer, NetworkTick);
|
||||
if (IsDistributedAuthority)
|
||||
{
|
||||
writer.WriteValueSafe(IsRestoredSession);
|
||||
BytePacker.WriteValueBitPacked(writer, CurrentSessionOwner);
|
||||
}
|
||||
|
||||
if (targetVersion >= k_VersionAddClientIds)
|
||||
{
|
||||
@@ -59,7 +69,8 @@ namespace Unity.Netcode
|
||||
if (sobj.SpawnWithObservers && (sobj.CheckObjectVisibility == null || sobj.CheckObjectVisibility(OwnerClientId)))
|
||||
{
|
||||
sobj.Observers.Add(OwnerClientId);
|
||||
var sceneObject = sobj.GetMessageSceneObject(OwnerClientId);
|
||||
// In distributed authority mode, we send the currently known observers of each NetworkObject to the client being synchronized.
|
||||
var sceneObject = sobj.GetMessageSceneObject(OwnerClientId, IsDistributedAuthority);
|
||||
sceneObject.Serialize(writer);
|
||||
++sceneObjectCount;
|
||||
}
|
||||
@@ -114,6 +125,11 @@ namespace Unity.Netcode
|
||||
|
||||
ByteUnpacker.ReadValueBitPacked(reader, out OwnerClientId);
|
||||
ByteUnpacker.ReadValueBitPacked(reader, out NetworkTick);
|
||||
if (networkManager.DistributedAuthorityMode)
|
||||
{
|
||||
reader.ReadValueSafe(out IsRestoredSession);
|
||||
ByteUnpacker.ReadValueBitPacked(reader, out CurrentSessionOwner);
|
||||
}
|
||||
|
||||
if (receivedMessageVersion >= k_VersionAddClientIds)
|
||||
{
|
||||
@@ -131,10 +147,23 @@ namespace Unity.Netcode
|
||||
public void Handle(ref NetworkContext context)
|
||||
{
|
||||
var networkManager = (NetworkManager)context.SystemOwner;
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
|
||||
{
|
||||
NetworkLog.LogInfo($"[Client-{OwnerClientId}] Connection approved! Synchronizing...");
|
||||
}
|
||||
networkManager.LocalClientId = OwnerClientId;
|
||||
networkManager.MessageManager.SetLocalClientId(networkManager.LocalClientId);
|
||||
networkManager.NetworkMetrics.SetConnectionId(networkManager.LocalClientId);
|
||||
|
||||
if (networkManager.DistributedAuthorityMode)
|
||||
{
|
||||
networkManager.SetSessionOwner(CurrentSessionOwner);
|
||||
if (networkManager.LocalClient.IsSessionOwner && networkManager.NetworkConfig.EnableSceneManagement)
|
||||
{
|
||||
networkManager.SceneManager.InitializeScenesLoaded();
|
||||
}
|
||||
}
|
||||
|
||||
var time = new NetworkTime(networkManager.NetworkTickSystem.TickRate, NetworkTick);
|
||||
networkManager.NetworkTimeSystem.Reset(time.Time, 0.15f); // Start with a constant RTT of 150 until we receive values from the transport.
|
||||
networkManager.NetworkTickSystem.Reset(networkManager.NetworkTimeSystem.LocalTime, networkManager.NetworkTimeSystem.ServerTime);
|
||||
@@ -148,13 +177,26 @@ namespace Unity.Netcode
|
||||
networkManager.ConnectionManager.ConnectedClientIds.Clear();
|
||||
foreach (var clientId in ConnectedClientIds)
|
||||
{
|
||||
networkManager.ConnectionManager.ConnectedClientIds.Add(clientId);
|
||||
if (!networkManager.ConnectionManager.ConnectedClientIds.Contains(clientId))
|
||||
{
|
||||
networkManager.ConnectionManager.AddClient(clientId);
|
||||
}
|
||||
}
|
||||
|
||||
// Only if scene management is disabled do we handle NetworkObject synchronization at this point
|
||||
if (!networkManager.NetworkConfig.EnableSceneManagement)
|
||||
{
|
||||
networkManager.SpawnManager.DestroySceneObjects();
|
||||
// DANGO-TODO: This is a temporary fix for no DA CMB scene event handling.
|
||||
// We will either use this same concept or provide some way for the CMB state plugin to handle it.
|
||||
if (networkManager.DistributedAuthorityMode && networkManager.LocalClient.IsSessionOwner)
|
||||
{
|
||||
networkManager.SpawnManager.ServerSpawnSceneObjectsOnStartSweep();
|
||||
}
|
||||
else
|
||||
{
|
||||
networkManager.SpawnManager.DestroySceneObjects();
|
||||
}
|
||||
|
||||
m_ReceivedSceneObjectData.ReadValueSafe(out uint sceneObjectCount);
|
||||
|
||||
// Deserializing NetworkVariable data is deferred from Receive() to Handle to avoid needing
|
||||
@@ -168,10 +210,38 @@ namespace Unity.Netcode
|
||||
|
||||
// Mark the client being connected
|
||||
networkManager.IsConnectedClient = true;
|
||||
|
||||
if (networkManager.AutoSpawnPlayerPrefabClientSide)
|
||||
{
|
||||
networkManager.ConnectionManager.CreateAndSpawnPlayer(OwnerClientId);
|
||||
}
|
||||
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
|
||||
{
|
||||
NetworkLog.LogInfo($"[Client-{OwnerClientId}][Scene Management Disabled] Synchronization complete!");
|
||||
}
|
||||
// When scene management is disabled we notify after everything is synchronized
|
||||
networkManager.ConnectionManager.InvokeOnClientConnectedCallback(context.SenderId);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (networkManager.DistributedAuthorityMode && networkManager.CMBServiceConnection && networkManager.LocalClient.IsSessionOwner && networkManager.NetworkConfig.EnableSceneManagement)
|
||||
{
|
||||
// Mark the client being connected
|
||||
networkManager.IsConnectedClient = true;
|
||||
|
||||
// Spawn any in-scene placed NetworkObjects
|
||||
networkManager.SpawnManager.ServerSpawnSceneObjectsOnStartSweep();
|
||||
|
||||
// Spawn the local player of the session owner
|
||||
if (networkManager.AutoSpawnPlayerPrefabClientSide)
|
||||
{
|
||||
networkManager.ConnectionManager.CreateAndSpawnPlayer(OwnerClientId);
|
||||
}
|
||||
// Synchronize the service with the initial session owner's loaded scenes and spawned objects
|
||||
networkManager.SceneManager.SynchronizeNetworkObjects(NetworkManager.ServerClientId);
|
||||
}
|
||||
}
|
||||
ConnectedClientIds.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,10 @@ namespace Unity.Netcode
|
||||
|
||||
public ulong ConfigHash;
|
||||
|
||||
public bool CMBServiceConnection;
|
||||
public uint TickRate;
|
||||
public bool EnableSceneManagement;
|
||||
|
||||
public byte[] ConnectionData;
|
||||
|
||||
public bool ShouldSendConnectionData;
|
||||
@@ -30,6 +34,12 @@ namespace Unity.Netcode
|
||||
// END FORBIDDEN SEGMENT
|
||||
// ============================================================
|
||||
|
||||
if (CMBServiceConnection)
|
||||
{
|
||||
writer.WriteValueSafe(TickRate);
|
||||
writer.WriteValueSafe(EnableSceneManagement);
|
||||
}
|
||||
|
||||
if (ShouldSendConnectionData)
|
||||
{
|
||||
writer.WriteValueSafe(ConfigHash);
|
||||
@@ -151,7 +161,7 @@ namespace Unity.Netcode
|
||||
var response = new NetworkManager.ConnectionApprovalResponse
|
||||
{
|
||||
Approved = true,
|
||||
CreatePlayerObject = networkManager.NetworkConfig.PlayerPrefab != null
|
||||
CreatePlayerObject = networkManager.DistributedAuthorityMode && networkManager.AutoSpawnPlayerPrefabClientSide ? false : networkManager.NetworkConfig.PlayerPrefab != null
|
||||
};
|
||||
networkManager.ConnectionManager.HandleConnectionApproval(senderId, response);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
internal struct CreateObjectMessage : INetworkMessage
|
||||
@@ -8,9 +10,109 @@ namespace Unity.Netcode
|
||||
public NetworkObject.SceneObject ObjectInfo;
|
||||
private FastBufferReader m_ReceivedNetworkVariableData;
|
||||
|
||||
// DA - NGO CMB SERVICE NOTES:
|
||||
// The ObserverIds and ExistingObserverIds will only be populated if k_UpdateObservers is set
|
||||
// ObserverIds is the full list of observers (see below)
|
||||
internal ulong[] ObserverIds;
|
||||
|
||||
// While this does consume a bit more bandwidth, this is only sent by the authority/owner
|
||||
// and can be used to determine which clients should receive the ObjectInfo serialized data.
|
||||
// All other already existing observers just need to receive the NewObserverIds and the
|
||||
// NetworkObjectId
|
||||
internal ulong[] NewObserverIds;
|
||||
|
||||
// If !IncludesSerializedObject then the NetworkObjectId will be serialized.
|
||||
// This happens when we are just sending an update to the observers list
|
||||
// to clients that already have the NetworkObject spawned
|
||||
internal ulong NetworkObjectId;
|
||||
|
||||
private const byte k_IncludesSerializedObject = 0x01;
|
||||
private const byte k_UpdateObservers = 0x02;
|
||||
private const byte k_UpdateNewObservers = 0x04;
|
||||
|
||||
|
||||
private byte m_CreateObjectMessageTypeFlags;
|
||||
|
||||
internal bool IncludesSerializedObject
|
||||
{
|
||||
get
|
||||
{
|
||||
return GetFlag(k_IncludesSerializedObject);
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
SetFlag(value, k_IncludesSerializedObject);
|
||||
}
|
||||
}
|
||||
|
||||
internal bool UpdateObservers
|
||||
{
|
||||
get
|
||||
{
|
||||
return GetFlag(k_UpdateObservers);
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
SetFlag(value, k_UpdateObservers);
|
||||
}
|
||||
}
|
||||
|
||||
internal bool UpdateNewObservers
|
||||
{
|
||||
get
|
||||
{
|
||||
return GetFlag(k_UpdateNewObservers);
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
SetFlag(value, k_UpdateNewObservers);
|
||||
}
|
||||
}
|
||||
|
||||
private bool GetFlag(int flag)
|
||||
{
|
||||
return (m_CreateObjectMessageTypeFlags & flag) != 0;
|
||||
}
|
||||
|
||||
private void SetFlag(bool set, byte flag)
|
||||
{
|
||||
if (set) { m_CreateObjectMessageTypeFlags = (byte)(m_CreateObjectMessageTypeFlags | flag); }
|
||||
else { m_CreateObjectMessageTypeFlags = (byte)(m_CreateObjectMessageTypeFlags & ~flag); }
|
||||
}
|
||||
|
||||
public void Serialize(FastBufferWriter writer, int targetVersion)
|
||||
{
|
||||
ObjectInfo.Serialize(writer);
|
||||
writer.WriteValueSafe(m_CreateObjectMessageTypeFlags);
|
||||
|
||||
if (UpdateObservers)
|
||||
{
|
||||
BytePacker.WriteValuePacked(writer, ObserverIds.Length);
|
||||
foreach (var clientId in ObserverIds)
|
||||
{
|
||||
BytePacker.WriteValuePacked(writer, clientId);
|
||||
}
|
||||
}
|
||||
|
||||
if (UpdateNewObservers)
|
||||
{
|
||||
BytePacker.WriteValuePacked(writer, NewObserverIds.Length);
|
||||
foreach (var clientId in NewObserverIds)
|
||||
{
|
||||
BytePacker.WriteValuePacked(writer, clientId);
|
||||
}
|
||||
}
|
||||
|
||||
if (IncludesSerializedObject)
|
||||
{
|
||||
ObjectInfo.Serialize(writer);
|
||||
}
|
||||
else
|
||||
{
|
||||
BytePacker.WriteValuePacked(writer, NetworkObjectId);
|
||||
}
|
||||
}
|
||||
|
||||
public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion)
|
||||
@@ -21,10 +123,45 @@ namespace Unity.Netcode
|
||||
return false;
|
||||
}
|
||||
|
||||
ObjectInfo.Deserialize(reader);
|
||||
reader.ReadValueSafe(out m_CreateObjectMessageTypeFlags);
|
||||
if (UpdateObservers)
|
||||
{
|
||||
var length = 0;
|
||||
ByteUnpacker.ReadValuePacked(reader, out length);
|
||||
ObserverIds = new ulong[length];
|
||||
var clientId = (ulong)0;
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
ByteUnpacker.ReadValuePacked(reader, out clientId);
|
||||
ObserverIds[i] = clientId;
|
||||
}
|
||||
}
|
||||
|
||||
if (UpdateNewObservers)
|
||||
{
|
||||
var length = 0;
|
||||
ByteUnpacker.ReadValuePacked(reader, out length);
|
||||
NewObserverIds = new ulong[length];
|
||||
var clientId = (ulong)0;
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
ByteUnpacker.ReadValuePacked(reader, out clientId);
|
||||
NewObserverIds[i] = clientId;
|
||||
}
|
||||
}
|
||||
|
||||
if (IncludesSerializedObject)
|
||||
{
|
||||
ObjectInfo.Deserialize(reader);
|
||||
}
|
||||
else
|
||||
{
|
||||
ByteUnpacker.ReadValuePacked(reader, out NetworkObjectId);
|
||||
}
|
||||
|
||||
if (!networkManager.NetworkConfig.ForceSamePrefabs && !networkManager.SpawnManager.HasPrefab(ObjectInfo))
|
||||
{
|
||||
networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnAddPrefab, ObjectInfo.Hash, reader, ref context);
|
||||
networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnAddPrefab, ObjectInfo.Hash, reader, ref context, GetType().Name);
|
||||
return false;
|
||||
}
|
||||
m_ReceivedNetworkVariableData = reader;
|
||||
@@ -38,21 +175,130 @@ namespace Unity.Netcode
|
||||
// If a client receives a create object message and it is still synchronizing, then defer the object creation until it has finished synchronizing
|
||||
if (networkManager.SceneManager.ShouldDeferCreateObject())
|
||||
{
|
||||
networkManager.SceneManager.DeferCreateObject(context.SenderId, context.MessageSize, ObjectInfo, m_ReceivedNetworkVariableData);
|
||||
networkManager.SceneManager.DeferCreateObject(context.SenderId, context.MessageSize, ObjectInfo, m_ReceivedNetworkVariableData, ObserverIds, NewObserverIds);
|
||||
}
|
||||
else
|
||||
{
|
||||
CreateObject(ref networkManager, context.SenderId, context.MessageSize, ObjectInfo, m_ReceivedNetworkVariableData);
|
||||
if (networkManager.DistributedAuthorityMode && !IncludesSerializedObject && UpdateObservers)
|
||||
{
|
||||
ObjectInfo = new NetworkObject.SceneObject()
|
||||
{
|
||||
NetworkObjectId = NetworkObjectId,
|
||||
};
|
||||
}
|
||||
CreateObject(ref networkManager, context.SenderId, context.MessageSize, ObjectInfo, m_ReceivedNetworkVariableData, ObserverIds, NewObserverIds);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static void CreateObject(ref NetworkManager networkManager, ulong senderId, uint messageSize, NetworkObject.SceneObject sceneObject, FastBufferReader networkVariableData)
|
||||
internal static void CreateObject(ref NetworkManager networkManager, ref NetworkSceneManager.DeferredObjectCreation deferredObjectCreation)
|
||||
{
|
||||
var senderId = deferredObjectCreation.SenderId;
|
||||
var observerIds = deferredObjectCreation.ObserverIds;
|
||||
var newObserverIds = deferredObjectCreation.NewObserverIds;
|
||||
var messageSize = deferredObjectCreation.MessageSize;
|
||||
var sceneObject = deferredObjectCreation.SceneObject;
|
||||
var networkVariableData = deferredObjectCreation.FastBufferReader;
|
||||
CreateObject(ref networkManager, senderId, messageSize, sceneObject, networkVariableData, observerIds, newObserverIds);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static void CreateObject(ref NetworkManager networkManager, ulong senderId, uint messageSize, NetworkObject.SceneObject sceneObject, FastBufferReader networkVariableData, ulong[] observerIds, ulong[] newObserverIds)
|
||||
{
|
||||
var networkObject = (NetworkObject)null;
|
||||
try
|
||||
{
|
||||
var networkObject = NetworkObject.AddSceneObject(sceneObject, networkVariableData, networkManager);
|
||||
networkManager.NetworkMetrics.TrackObjectSpawnReceived(senderId, networkObject, messageSize);
|
||||
if (!networkManager.DistributedAuthorityMode)
|
||||
{
|
||||
networkObject = NetworkObject.AddSceneObject(sceneObject, networkVariableData, networkManager);
|
||||
}
|
||||
else
|
||||
{
|
||||
var hasObserverIdList = observerIds != null && observerIds.Length > 0;
|
||||
var hasNewObserverIdList = newObserverIds != null && newObserverIds.Length > 0;
|
||||
// Depending upon visibility of the NetworkObject and the client in question, it could be that
|
||||
// this client already has visibility of this NetworkObject
|
||||
if (networkManager.SpawnManager.SpawnedObjects.ContainsKey(sceneObject.NetworkObjectId))
|
||||
{
|
||||
// If so, then just get the local instance
|
||||
networkObject = networkManager.SpawnManager.SpawnedObjects[sceneObject.NetworkObjectId];
|
||||
|
||||
// This should not happen, logging error just in case
|
||||
if (hasNewObserverIdList && newObserverIds.Contains(networkManager.LocalClientId))
|
||||
{
|
||||
NetworkLog.LogErrorServer($"[{nameof(CreateObjectMessage)}][Duplicate-Broadcast] Detected duplicated object creation for {sceneObject.NetworkObjectId}!");
|
||||
}
|
||||
else // Trap to make sure the owner is not receiving any messages it sent
|
||||
if (networkManager.CMBServiceConnection && networkManager.LocalClientId == networkObject.OwnerClientId)
|
||||
{
|
||||
NetworkLog.LogWarning($"[{nameof(CreateObjectMessage)}][Client-{networkManager.LocalClientId}][Duplicate-CreateObjectMessage][Client Is Owner] Detected duplicated object creation for {networkObject.name}-{sceneObject.NetworkObjectId}!");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
networkObject = NetworkObject.AddSceneObject(sceneObject, networkVariableData, networkManager, true);
|
||||
}
|
||||
|
||||
// DA - NGO CMB SERVICE NOTES:
|
||||
// It is possible for two clients to connect at the exact same time which, due to client-side spawning, can cause each client
|
||||
// to miss their spawns. For now, all player NetworkObject spawns will always be visible to all known connected clients
|
||||
var clientList = hasObserverIdList && !networkObject.IsPlayerObject ? observerIds : networkManager.ConnectedClientsIds;
|
||||
|
||||
// Update the observers for this instance
|
||||
foreach (var clientId in clientList)
|
||||
{
|
||||
networkObject.Observers.Add(clientId);
|
||||
}
|
||||
|
||||
// Mock CMB Service and forward to all clients
|
||||
if (networkManager.DAHost)
|
||||
{
|
||||
// DA - NGO CMB SERVICE NOTES:
|
||||
// (*** See above notes fist ***)
|
||||
// If it is a player object freshly spawning and one or more clients all connect at the exact same time (i.e. received on effectively
|
||||
// the same frame), then we need to check the observers list to make sure all players are visible upon first spawning. At a later date,
|
||||
// for area of interest we will need to have some form of follow up "observer update" message to cull out players not within each
|
||||
// player's AOI.
|
||||
if (networkObject.IsPlayerObject && hasNewObserverIdList && clientList.Count != observerIds.Length)
|
||||
{
|
||||
// For same-frame newly spawned players that might not be aware of all other players, update the player's observer
|
||||
// list.
|
||||
observerIds = clientList.ToArray();
|
||||
}
|
||||
|
||||
var createObjectMessage = new CreateObjectMessage()
|
||||
{
|
||||
ObjectInfo = sceneObject,
|
||||
m_ReceivedNetworkVariableData = networkVariableData,
|
||||
ObserverIds = hasObserverIdList ? observerIds : null,
|
||||
NetworkObjectId = networkObject.NetworkObjectId,
|
||||
IncludesSerializedObject = true,
|
||||
};
|
||||
foreach (var clientId in clientList)
|
||||
{
|
||||
// DA - NGO CMB SERVICE NOTES:
|
||||
// If the authority did not specify the list of clients and the client is not an observer or we are the owner/originator
|
||||
// or we are the DAHost, then we skip sending the message.
|
||||
if ((!hasObserverIdList && (!networkObject.Observers.Contains(clientId)) ||
|
||||
clientId == networkObject.OwnerClientId || clientId == NetworkManager.ServerClientId))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// DA - NGO CMB SERVICE NOTES:
|
||||
// If this included a list of new observers and the targeted clientId is one of the observers, then send the serialized data.
|
||||
// Otherwise, the targeted clientId has already has visibility (i.e. it is already spawned) and so just send the updated
|
||||
// observers list to that client's instance.
|
||||
createObjectMessage.IncludesSerializedObject = hasNewObserverIdList && newObserverIds.Contains(clientId);
|
||||
|
||||
networkManager.SpawnManager.SendSpawnCallForObject(clientId, networkObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (networkObject != null)
|
||||
{
|
||||
networkManager.NetworkMetrics.TrackObjectSpawnReceived(senderId, networkObject, messageSize);
|
||||
}
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using System.Linq;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
internal struct DestroyObjectMessage : INetworkMessage, INetworkSerializeByMemcpy
|
||||
@@ -6,10 +8,53 @@ namespace Unity.Netcode
|
||||
|
||||
public ulong NetworkObjectId;
|
||||
public bool DestroyGameObject;
|
||||
private byte m_DestroyFlags;
|
||||
|
||||
internal int DeferredDespawnTick;
|
||||
// Temporary until we make this a list
|
||||
internal ulong TargetClientId;
|
||||
|
||||
internal bool IsDistributedAuthority;
|
||||
|
||||
internal const byte ClientTargetedDestroy = 0x01;
|
||||
|
||||
internal bool IsTargetedDestroy
|
||||
{
|
||||
get
|
||||
{
|
||||
return GetFlag(ClientTargetedDestroy);
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
SetFlag(value, ClientTargetedDestroy);
|
||||
}
|
||||
}
|
||||
|
||||
private bool GetFlag(int flag)
|
||||
{
|
||||
return (m_DestroyFlags & flag) != 0;
|
||||
}
|
||||
|
||||
private void SetFlag(bool set, byte flag)
|
||||
{
|
||||
if (set) { m_DestroyFlags = (byte)(m_DestroyFlags | flag); }
|
||||
else { m_DestroyFlags = (byte)(m_DestroyFlags & ~flag); }
|
||||
}
|
||||
|
||||
public void Serialize(FastBufferWriter writer, int targetVersion)
|
||||
{
|
||||
BytePacker.WriteValueBitPacked(writer, NetworkObjectId);
|
||||
if (IsDistributedAuthority)
|
||||
{
|
||||
writer.WriteByteSafe(m_DestroyFlags);
|
||||
|
||||
if (IsTargetedDestroy)
|
||||
{
|
||||
BytePacker.WriteValueBitPacked(writer, TargetClientId);
|
||||
}
|
||||
BytePacker.WriteValueBitPacked(writer, DeferredDespawnTick);
|
||||
}
|
||||
writer.WriteValueSafe(DestroyGameObject);
|
||||
}
|
||||
|
||||
@@ -22,12 +67,25 @@ namespace Unity.Netcode
|
||||
}
|
||||
|
||||
ByteUnpacker.ReadValueBitPacked(reader, out NetworkObjectId);
|
||||
if (networkManager.DistributedAuthorityMode)
|
||||
{
|
||||
reader.ReadByteSafe(out m_DestroyFlags);
|
||||
if (IsTargetedDestroy)
|
||||
{
|
||||
ByteUnpacker.ReadValueBitPacked(reader, out TargetClientId);
|
||||
}
|
||||
ByteUnpacker.ReadValueBitPacked(reader, out DeferredDespawnTick);
|
||||
}
|
||||
|
||||
reader.ReadValueSafe(out DestroyGameObject);
|
||||
|
||||
if (!networkManager.SpawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out var networkObject))
|
||||
{
|
||||
networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnSpawn, NetworkObjectId, reader, ref context);
|
||||
return false;
|
||||
// Client-Server mode we always defer where in distributed authority mode we only defer if it is not a targeted destroy
|
||||
if (!networkManager.DistributedAuthorityMode || (networkManager.DistributedAuthorityMode && !IsTargetedDestroy))
|
||||
{
|
||||
networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnSpawn, NetworkObjectId, reader, ref context, GetType().Name);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -35,14 +93,80 @@ namespace Unity.Netcode
|
||||
public void Handle(ref NetworkContext context)
|
||||
{
|
||||
var networkManager = (NetworkManager)context.SystemOwner;
|
||||
if (!networkManager.SpawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out var networkObject))
|
||||
|
||||
var networkObject = (NetworkObject)null;
|
||||
if (!networkManager.DistributedAuthorityMode)
|
||||
{
|
||||
// This is the same check and log message that happens inside OnDespawnObject, but we have to do it here
|
||||
return;
|
||||
// If this NetworkObject does not exist on this instance then exit early
|
||||
if (!networkManager.SpawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out networkObject))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
networkManager.SpawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out networkObject);
|
||||
if (!networkManager.DAHost && networkObject == null)
|
||||
{
|
||||
// If this NetworkObject does not exist on this instance then exit early
|
||||
return;
|
||||
}
|
||||
}
|
||||
// DANGO-TODO: This is just a quick way to foward despawn messages to the remaining clients
|
||||
if (networkManager.DistributedAuthorityMode && networkManager.DAHost)
|
||||
{
|
||||
var message = new DestroyObjectMessage
|
||||
{
|
||||
NetworkObjectId = NetworkObjectId,
|
||||
DestroyGameObject = DestroyGameObject,
|
||||
IsDistributedAuthority = true,
|
||||
IsTargetedDestroy = IsTargetedDestroy,
|
||||
TargetClientId = TargetClientId, // Just always populate this value whether we write it or not
|
||||
DeferredDespawnTick = DeferredDespawnTick,
|
||||
};
|
||||
var ownerClientId = networkObject == null ? context.SenderId : networkObject.OwnerClientId;
|
||||
var clientIds = networkObject == null ? networkManager.ConnectedClientsIds.ToList() : networkObject.Observers.ToList();
|
||||
|
||||
foreach (var clientId in clientIds)
|
||||
{
|
||||
if (clientId == networkManager.LocalClientId || clientId == ownerClientId)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
networkManager.ConnectionManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, clientId);
|
||||
}
|
||||
}
|
||||
|
||||
networkManager.NetworkMetrics.TrackObjectDestroyReceived(context.SenderId, networkObject, context.MessageSize);
|
||||
networkManager.SpawnManager.OnDespawnObject(networkObject, DestroyGameObject);
|
||||
// If we are deferring the despawn, then add it to the deferred despawn queue
|
||||
if (networkManager.DistributedAuthorityMode)
|
||||
{
|
||||
if (DeferredDespawnTick > 0)
|
||||
{
|
||||
// Clients always add it to the queue while DAHost will only add it to the queue if it is not a targeted destroy or it is and the target is the
|
||||
// DAHost client.
|
||||
if (!networkManager.DAHost || (networkManager.DAHost && (!IsTargetedDestroy || (IsTargetedDestroy && TargetClientId == 0))))
|
||||
{
|
||||
networkObject.DeferredDespawnTick = DeferredDespawnTick;
|
||||
var hasCallback = networkObject.OnDeferredDespawnComplete != null;
|
||||
networkManager.SpawnManager.DeferDespawnNetworkObject(NetworkObjectId, DeferredDespawnTick, hasCallback);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If this is targeted and we are not the target, then just update our local observers for this object
|
||||
if (IsTargetedDestroy && TargetClientId != networkManager.LocalClientId && networkObject != null)
|
||||
{
|
||||
networkObject.Observers.Remove(TargetClientId);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (networkObject != null)
|
||||
{
|
||||
// Otherwise just despawn the NetworkObject right now
|
||||
networkManager.SpawnManager.OnDespawnObject(networkObject, DestroyGameObject);
|
||||
networkManager.NetworkMetrics.TrackObjectDestroyReceived(context.SenderId, networkObject, context.MessageSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,8 @@ namespace Unity.Netcode
|
||||
|
||||
private FastBufferReader m_ReceivedNetworkVariableData;
|
||||
|
||||
// DANGO-TODO: Made some modifications here that overlap/won't play nice with EnsureNetworkVariableLenghtSafety.
|
||||
// Worth either merging or more cleanly separating these codepaths.
|
||||
public void Serialize(FastBufferWriter writer, int targetVersion)
|
||||
{
|
||||
if (!writer.TryBeginWrite(FastBufferWriter.GetWriteSize(NetworkObjectId) + FastBufferWriter.GetWriteSize(NetworkBehaviourIndex)))
|
||||
@@ -30,15 +32,26 @@ namespace Unity.Netcode
|
||||
throw new OverflowException($"Not enough space in the buffer to write {nameof(NetworkVariableDeltaMessage)}");
|
||||
}
|
||||
|
||||
var obj = NetworkBehaviour.NetworkObject;
|
||||
var networkManager = obj.NetworkManagerOwner;
|
||||
|
||||
BytePacker.WriteValueBitPacked(writer, NetworkObjectId);
|
||||
BytePacker.WriteValueBitPacked(writer, NetworkBehaviourIndex);
|
||||
if (networkManager.DistributedAuthorityMode)
|
||||
{
|
||||
writer.WriteValueSafe((ushort)NetworkBehaviour.NetworkVariableFields.Count);
|
||||
}
|
||||
|
||||
for (int i = 0; i < NetworkBehaviour.NetworkVariableFields.Count; i++)
|
||||
{
|
||||
if (!DeliveryMappedNetworkVariableIndex.Contains(i))
|
||||
{
|
||||
// This var does not belong to the currently iterating delivery group.
|
||||
if (NetworkBehaviour.NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
|
||||
if (networkManager.DistributedAuthorityMode)
|
||||
{
|
||||
writer.WriteValueSafe<ushort>(0);
|
||||
}
|
||||
else if (networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
|
||||
{
|
||||
BytePacker.WriteValueBitPacked(writer, (ushort)0);
|
||||
}
|
||||
@@ -54,7 +67,7 @@ namespace Unity.Netcode
|
||||
var networkVariable = NetworkBehaviour.NetworkVariableFields[i];
|
||||
var shouldWrite = networkVariable.IsDirty() &&
|
||||
networkVariable.CanClientRead(TargetClientId) &&
|
||||
(NetworkBehaviour.NetworkManager.IsServer || networkVariable.CanClientWrite(NetworkBehaviour.NetworkManager.LocalClientId));
|
||||
(networkManager.IsServer || networkVariable.CanClientWrite(networkManager.LocalClientId));
|
||||
|
||||
// Prevent the server from writing to the client that owns a given NetworkVariable
|
||||
// Allowing the write would send an old value to the client and cause jitter
|
||||
@@ -67,14 +80,21 @@ namespace Unity.Netcode
|
||||
// The object containing the behaviour we're about to process is about to be shown to this client
|
||||
// As a result, the client will get the fully serialized NetworkVariable and would be confused by
|
||||
// an extraneous delta
|
||||
if (NetworkBehaviour.NetworkManager.SpawnManager.ObjectsToShowToClient.ContainsKey(TargetClientId) &&
|
||||
NetworkBehaviour.NetworkManager.SpawnManager.ObjectsToShowToClient[TargetClientId]
|
||||
.Contains(NetworkBehaviour.NetworkObject))
|
||||
if (networkManager.SpawnManager.ObjectsToShowToClient.ContainsKey(TargetClientId) &&
|
||||
networkManager.SpawnManager.ObjectsToShowToClient[TargetClientId]
|
||||
.Contains(obj))
|
||||
{
|
||||
shouldWrite = false;
|
||||
}
|
||||
|
||||
if (NetworkBehaviour.NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
|
||||
if (networkManager.DistributedAuthorityMode)
|
||||
{
|
||||
if (!shouldWrite)
|
||||
{
|
||||
writer.WriteValueSafe<ushort>(0);
|
||||
}
|
||||
}
|
||||
else if (networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
|
||||
{
|
||||
if (!shouldWrite)
|
||||
{
|
||||
@@ -88,9 +108,9 @@ namespace Unity.Netcode
|
||||
|
||||
if (shouldWrite)
|
||||
{
|
||||
if (NetworkBehaviour.NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
|
||||
if (networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
|
||||
{
|
||||
var tempWriter = new FastBufferWriter(NetworkBehaviour.NetworkManager.MessageManager.NonFragmentedMessageMaxSize, Allocator.Temp, NetworkBehaviour.NetworkManager.MessageManager.FragmentedMessageMaxSize);
|
||||
var tempWriter = new FastBufferWriter(networkManager.MessageManager.NonFragmentedMessageMaxSize, Allocator.Temp, networkManager.MessageManager.FragmentedMessageMaxSize);
|
||||
NetworkBehaviour.NetworkVariableFields[i].WriteDelta(tempWriter);
|
||||
BytePacker.WriteValueBitPacked(writer, tempWriter.Length);
|
||||
|
||||
@@ -103,11 +123,30 @@ namespace Unity.Netcode
|
||||
}
|
||||
else
|
||||
{
|
||||
networkVariable.WriteDelta(writer);
|
||||
// DANGO-TODO:
|
||||
// Complex types with custom type serialization (either registered custom types or INetworkSerializable implementations) will be problematic
|
||||
// Non-complex types always provide a full state update per delta
|
||||
// DANGO-TODO: Add NetworkListEvent<T>.EventType awareness to the cloud-state server
|
||||
if (networkManager.DistributedAuthorityMode)
|
||||
{
|
||||
var size_marker = writer.Position;
|
||||
writer.WriteValueSafe<ushort>(0);
|
||||
var start_marker = writer.Position;
|
||||
networkVariable.WriteDelta(writer);
|
||||
var end_marker = writer.Position;
|
||||
writer.Seek(size_marker);
|
||||
var size = end_marker - start_marker;
|
||||
writer.WriteValueSafe((ushort)size);
|
||||
writer.Seek(end_marker);
|
||||
}
|
||||
else
|
||||
{
|
||||
networkVariable.WriteDelta(writer);
|
||||
}
|
||||
}
|
||||
NetworkBehaviour.NetworkManager.NetworkMetrics.TrackNetworkVariableDeltaSent(
|
||||
networkManager.NetworkMetrics.TrackNetworkVariableDeltaSent(
|
||||
TargetClientId,
|
||||
NetworkBehaviour.NetworkObject,
|
||||
obj,
|
||||
networkVariable.Name,
|
||||
NetworkBehaviour.__getTypeName(),
|
||||
writer.Length - startingSize);
|
||||
@@ -125,6 +164,8 @@ namespace Unity.Netcode
|
||||
return true;
|
||||
}
|
||||
|
||||
// DANGO-TODO: Made some modifications here that overlap/won't play nice with EnsureNetworkVariableLenghtSafety.
|
||||
// Worth either merging or more cleanly separating these codepaths.
|
||||
public void Handle(ref NetworkContext context)
|
||||
{
|
||||
var networkManager = (NetworkManager)context.SystemOwner;
|
||||
@@ -142,10 +183,29 @@ namespace Unity.Netcode
|
||||
}
|
||||
else
|
||||
{
|
||||
if (networkManager.DistributedAuthorityMode)
|
||||
{
|
||||
m_ReceivedNetworkVariableData.ReadValueSafe(out ushort variableCount);
|
||||
if (variableCount != networkBehaviour.NetworkVariableFields.Count)
|
||||
{
|
||||
UnityEngine.Debug.LogError("Variable count mismatch");
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < networkBehaviour.NetworkVariableFields.Count; i++)
|
||||
{
|
||||
int varSize = 0;
|
||||
if (networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
|
||||
if (networkManager.DistributedAuthorityMode)
|
||||
{
|
||||
m_ReceivedNetworkVariableData.ReadValueSafe(out ushort variableSize);
|
||||
varSize = variableSize;
|
||||
|
||||
if (varSize == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
|
||||
{
|
||||
ByteUnpacker.ReadValueBitPacked(m_ReceivedNetworkVariableData, out varSize);
|
||||
|
||||
@@ -197,6 +257,7 @@ namespace Unity.Netcode
|
||||
}
|
||||
int readStartPos = m_ReceivedNetworkVariableData.Position;
|
||||
|
||||
// Read Delta so we also notify any subscribers to a change in the NetworkVariable
|
||||
networkVariable.ReadDelta(m_ReceivedNetworkVariableData, networkManager.IsServer);
|
||||
|
||||
networkManager.NetworkMetrics.TrackNetworkVariableDeltaReceived(
|
||||
@@ -206,7 +267,7 @@ namespace Unity.Netcode
|
||||
networkBehaviour.__getTypeName(),
|
||||
context.MessageSize);
|
||||
|
||||
if (networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
|
||||
if (networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety || networkManager.DistributedAuthorityMode)
|
||||
{
|
||||
if (m_ReceivedNetworkVariableData.Position > (readStartPos + varSize))
|
||||
{
|
||||
@@ -232,7 +293,10 @@ namespace Unity.Netcode
|
||||
}
|
||||
else
|
||||
{
|
||||
networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnSpawn, NetworkObjectId, m_ReceivedNetworkVariableData, ref context);
|
||||
// DANGO-TODO: Fix me!
|
||||
// When a client-spawned NetworkObject is despawned by the owner client, the owner client will still get messages for deltas and cause this to
|
||||
// log a warning. The issue is primarily how NetworkVariables handle updating and will require some additional re-factoring.
|
||||
networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnSpawn, NetworkObjectId, m_ReceivedNetworkVariableData, ref context, GetType().Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,12 @@ namespace Unity.Netcode
|
||||
set => ByteUtility.SetBit(ref m_BitField, 2, value);
|
||||
}
|
||||
|
||||
public bool AuthorityApplied
|
||||
{
|
||||
get => ByteUtility.GetBit(m_BitField, 3);
|
||||
set => ByteUtility.SetBit(ref m_BitField, 3, value);
|
||||
}
|
||||
|
||||
// These additional properties are used to synchronize clients with the current position,
|
||||
// rotation, and scale after parenting/de-parenting (world/local space relative). This
|
||||
// allows users to control the final child's transform values without having to have a
|
||||
@@ -83,9 +89,10 @@ namespace Unity.Netcode
|
||||
reader.ReadValueSafe(out Rotation);
|
||||
reader.ReadValueSafe(out Scale);
|
||||
|
||||
if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(NetworkObjectId))
|
||||
// If the target NetworkObject does not exist =or= the target latest parent does not exist then defer the message
|
||||
if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(NetworkObjectId) || (LatestParent.HasValue && !networkManager.SpawnManager.SpawnedObjects.ContainsKey(LatestParent.Value)))
|
||||
{
|
||||
networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnSpawn, NetworkObjectId, reader, ref context);
|
||||
networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnSpawn, NetworkObjectId, reader, ref context, GetType().Name);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -95,6 +102,16 @@ namespace Unity.Netcode
|
||||
{
|
||||
var networkManager = (NetworkManager)context.SystemOwner;
|
||||
var networkObject = networkManager.SpawnManager.SpawnedObjects[NetworkObjectId];
|
||||
|
||||
// For either DA or Client-Server modes, parenting is only valid if the parent was owned by a different authority (i.e. AuthorityApplied) or the sender is from the owner (DA mode)
|
||||
// or the server (client-server mode).
|
||||
networkObject.AuthorityAppliedParenting = AuthorityApplied || context.SenderId == networkObject.OwnerClientId || context.SenderId == NetworkManager.ServerClientId;
|
||||
if (!networkObject.AuthorityAppliedParenting && networkManager.LogLevel <= LogLevel.Normal)
|
||||
{
|
||||
NetworkLog.LogWarningServer($"Client-{context.SenderId} sent a ParentSyncMessage but is not the authority of {networkObject.gameObject.name}'s {nameof(NetworkObject)} component!");
|
||||
// DANGO-TODO: Still determining if we should not apply this change (I am leaning towards not allowing it).
|
||||
}
|
||||
|
||||
networkObject.SetNetworkParenting(LatestParent, WorldPositionStays);
|
||||
networkObject.ApplyNetworkParenting(RemoveParent);
|
||||
|
||||
@@ -111,6 +128,30 @@ namespace Unity.Netcode
|
||||
networkObject.transform.rotation = Rotation;
|
||||
}
|
||||
networkObject.transform.localScale = Scale;
|
||||
|
||||
// If in distributed authority mode and we are running a DAHost and this is the DAHost, then forward the parent changed message to any remaining clients
|
||||
if (networkManager.DistributedAuthorityMode && !networkManager.CMBServiceConnection && networkManager.DAHost)
|
||||
{
|
||||
var size = 0;
|
||||
var message = this;
|
||||
|
||||
foreach (var client in networkManager.ConnectedClients)
|
||||
{
|
||||
if (client.Value.ClientId == networkObject.OwnerClientId || client.Value.ClientId == networkManager.LocalClientId)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (networkObject.IsNetworkVisibleTo(client.Value.ClientId))
|
||||
{
|
||||
size = networkManager.ConnectionManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, client.Value.ClientId);
|
||||
networkManager.NetworkMetrics.TrackOwnershipChangeSent(client.Key, networkObject, size);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log($"[DAHost][ParentingProxy] Client-{client.Value.ClientId} has no visibility to {networkObject.name}!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,11 +31,24 @@ namespace Unity.Netcode
|
||||
|
||||
public unsafe void Handle(ref NetworkContext context)
|
||||
{
|
||||
|
||||
var networkManager = (NetworkManager)context.SystemOwner;
|
||||
if (!networkManager.SpawnManager.SpawnedObjects.TryGetValue(WrappedMessage.Metadata.NetworkObjectId, out var networkObject))
|
||||
{
|
||||
throw new InvalidOperationException($"An RPC called on a {nameof(NetworkObject)} that is not in the spawned objects list. Please make sure the {nameof(NetworkObject)} is spawned before calling RPCs.");
|
||||
// With distributed authority mode, we can send Rpcs before we have been notified the NetworkObject is despawned.
|
||||
// DANGO-TODO: Should the CMB Service cull out any Rpcs targeting recently despawned NetworkObjects?
|
||||
// DANGO-TODO: This would require the service to keep track of despawned NetworkObjects since we re-use NetworkObject identifiers.
|
||||
if (networkManager.DistributedAuthorityMode)
|
||||
{
|
||||
if (networkManager.LogLevel == LogLevel.Developer)
|
||||
{
|
||||
NetworkLog.LogWarning($"[{WrappedMessage.Metadata.NetworkObjectId}, {WrappedMessage.Metadata.NetworkBehaviourId}, {WrappedMessage.Metadata.NetworkRpcMethodId}]An RPC called on a {nameof(NetworkObject)} that is not in the spawned objects list. Please make sure the {nameof(NetworkObject)} is spawned before calling RPCs.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException($"[{WrappedMessage.Metadata.NetworkObjectId}, {WrappedMessage.Metadata.NetworkBehaviourId}, {WrappedMessage.Metadata.NetworkRpcMethodId}]An RPC called on a {nameof(NetworkObject)} that is not in the spawned objects list. Please make sure the {nameof(NetworkObject)} is spawned before calling RPCs.");
|
||||
}
|
||||
}
|
||||
|
||||
var observers = networkObject.Observers;
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Unity.Netcode
|
||||
writer.WriteBytesSafe(payload.GetUnsafePtr(), payload.Length);
|
||||
}
|
||||
|
||||
public static unsafe bool Deserialize(ref FastBufferReader reader, ref NetworkContext context, ref RpcMetadata metadata, ref FastBufferReader payload)
|
||||
public static unsafe bool Deserialize(ref FastBufferReader reader, ref NetworkContext context, ref RpcMetadata metadata, ref FastBufferReader payload, string messageType)
|
||||
{
|
||||
ByteUnpacker.ReadValueBitPacked(reader, out metadata.NetworkObjectId);
|
||||
ByteUnpacker.ReadValueBitPacked(reader, out metadata.NetworkBehaviourId);
|
||||
@@ -23,7 +23,7 @@ namespace Unity.Netcode
|
||||
var networkManager = (NetworkManager)context.SystemOwner;
|
||||
if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(metadata.NetworkObjectId))
|
||||
{
|
||||
networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnSpawn, metadata.NetworkObjectId, reader, ref context);
|
||||
networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnSpawn, metadata.NetworkObjectId, reader, ref context, messageType);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -52,7 +52,6 @@ namespace Unity.Netcode
|
||||
reader.Length);
|
||||
}
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -107,7 +106,7 @@ namespace Unity.Netcode
|
||||
|
||||
public unsafe bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion)
|
||||
{
|
||||
return RpcMessageHelpers.Deserialize(ref reader, ref context, ref Metadata, ref ReadBuffer);
|
||||
return RpcMessageHelpers.Deserialize(ref reader, ref context, ref Metadata, ref ReadBuffer, GetType().Name);
|
||||
}
|
||||
|
||||
public void Handle(ref NetworkContext context)
|
||||
@@ -142,7 +141,7 @@ namespace Unity.Netcode
|
||||
|
||||
public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion)
|
||||
{
|
||||
return RpcMessageHelpers.Deserialize(ref reader, ref context, ref Metadata, ref ReadBuffer);
|
||||
return RpcMessageHelpers.Deserialize(ref reader, ref context, ref Metadata, ref ReadBuffer, GetType().Name);
|
||||
}
|
||||
|
||||
public void Handle(ref NetworkContext context)
|
||||
@@ -179,7 +178,8 @@ namespace Unity.Netcode
|
||||
public unsafe bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion)
|
||||
{
|
||||
ByteUnpacker.ReadValuePacked(reader, out SenderClientId);
|
||||
return RpcMessageHelpers.Deserialize(ref reader, ref context, ref Metadata, ref ReadBuffer);
|
||||
|
||||
return RpcMessageHelpers.Deserialize(ref reader, ref context, ref Metadata, ref ReadBuffer, GetType().Name);
|
||||
}
|
||||
|
||||
public void Handle(ref NetworkContext context)
|
||||
@@ -197,4 +197,138 @@ namespace Unity.Netcode
|
||||
RpcMessageHelpers.Handle(ref context, ref Metadata, ref ReadBuffer, ref rpcParams);
|
||||
}
|
||||
}
|
||||
|
||||
// DANGO-EXP TODO: REMOVE THIS
|
||||
internal struct ForwardServerRpcMessage : INetworkMessage
|
||||
{
|
||||
public int Version => 0;
|
||||
public ulong OwnerId;
|
||||
public NetworkDelivery NetworkDelivery;
|
||||
public ServerRpcMessage ServerRpcMessage;
|
||||
|
||||
public unsafe void Serialize(FastBufferWriter writer, int targetVersion)
|
||||
{
|
||||
writer.WriteValueSafe(OwnerId);
|
||||
writer.WriteValueSafe(NetworkDelivery);
|
||||
ServerRpcMessage.Serialize(writer, targetVersion);
|
||||
}
|
||||
|
||||
public unsafe bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion)
|
||||
{
|
||||
reader.ReadValueSafe(out OwnerId);
|
||||
reader.ReadValueSafe(out NetworkDelivery);
|
||||
ServerRpcMessage.ReadBuffer = new FastBufferReader(reader, Allocator.Persistent, reader.Length - reader.Position, sizeof(RpcMetadata));
|
||||
|
||||
// If deserializing failed or this message was deferred.
|
||||
if (!ServerRpcMessage.Deserialize(reader, ref context, receivedMessageVersion))
|
||||
{
|
||||
// release this reader as the handler will either be invoked later (deferred) or will not be invoked at all.
|
||||
ServerRpcMessage.ReadBuffer.Dispose();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Handle(ref NetworkContext context)
|
||||
{
|
||||
var networkManager = (NetworkManager)context.SystemOwner;
|
||||
if (networkManager.DAHost)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Since this is temporary, we will not be collection metrics for this.
|
||||
// DAHost just forwards the message to the owner
|
||||
ServerRpcMessage.WriteBuffer = new FastBufferWriter(ServerRpcMessage.ReadBuffer.Length, Allocator.TempJob);
|
||||
ServerRpcMessage.WriteBuffer.WriteBytesSafe(ServerRpcMessage.ReadBuffer.ToArray());
|
||||
networkManager.ConnectionManager.SendMessage(ref ServerRpcMessage, NetworkDelivery, OwnerId);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogException(ex);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
NetworkLog.LogErrorServer($"Received {nameof(ForwardServerRpcMessage)} on client-{networkManager.LocalClientId}! Only DAHost may forward RPC messages!");
|
||||
}
|
||||
ServerRpcMessage.ReadBuffer.Dispose();
|
||||
ServerRpcMessage.WriteBuffer.Dispose();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// DANGO-EXP TODO: REMOVE THIS
|
||||
internal struct ForwardClientRpcMessage : INetworkMessage
|
||||
{
|
||||
public int Version => 0;
|
||||
public bool BroadCast;
|
||||
public ulong[] TargetClientIds;
|
||||
public NetworkDelivery NetworkDelivery;
|
||||
public ClientRpcMessage ClientRpcMessage;
|
||||
|
||||
public unsafe void Serialize(FastBufferWriter writer, int targetVersion)
|
||||
{
|
||||
if (TargetClientIds == null)
|
||||
{
|
||||
BroadCast = true;
|
||||
writer.WriteValueSafe(BroadCast);
|
||||
}
|
||||
else
|
||||
{
|
||||
BroadCast = false;
|
||||
writer.WriteValueSafe(BroadCast);
|
||||
writer.WriteValueSafe(TargetClientIds);
|
||||
}
|
||||
writer.WriteValueSafe(NetworkDelivery);
|
||||
ClientRpcMessage.Serialize(writer, targetVersion);
|
||||
}
|
||||
|
||||
public unsafe bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion)
|
||||
{
|
||||
reader.ReadValueSafe(out BroadCast);
|
||||
|
||||
if (!BroadCast)
|
||||
{
|
||||
reader.ReadValueSafe(out TargetClientIds);
|
||||
}
|
||||
|
||||
reader.ReadValueSafe(out NetworkDelivery);
|
||||
|
||||
ClientRpcMessage.ReadBuffer = new FastBufferReader(reader, Allocator.Persistent, reader.Length - reader.Position, sizeof(RpcMetadata));
|
||||
// If deserializing failed or this message was deferred.
|
||||
if (!ClientRpcMessage.Deserialize(reader, ref context, receivedMessageVersion))
|
||||
{
|
||||
// release this reader as the handler will either be invoked later (deferred) or will not be invoked at all.
|
||||
ClientRpcMessage.ReadBuffer.Dispose();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Handle(ref NetworkContext context)
|
||||
{
|
||||
var networkManager = (NetworkManager)context.SystemOwner;
|
||||
if (networkManager.DAHost)
|
||||
{
|
||||
ClientRpcMessage.WriteBuffer = new FastBufferWriter(ClientRpcMessage.ReadBuffer.Length, Allocator.TempJob);
|
||||
ClientRpcMessage.WriteBuffer.WriteBytesSafe(ClientRpcMessage.ReadBuffer.ToArray());
|
||||
// Since this is temporary, we will not be collection metrics for this.
|
||||
// DAHost just forwards the message to the clients
|
||||
if (BroadCast)
|
||||
{
|
||||
networkManager.ConnectionManager.SendMessage(ref ClientRpcMessage, NetworkDelivery, networkManager.ConnectedClientsIds);
|
||||
}
|
||||
else
|
||||
{
|
||||
networkManager.ConnectionManager.SendMessage(ref ClientRpcMessage, NetworkDelivery, TargetClientIds);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
NetworkLog.LogErrorServer($"Received {nameof(ForwardClientRpcMessage)} on client-{networkManager.LocalClientId}! Only DAHost may forward RPC messages!");
|
||||
}
|
||||
ClientRpcMessage.WriteBuffer.Dispose();
|
||||
ClientRpcMessage.ReadBuffer.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
// Todo: Would be lovely to get this one nicely formatted with all the data it sends in the struct
|
||||
@@ -8,6 +9,7 @@ namespace Unity.Netcode
|
||||
|
||||
public SceneEventData EventData;
|
||||
|
||||
|
||||
private FastBufferReader m_ReceivedData;
|
||||
|
||||
public void Serialize(FastBufferWriter writer, int targetVersion)
|
||||
@@ -23,7 +25,8 @@ namespace Unity.Netcode
|
||||
|
||||
public void Handle(ref NetworkContext context)
|
||||
{
|
||||
((NetworkManager)context.SystemOwner).SceneManager.HandleSceneEvent(context.SenderId, m_ReceivedData);
|
||||
var networkManager = (NetworkManager)context.SystemOwner;
|
||||
networkManager.SceneManager.HandleSceneEvent(context.SenderId, m_ReceivedData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ namespace Unity.Netcode
|
||||
{
|
||||
public int Version => 0;
|
||||
|
||||
public ulong SenderId;
|
||||
|
||||
public NetworkLog.LogType LogType;
|
||||
// It'd be lovely to be able to replace this with FixedString or NativeArray...
|
||||
// But it's not really practical. On the sending side, the user is likely to want
|
||||
@@ -12,30 +14,39 @@ namespace Unity.Netcode
|
||||
// So an allocation is unavoidable here on both sides.
|
||||
public string Message;
|
||||
|
||||
|
||||
public void Serialize(FastBufferWriter writer, int targetVersion)
|
||||
{
|
||||
writer.WriteValueSafe(LogType);
|
||||
BytePacker.WriteValuePacked(writer, Message);
|
||||
BytePacker.WriteValueBitPacked(writer, SenderId);
|
||||
}
|
||||
|
||||
public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion)
|
||||
{
|
||||
var networkManager = (NetworkManager)context.SystemOwner;
|
||||
if (networkManager.IsServer && networkManager.NetworkConfig.EnableNetworkLogs)
|
||||
|
||||
if ((networkManager.IsServer || networkManager.LocalClient.IsSessionOwner) && networkManager.NetworkConfig.EnableNetworkLogs)
|
||||
{
|
||||
reader.ReadValueSafe(out LogType);
|
||||
ByteUnpacker.ReadValuePacked(reader, out Message);
|
||||
ByteUnpacker.ReadValuePacked(reader, out SenderId);
|
||||
// If in distributed authority mode and the DAHost is not the session owner, then the DAHost will just forward the message.
|
||||
if (networkManager.DAHost && networkManager.CurrentSessionOwner != networkManager.LocalClientId)
|
||||
{
|
||||
var message = this;
|
||||
var size = networkManager.ConnectionManager.SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, networkManager.CurrentSessionOwner);
|
||||
networkManager.NetworkMetrics.TrackServerLogSent(networkManager.CurrentSessionOwner, (uint)LogType, size);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Handle(ref NetworkContext context)
|
||||
{
|
||||
var networkManager = (NetworkManager)context.SystemOwner;
|
||||
var senderId = context.SenderId;
|
||||
var senderId = networkManager.DistributedAuthorityMode ? SenderId : context.SenderId;
|
||||
|
||||
networkManager.NetworkMetrics.TrackServerLogReceived(senderId, (uint)LogType, context.MessageSize);
|
||||
|
||||
|
||||
26
Runtime/Messaging/Messages/SessionOwnerMessage.cs
Normal file
26
Runtime/Messaging/Messages/SessionOwnerMessage.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
internal struct SessionOwnerMessage : INetworkMessage
|
||||
{
|
||||
public int Version => 0;
|
||||
|
||||
public ulong SessionOwner;
|
||||
|
||||
public void Serialize(FastBufferWriter writer, int targetVersion)
|
||||
{
|
||||
BytePacker.WriteValuePacked(writer, SessionOwner);
|
||||
}
|
||||
|
||||
public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion)
|
||||
{
|
||||
ByteUnpacker.ReadValuePacked(reader, out SessionOwner);
|
||||
return true;
|
||||
}
|
||||
|
||||
public unsafe void Handle(ref NetworkContext context)
|
||||
{
|
||||
var networkManager = (NetworkManager)context.SystemOwner;
|
||||
networkManager.SetSessionOwner(SessionOwner);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Runtime/Messaging/Messages/SessionOwnerMessage.cs.meta
Normal file
11
Runtime/Messaging/Messages/SessionOwnerMessage.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8e02985965397ab44809c99406914311
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -167,6 +167,28 @@ namespace Unity.Netcode
|
||||
{
|
||||
RegisterMessageType(type);
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
if (EnableMessageOrderConsoleLog)
|
||||
{
|
||||
// DANGO-TODO: Remove this when we have some form of message type indices stability in place
|
||||
// For now, just log the messages and their assigned types for reference purposes.
|
||||
var networkManager = m_Owner as NetworkManager;
|
||||
if (networkManager != null)
|
||||
{
|
||||
if (networkManager.DistributedAuthorityMode)
|
||||
{
|
||||
var messageListing = new StringBuilder();
|
||||
messageListing.AppendLine("NGO Message Index to Type Listing:");
|
||||
foreach (var message in m_MessageTypes)
|
||||
{
|
||||
messageListing.AppendLine($"[{message.Value}][{message.Key.Name}]");
|
||||
}
|
||||
Debug.Log(messageListing);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
@@ -175,6 +197,8 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
internal static bool EnableMessageOrderConsoleLog = false;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (m_Disposed)
|
||||
@@ -823,7 +847,11 @@ namespace Unity.Netcode
|
||||
internal unsafe int SendMessage<T>(ref T message, NetworkDelivery delivery, in NativeList<ulong> clientIds)
|
||||
where T : INetworkMessage
|
||||
{
|
||||
#if UTP_TRANSPORT_2_0_ABOVE
|
||||
return SendMessage(ref message, delivery, new PointerListWrapper<ulong>(clientIds.GetUnsafePtr(), clientIds.Length));
|
||||
#else
|
||||
return SendMessage(ref message, delivery, new PointerListWrapper<ulong>((ulong*)clientIds.GetUnsafePtr(), clientIds.Length));
|
||||
#endif
|
||||
}
|
||||
|
||||
internal unsafe void ProcessSendQueues()
|
||||
|
||||
75
Runtime/Messaging/RpcTargets/AuthorityRpcTarget.cs
Normal file
75
Runtime/Messaging/RpcTargets/AuthorityRpcTarget.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
internal class AuthorityRpcTarget : ServerRpcTarget
|
||||
{
|
||||
private ProxyRpcTarget m_AuthorityTarget;
|
||||
private DirectSendRpcTarget m_DirectSendTarget;
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
if (m_AuthorityTarget != null)
|
||||
{
|
||||
m_AuthorityTarget.Dispose();
|
||||
m_AuthorityTarget = null;
|
||||
}
|
||||
|
||||
if (m_DirectSendTarget != null)
|
||||
{
|
||||
m_DirectSendTarget.Dispose();
|
||||
m_DirectSendTarget = null;
|
||||
}
|
||||
|
||||
base.Dispose();
|
||||
}
|
||||
|
||||
internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, NetworkDelivery delivery, RpcParams rpcParams)
|
||||
{
|
||||
if (behaviour.NetworkManager.DistributedAuthorityMode)
|
||||
{
|
||||
// If invoked locally, then send locally
|
||||
if (behaviour.HasAuthority)
|
||||
{
|
||||
if (m_UnderlyingTarget == null)
|
||||
{
|
||||
m_UnderlyingTarget = new LocalSendRpcTarget(m_NetworkManager);
|
||||
}
|
||||
m_UnderlyingTarget.Send(behaviour, ref message, delivery, rpcParams);
|
||||
}
|
||||
else if (behaviour.NetworkManager.DAHost)
|
||||
{
|
||||
if (m_DirectSendTarget == null)
|
||||
{
|
||||
m_DirectSendTarget = new DirectSendRpcTarget(behaviour.OwnerClientId, m_NetworkManager);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_DirectSendTarget.ClientId = behaviour.OwnerClientId;
|
||||
}
|
||||
m_DirectSendTarget.Send(behaviour, ref message, delivery, rpcParams);
|
||||
}
|
||||
else // Otherwise (for now), we always proxy the RPC messages to the owner
|
||||
{
|
||||
if (m_AuthorityTarget == null)
|
||||
{
|
||||
m_AuthorityTarget = new ProxyRpcTarget(behaviour.OwnerClientId, m_NetworkManager);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Since the owner can change, for now we will just clear and set the owner each time
|
||||
m_AuthorityTarget.SetClientId(behaviour.OwnerClientId);
|
||||
}
|
||||
m_AuthorityTarget.Send(behaviour, ref message, delivery, rpcParams);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// If we are not in distributed authority mode, then we invoke the normal ServerRpc code.
|
||||
base.Send(behaviour, ref message, delivery, rpcParams);
|
||||
}
|
||||
}
|
||||
|
||||
internal AuthorityRpcTarget(NetworkManager manager) : base(manager)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Runtime/Messaging/RpcTargets/AuthorityRpcTarget.cs.meta
Normal file
11
Runtime/Messaging/RpcTargets/AuthorityRpcTarget.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6b8f28b7a617fd34faee91e5f88e89ac
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -36,7 +36,7 @@ namespace Unity.Netcode
|
||||
MessageType = m_NetworkManager.MessageManager.GetMessageType(typeof(RpcMessage))
|
||||
};
|
||||
|
||||
behaviour.NetworkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnNextFrame, 0, reader, ref context);
|
||||
networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnNextFrame, 0, reader, ref context);
|
||||
length = reader.Length;
|
||||
}
|
||||
else
|
||||
@@ -49,8 +49,8 @@ namespace Unity.Netcode
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
if (NetworkBehaviour.__rpc_name_table[behaviour.GetType()].TryGetValue(message.Metadata.NetworkRpcMethodId, out var rpcMethodName))
|
||||
{
|
||||
behaviour.NetworkManager.NetworkMetrics.TrackRpcSent(
|
||||
behaviour.NetworkManager.LocalClientId,
|
||||
networkManager.NetworkMetrics.TrackRpcSent(
|
||||
networkManager.LocalClientId,
|
||||
behaviour.NetworkObject,
|
||||
rpcMethodName,
|
||||
behaviour.__getTypeName(),
|
||||
|
||||
65
Runtime/Messaging/RpcTargets/NotAuthorityRpcTarget.cs
Normal file
65
Runtime/Messaging/RpcTargets/NotAuthorityRpcTarget.cs
Normal file
@@ -0,0 +1,65 @@
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
internal class NotAuthorityRpcTarget : NotServerRpcTarget
|
||||
{
|
||||
internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, NetworkDelivery delivery, RpcParams rpcParams)
|
||||
{
|
||||
var networkObject = behaviour.NetworkObject;
|
||||
if (m_NetworkManager.DistributedAuthorityMode)
|
||||
{
|
||||
if (m_GroupSendTarget == null)
|
||||
{
|
||||
// When mocking the CMB service, we are running a server so create a non-proxy target group
|
||||
if (m_NetworkManager.DAHost)
|
||||
{
|
||||
m_GroupSendTarget = new RpcTargetGroup(m_NetworkManager);
|
||||
}
|
||||
else // Otherwise (for now), we always proxy the RPC messages
|
||||
{
|
||||
m_GroupSendTarget = new ProxyRpcTargetGroup(m_NetworkManager);
|
||||
}
|
||||
}
|
||||
m_GroupSendTarget.Clear();
|
||||
|
||||
if (behaviour.HasAuthority)
|
||||
{
|
||||
foreach (var clientId in networkObject.Observers)
|
||||
{
|
||||
if (clientId == behaviour.OwnerClientId)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
m_GroupSendTarget.Add(clientId);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var clientId in m_NetworkManager.ConnectedClientsIds)
|
||||
{
|
||||
if (clientId == behaviour.OwnerClientId || !networkObject.Observers.Contains(clientId))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (clientId == m_NetworkManager.LocalClientId)
|
||||
{
|
||||
m_LocalSendRpcTarget.Send(behaviour, ref message, delivery, rpcParams);
|
||||
continue;
|
||||
}
|
||||
m_GroupSendTarget.Add(clientId);
|
||||
}
|
||||
}
|
||||
|
||||
m_GroupSendTarget.Target.Send(behaviour, ref message, delivery, rpcParams);
|
||||
}
|
||||
else
|
||||
{
|
||||
base.Send(behaviour, ref message, delivery, rpcParams);
|
||||
}
|
||||
}
|
||||
|
||||
internal NotAuthorityRpcTarget(NetworkManager manager) : base(manager)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Runtime/Messaging/RpcTargets/NotAuthorityRpcTarget.cs.meta
Normal file
11
Runtime/Messaging/RpcTargets/NotAuthorityRpcTarget.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4968ce7e70c277d4a892c1ed209ce4d6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -49,11 +49,18 @@ namespace Unity.Netcode
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// In distributed authority mode, we send to target id 0 (which would be a DAHost) via the group
|
||||
if (clientId == NetworkManager.ServerClientId && !m_NetworkManager.DistributedAuthorityMode)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
m_GroupSendTarget.Add(clientId);
|
||||
}
|
||||
}
|
||||
m_GroupSendTarget.Target.Send(behaviour, ref message, delivery, rpcParams);
|
||||
if (!behaviour.IsServer)
|
||||
|
||||
// In distributed authority mode, we don't use ServerRpc
|
||||
if (!behaviour.IsServer && !m_NetworkManager.DistributedAuthorityMode)
|
||||
{
|
||||
m_ServerRpcTarget.Send(behaviour, ref message, delivery, rpcParams);
|
||||
}
|
||||
|
||||
@@ -40,6 +40,10 @@ namespace Unity.Netcode
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (clientId == NetworkManager.ServerClientId)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (clientId == behaviour.NetworkManager.LocalClientId)
|
||||
{
|
||||
m_LocalSendRpcTarget.Send(behaviour, ref message, delivery, rpcParams);
|
||||
@@ -57,6 +61,10 @@ namespace Unity.Netcode
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (clientId == NetworkManager.ServerClientId)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (clientId == behaviour.NetworkManager.LocalClientId)
|
||||
{
|
||||
m_LocalSendRpcTarget.Send(behaviour, ref message, delivery, rpcParams);
|
||||
|
||||
@@ -2,8 +2,8 @@ namespace Unity.Netcode
|
||||
{
|
||||
internal class NotServerRpcTarget : BaseRpcTarget
|
||||
{
|
||||
private IGroupRpcTarget m_GroupSendTarget;
|
||||
private LocalSendRpcTarget m_LocalSendRpcTarget;
|
||||
protected IGroupRpcTarget m_GroupSendTarget;
|
||||
protected LocalSendRpcTarget m_LocalSendRpcTarget;
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
|
||||
@@ -62,6 +62,18 @@ namespace Unity.Netcode
|
||||
/// </summary>
|
||||
ClientsAndHost,
|
||||
/// <summary>
|
||||
/// Send this RPC to the authority.
|
||||
/// In distributed authority mode, this will be the owner of the NetworkObject.
|
||||
/// In normal client-server mode, this is basically the exact same thing as a server rpc.
|
||||
/// </summary>
|
||||
Authority,
|
||||
/// <summary>
|
||||
/// Send this RPC to all non-authority instances.
|
||||
/// In distributed authority mode, this will be the non-owners of the NetworkObject.
|
||||
/// In normal client-server mode, this is basically the exact same thing as a client rpc.
|
||||
/// </summary>
|
||||
NotAuthority,
|
||||
/// <summary>
|
||||
/// This RPC cannot be sent without passing in a target in RpcSendParams.
|
||||
/// </summary>
|
||||
SpecifiedInParams
|
||||
@@ -100,7 +112,8 @@ namespace Unity.Netcode
|
||||
NotMe = new NotMeRpcTarget(manager);
|
||||
Me = new LocalSendRpcTarget(manager);
|
||||
ClientsAndHost = new ClientsAndHostRpcTarget(manager);
|
||||
|
||||
Authority = new AuthorityRpcTarget(manager);
|
||||
NotAuthority = new NotAuthorityRpcTarget(manager);
|
||||
m_CachedProxyRpcTargetGroup = new ProxyRpcTargetGroup(manager);
|
||||
m_CachedTargetGroup = new RpcTargetGroup(manager);
|
||||
m_CachedDirectSendTarget = new DirectSendRpcTarget(manager);
|
||||
@@ -122,7 +135,8 @@ namespace Unity.Netcode
|
||||
NotMe.Dispose();
|
||||
Me.Dispose();
|
||||
ClientsAndHost.Dispose();
|
||||
|
||||
Authority.Dispose();
|
||||
NotAuthority.Dispose();
|
||||
m_CachedProxyRpcTargetGroup.Unlock();
|
||||
m_CachedTargetGroup.Unlock();
|
||||
m_CachedDirectSendTarget.Unlock();
|
||||
@@ -134,7 +148,6 @@ namespace Unity.Netcode
|
||||
m_CachedProxyRpcTarget.Dispose();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Send to the NetworkObject's current owner.
|
||||
/// Will execute locally if the local process is the owner.
|
||||
@@ -196,6 +209,20 @@ namespace Unity.Netcode
|
||||
/// </summary>
|
||||
public BaseRpcTarget ClientsAndHost;
|
||||
|
||||
/// <summary>
|
||||
/// Send this RPC to the authority.
|
||||
/// In distributed authority mode, this will be the owner of the NetworkObject.
|
||||
/// In normal client-server mode, this is basically the exact same thing as a server rpc.
|
||||
/// </summary>
|
||||
public BaseRpcTarget Authority;
|
||||
|
||||
/// <summary>
|
||||
/// Send this RPC to all non-authority instances.
|
||||
/// In distributed authority mode, this will be the non-owners of the NetworkObject.
|
||||
/// In normal client-server mode, this is basically the exact same thing as a client rpc.
|
||||
/// </summary>
|
||||
public BaseRpcTarget NotAuthority;
|
||||
|
||||
/// <summary>
|
||||
/// Send to a specific single client ID.
|
||||
/// </summary>
|
||||
|
||||
@@ -2,7 +2,7 @@ namespace Unity.Netcode
|
||||
{
|
||||
internal class ServerRpcTarget : BaseRpcTarget
|
||||
{
|
||||
private BaseRpcTarget m_UnderlyingTarget;
|
||||
protected BaseRpcTarget m_UnderlyingTarget;
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
@@ -15,6 +15,12 @@ namespace Unity.Netcode
|
||||
|
||||
internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, NetworkDelivery delivery, RpcParams rpcParams)
|
||||
{
|
||||
if (behaviour.NetworkManager.DistributedAuthorityMode && behaviour.NetworkManager.CMBServiceConnection)
|
||||
{
|
||||
UnityEngine.Debug.LogWarning("[Invalid Target] There is no server to send to when in Distributed Authority mode!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_UnderlyingTarget == null)
|
||||
{
|
||||
if (behaviour.NetworkManager.IsServer)
|
||||
|
||||
Reference in New Issue
Block a user