com.unity.netcode.gameobjects@1.8.0

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).

## [1.8.0] - 2023-12-12

### Added

- Added a new RPC attribute, which is simply `Rpc`. (#2762)
  - This is a generic attribute that can perform the functions of both Server and Client RPCs, as well as enabling client-to-client RPCs. Includes several default targets: `Server`, `NotServer`, `Owner`, `NotOwner`, `Me`, `NotMe`, `ClientsAndHost`, and `Everyone`. Runtime overrides are available for any of these targets, as well as for sending to a specific ID or groups of IDs.
  - This attribute also includes the ability to defer RPCs that are sent to the local process to the start of the next frame instead of executing them immediately, treating them as if they had gone across the network. The default behavior is to execute immediately.
  - This attribute effectively replaces `ServerRpc` and `ClientRpc`. `ServerRpc` and `ClientRpc` remain in their existing forms for backward compatibility, but `Rpc` will be the recommended and most supported option.
- Added `NetworkManager.OnConnectionEvent` as a unified connection event callback to notify clients and servers of all client connections and disconnections within the session (#2762)
- Added `NetworkManager.ServerIsHost` and `NetworkBehaviour.ServerIsHost` to allow a client to tell if it is connected to a host or to a dedicated server (#2762)
- Added `SceneEventProgress.SceneManagementNotEnabled` return status to be returned when a `NetworkSceneManager` method is invoked and scene management is not enabled. (#2735)
- Added `SceneEventProgress.ServerOnlyAction` return status to be returned when a `NetworkSceneManager` method is invoked by a client. (#2735)
- Added `NetworkObject.InstantiateAndSpawn` and `NetworkSpawnManager.InstantiateAndSpawn` methods to simplify prefab spawning by assuring that the prefab is valid and applies any override prior to instantiating the `GameObject` and spawning the `NetworkObject` instance. (#2710)

### Fixed

- Fixed issue where a client disconnected by a server-host would not receive a local notification. (#2789)
- Fixed issue where a server-host could shutdown during a relay connection but periodically the transport disconnect message sent to any connected clients could be dropped. (#2789)
- Fixed issue where a host could disconnect its local client but remain running as a server. (#2789)
- Fixed issue where `OnClientDisconnectedCallback` was not being invoked under certain conditions. (#2789)
- Fixed issue where `OnClientDisconnectedCallback` was always returning 0 as the client identifier. (#2789)
- Fixed issue where if a host or server shutdown while a client owned NetworkObjects (other than the player) it would throw an exception. (#2789)
- Fixed issue where setting values on a `NetworkVariable` or `NetworkList` within `OnNetworkDespawn` during a shutdown sequence would throw an exception. (#2789)
- Fixed issue where a teleport state could potentially be overridden by a previous unreliable delta state. (#2777)
- Fixed issue where `NetworkTransform` was using the `NetworkManager.ServerTime.Tick` as opposed to `NetworkManager.NetworkTickSystem.ServerTime.Tick` during the authoritative side's tick update where it performed a delta state check. (#2777)
- Fixed issue where a parented in-scene placed NetworkObject would be destroyed upon a client or server exiting a network session but not unloading the original scene in which the NetworkObject was placed. (#2737)
- Fixed issue where during client synchronization and scene loading, when client synchronization or the scene loading mode are set to `LoadSceneMode.Single`, a `CreateObjectMessage` could be received, processed, and the resultant spawned `NetworkObject` could be instantiated in the client's currently active scene that could, towards the end of the client synchronization or loading process, be unloaded and cause the newly created `NetworkObject` to be destroyed (and throw and exception). (#2735)
- Fixed issue where a `NetworkTransform` instance with interpolation enabled would result in wide visual motion gaps (stuttering) under above normal latency conditions and a 1-5% or higher packet are drop rate. (#2713)
- Fixed issue where  you could not have multiple source network prefab overrides targeting the same network prefab as their override. (#2710)

### Changed
- Changed the server or host shutdown so it will now perform a "soft shutdown" when `NetworkManager.Shutdown` is invoked. This will send a disconnect notification to all connected clients and the server-host will wait for all connected clients to disconnect or timeout after a 5 second period before completing the shutdown process. (#2789)
- Changed `OnClientDisconnectedCallback` will now return the assigned client identifier on the local client side if the client was approved and assigned one prior to being disconnected. (#2789)
- Changed `NetworkTransform.SetState` (and related methods) now are cumulative during a fractional tick period and sent on the next pending tick. (#2777)
- `NetworkManager.ConnectedClientsIds` is now accessible on the client side and will contain the list of all clients in the session, including the host client if the server is operating in host mode (#2762)
- Changed `NetworkSceneManager` to return a `SceneEventProgress` status and not throw exceptions for methods invoked when scene management is disabled and when a client attempts to access a `NetworkSceneManager` method by a client. (#2735)
- Changed `NetworkTransform` authoritative instance tick registration so a single `NetworkTransform` specific tick event update will update all authoritative instances to improve perofmance. (#2713)
- Changed `NetworkPrefabs.OverrideToNetworkPrefab` dictionary is no longer used/populated due to it ending up being related to a regression bug and not allowing more than one override to be assigned to a network prefab asset. (#2710)
- Changed in-scene placed `NetworkObject`s now store their source network prefab asset's `GlobalObjectIdHash` internally that is used, when scene management is disabled, by clients to spawn the correct prefab even if the `NetworkPrefab` entry has an override. This does not impact dynamically spawning the same prefab which will yield the override on both host and client. (#2710)
- Changed in-scene placed `NetworkObject`s no longer require a `NetworkPrefab` entry with `GlobalObjectIdHash` override in order for clients to properly synchronize. (#2710)
- Changed in-scene placed `NetworkObject`s now set their `IsSceneObject` value when generating their `GlobalObjectIdHash` value. (#2710)
- Changed the default `NetworkConfig.SpawnTimeout` value from 1.0s to 10.0s. (#2710)
This commit is contained in:
Unity Technologies
2023-12-12 00:00:00 +00:00
parent 514166e159
commit 07f206ff9e
99 changed files with 8667 additions and 1710 deletions

View File

@@ -113,6 +113,7 @@ namespace Unity.Netcode
// processed before the object is fully spawned. This must be the last thing done in the spawn process.
if (triggers.TryGetValue(key, out var triggerInfo))
{
triggers.Remove(key);
foreach (var deferredMessage in triggerInfo.TriggerData)
{
// Reader will be disposed within HandleMessage
@@ -120,7 +121,6 @@ namespace Unity.Netcode
}
triggerInfo.TriggerData.Dispose();
triggers.Remove(key);
}
}
}

View File

@@ -6,6 +6,7 @@ namespace Unity.Netcode
{
OnSpawn,
OnAddPrefab,
OnNextFrame,
}
/// <summary>

View File

@@ -0,0 +1,35 @@
namespace Unity.Netcode
{
internal struct ClientConnectedMessage : INetworkMessage, INetworkSerializeByMemcpy
{
public int Version => 0;
public ulong ClientId;
public void Serialize(FastBufferWriter writer, int targetVersion)
{
BytePacker.WriteValueBitPacked(writer, ClientId);
}
public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion)
{
var networkManager = (NetworkManager)context.SystemOwner;
if (!networkManager.IsClient)
{
return false;
}
ByteUnpacker.ReadValueBitPacked(reader, out ClientId);
return true;
}
public void Handle(ref NetworkContext context)
{
var networkManager = (NetworkManager)context.SystemOwner;
networkManager.ConnectionManager.ConnectedClientIds.Add(ClientId);
if (networkManager.IsConnectedClient)
{
networkManager.ConnectionManager.InvokeOnPeerConnectedCallback(ClientId);
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 158454105806474cba54a4ea5a0bfb12
timeCreated: 1697836112

View File

@@ -0,0 +1,35 @@
namespace Unity.Netcode
{
internal struct ClientDisconnectedMessage : INetworkMessage, INetworkSerializeByMemcpy
{
public int Version => 0;
public ulong ClientId;
public void Serialize(FastBufferWriter writer, int targetVersion)
{
BytePacker.WriteValueBitPacked(writer, ClientId);
}
public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion)
{
var networkManager = (NetworkManager)context.SystemOwner;
if (!networkManager.IsClient)
{
return false;
}
ByteUnpacker.ReadValueBitPacked(reader, out ClientId);
return true;
}
public void Handle(ref NetworkContext context)
{
var networkManager = (NetworkManager)context.SystemOwner;
networkManager.ConnectionManager.ConnectedClientIds.Remove(ClientId);
if (networkManager.IsConnectedClient)
{
networkManager.ConnectionManager.InvokeOnPeerDisconnectedCallback(ClientId);
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 8f91296c8e5f40b1a2a03d74a31526b6
timeCreated: 1697836161

View File

@@ -5,7 +5,8 @@ namespace Unity.Netcode
{
internal struct ConnectionApprovedMessage : INetworkMessage
{
public int Version => 0;
private const int k_VersionAddClientIds = 1;
public int Version => k_VersionAddClientIds;
public ulong OwnerClientId;
public int NetworkTick;
@@ -17,6 +18,8 @@ namespace Unity.Netcode
public NativeArray<MessageVersionData> MessageVersions;
public NativeArray<ulong> ConnectedClientIds;
public void Serialize(FastBufferWriter writer, int targetVersion)
{
// ============================================================
@@ -36,6 +39,11 @@ namespace Unity.Netcode
BytePacker.WriteValueBitPacked(writer, OwnerClientId);
BytePacker.WriteValueBitPacked(writer, NetworkTick);
if (targetVersion >= k_VersionAddClientIds)
{
writer.WriteValueSafe(ConnectedClientIds);
}
uint sceneObjectCount = 0;
// When SpawnedObjectsList is not null then scene management is disabled. Provide a list of
@@ -106,6 +114,16 @@ namespace Unity.Netcode
ByteUnpacker.ReadValueBitPacked(reader, out OwnerClientId);
ByteUnpacker.ReadValueBitPacked(reader, out NetworkTick);
if (receivedMessageVersion >= k_VersionAddClientIds)
{
reader.ReadValueSafe(out ConnectedClientIds, Allocator.TempJob);
}
else
{
ConnectedClientIds = new NativeArray<ulong>(0, Allocator.TempJob);
}
m_ReceivedSceneObjectData = reader;
return true;
}
@@ -114,6 +132,7 @@ namespace Unity.Netcode
{
var networkManager = (NetworkManager)context.SystemOwner;
networkManager.LocalClientId = OwnerClientId;
networkManager.MessageManager.SetLocalClientId(networkManager.LocalClientId);
networkManager.NetworkMetrics.SetConnectionId(networkManager.LocalClientId);
var time = new NetworkTime(networkManager.NetworkTickSystem.TickRate, NetworkTick);
@@ -126,6 +145,12 @@ namespace Unity.Netcode
// Stop the client-side approval timeout coroutine since we are approved.
networkManager.ConnectionManager.StopClientApprovalCoroutine();
networkManager.ConnectionManager.ConnectedClientIds.Clear();
foreach (var clientId in ConnectedClientIds)
{
networkManager.ConnectionManager.ConnectedClientIds.Add(clientId);
}
// Only if scene management is disabled do we handle NetworkObject synchronization at this point
if (!networkManager.NetworkConfig.EnableSceneManagement)
{
@@ -146,6 +171,8 @@ namespace Unity.Netcode
// When scene management is disabled we notify after everything is synchronized
networkManager.ConnectionManager.InvokeOnClientConnectedCallback(context.SenderId);
}
ConnectedClientIds.Dispose();
}
}
}

View File

@@ -1,3 +1,4 @@
using System.Runtime.CompilerServices;
namespace Unity.Netcode
{
internal struct CreateObjectMessage : INetworkMessage
@@ -34,9 +35,29 @@ namespace Unity.Netcode
public void Handle(ref NetworkContext context)
{
var networkManager = (NetworkManager)context.SystemOwner;
var networkObject = NetworkObject.AddSceneObject(ObjectInfo, m_ReceivedNetworkVariableData, networkManager);
// 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);
}
else
{
CreateObject(ref networkManager, context.SenderId, context.MessageSize, ObjectInfo, m_ReceivedNetworkVariableData);
}
}
networkManager.NetworkMetrics.TrackObjectSpawnReceived(context.SenderId, networkObject, context.MessageSize);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void CreateObject(ref NetworkManager networkManager, ulong senderId, uint messageSize, NetworkObject.SceneObject sceneObject, FastBufferReader networkVariableData)
{
try
{
var networkObject = NetworkObject.AddSceneObject(sceneObject, networkVariableData, networkManager);
networkManager.NetworkMetrics.TrackObjectSpawnReceived(senderId, networkObject, messageSize);
}
catch (System.Exception ex)
{
UnityEngine.Debug.LogException(ex);
}
}
}
}

View File

@@ -25,9 +25,9 @@ namespace Unity.Netcode
public void Handle(ref NetworkContext context)
{
var networkManager = (NetworkManager)context.SystemOwner;
if (!networkManager.ShutdownInProgress)
if (!networkManager.ShutdownInProgress && networkManager.CustomMessagingManager != null)
{
((NetworkManager)context.SystemOwner).CustomMessagingManager.InvokeNamedMessage(Hash, context.SenderId, m_ReceiveData, context.SerializedHeaderSize);
networkManager.CustomMessagingManager.InvokeNamedMessage(Hash, context.SenderId, m_ReceiveData, context.SerializedHeaderSize);
}
}
}

View File

@@ -0,0 +1,70 @@
using System;
using Unity.Collections;
namespace Unity.Netcode
{
internal struct ProxyMessage : INetworkMessage
{
public NativeArray<ulong> TargetClientIds;
public NetworkDelivery Delivery;
public RpcMessage WrappedMessage;
// Version of ProxyMessage and RpcMessage must always match.
// If ProxyMessage needs to change, increment RpcMessage's version
public int Version => new RpcMessage().Version;
public void Serialize(FastBufferWriter writer, int targetVersion)
{
writer.WriteValueSafe(TargetClientIds);
BytePacker.WriteValuePacked(writer, Delivery);
WrappedMessage.Serialize(writer, targetVersion);
}
public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion)
{
reader.ReadValueSafe(out TargetClientIds, Allocator.Temp);
ByteUnpacker.ReadValuePacked(reader, out Delivery);
WrappedMessage = new RpcMessage();
WrappedMessage.Deserialize(reader, ref context, receivedMessageVersion);
return true;
}
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.");
}
var observers = networkObject.Observers;
var nonServerIds = new NativeList<ulong>(Allocator.Temp);
for (var i = 0; i < TargetClientIds.Length; ++i)
{
if (!observers.Contains(TargetClientIds[i]))
{
continue;
}
if (TargetClientIds[i] == NetworkManager.ServerClientId)
{
WrappedMessage.Handle(ref context);
}
else
{
nonServerIds.Add(TargetClientIds[i]);
}
}
WrappedMessage.WriteBuffer = new FastBufferWriter(WrappedMessage.ReadBuffer.Length, Allocator.Temp);
using (WrappedMessage.WriteBuffer)
{
WrappedMessage.WriteBuffer.WriteBytesSafe(WrappedMessage.ReadBuffer.GetUnsafePtr(), WrappedMessage.ReadBuffer.Length);
networkManager.MessageManager.SendMessage(ref WrappedMessage, Delivery, nonServerIds);
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e9ee0457d5b740b38dfe6542658fb522
timeCreated: 1697825043

View File

@@ -159,4 +159,42 @@ namespace Unity.Netcode
RpcMessageHelpers.Handle(ref context, ref Metadata, ref ReadBuffer, ref rpcParams);
}
}
internal struct RpcMessage : INetworkMessage
{
public int Version => 0;
public RpcMetadata Metadata;
public ulong SenderClientId;
public FastBufferWriter WriteBuffer;
public FastBufferReader ReadBuffer;
public unsafe void Serialize(FastBufferWriter writer, int targetVersion)
{
BytePacker.WriteValuePacked(writer, SenderClientId);
RpcMessageHelpers.Serialize(ref writer, ref Metadata, ref WriteBuffer);
}
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);
}
public void Handle(ref NetworkContext context)
{
var rpcParams = new __RpcParams
{
Ext = new RpcParams
{
Receive = new RpcReceiveParams
{
SenderClientId = SenderClientId
}
}
};
RpcMessageHelpers.Handle(ref context, ref Metadata, ref ReadBuffer, ref rpcParams);
}
}
}

View File

@@ -1,4 +1,5 @@
using System;
using Unity.Netcode.Transports.UTP;
namespace Unity.Netcode
{
@@ -56,7 +57,8 @@ namespace Unity.Netcode
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{
NetworkLog.LogError($"A {nameof(ConnectionApprovedMessage)} was received from a client on the server side. This should not happen. Please report this to the Netcode for GameObjects team at https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/issues and include the following data: Message Size: {messageContent.Length}. Message Content: {NetworkMessageManager.ByteArrayToString(messageContent.ToArray(), 0, messageContent.Length)}");
var transportErrorMsg = GetTransportErrorMessage(messageContent, m_NetworkManager);
NetworkLog.LogError($"A {nameof(ConnectionApprovedMessage)} was received from a client on the server side. {transportErrorMsg}");
}
return false;
@@ -66,7 +68,7 @@ namespace Unity.Netcode
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{
NetworkLog.LogWarning($"Message received from {nameof(senderId)}={senderId} before it has been accepted");
NetworkLog.LogWarning($"Message received from {nameof(senderId)}={senderId} before it has been accepted.");
}
return false;
@@ -76,7 +78,8 @@ namespace Unity.Netcode
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{
NetworkLog.LogError($"A {nameof(ConnectionRequestMessage)} was received from a client when the connection has already been established. This should not happen. Please report this to the Netcode for GameObjects team at https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/issues and include the following data: Message Size: {messageContent.Length}. Message Content: {NetworkMessageManager.ByteArrayToString(messageContent.ToArray(), 0, messageContent.Length)}");
var transportErrorMsg = GetTransportErrorMessage(messageContent, m_NetworkManager);
NetworkLog.LogError($"A {nameof(ConnectionRequestMessage)} was received from a client when the connection has already been established. {transportErrorMsg}");
}
return false;
@@ -88,7 +91,8 @@ namespace Unity.Netcode
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{
NetworkLog.LogError($"A {nameof(ConnectionRequestMessage)} was received from the server on the client side. This should not happen. Please report this to the Netcode for GameObjects team at https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/issues and include the following data: Message Size: {messageContent.Length}. Message Content: {NetworkMessageManager.ByteArrayToString(messageContent.ToArray(), 0, messageContent.Length)}");
var transportErrorMsg = GetTransportErrorMessage(messageContent, m_NetworkManager);
NetworkLog.LogError($"A {nameof(ConnectionRequestMessage)} was received from the server on the client side. {transportErrorMsg}");
}
return false;
@@ -98,7 +102,8 @@ namespace Unity.Netcode
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{
NetworkLog.LogError($"A {nameof(ConnectionApprovedMessage)} was received from the server when the connection has already been established. This should not happen. Please report this to the Netcode for GameObjects team at https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/issues and include the following data: Message Size: {messageContent.Length}. Message Content: {NetworkMessageManager.ByteArrayToString(messageContent.ToArray(), 0, messageContent.Length)}");
var transportErrorMsg = GetTransportErrorMessage(messageContent, m_NetworkManager);
NetworkLog.LogError($"A {nameof(ConnectionApprovedMessage)} was received from the server when the connection has already been established. {transportErrorMsg}");
}
return false;
@@ -108,6 +113,28 @@ namespace Unity.Netcode
return !m_NetworkManager.MessageManager.StopProcessing;
}
private static string GetTransportErrorMessage(FastBufferReader messageContent, NetworkManager networkManager)
{
if (networkManager.NetworkConfig.NetworkTransport is not UnityTransport)
{
return $"NetworkTransport: {networkManager.NetworkConfig.NetworkTransport.GetType()}. Please report this to the maintainer of transport layer.";
}
var transportVersion = GetTransportVersion(networkManager);
return $"{transportVersion}. This should not happen. Please report this to the Netcode for GameObjects team at https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/issues and include the following data: Message Size: {messageContent.Length}. Message Content: {NetworkMessageManager.ByteArrayToString(messageContent.ToArray(), 0, messageContent.Length)}";
}
private static string GetTransportVersion(NetworkManager networkManager)
{
var transportVersion = "NetworkTransport: " + networkManager.NetworkConfig.NetworkTransport.GetType();
if (networkManager.NetworkConfig.NetworkTransport is UnityTransport unityTransport)
{
transportVersion += " UnityTransportProtocol: " + unityTransport.Protocol;
}
return transportVersion;
}
public void OnBeforeHandleMessage<T>(ref T message, ref NetworkContext context) where T : INetworkMessage
{
}

View File

@@ -85,6 +85,8 @@ namespace Unity.Netcode
private INetworkMessageSender m_Sender;
private bool m_Disposed;
private ulong m_LocalClientId;
internal Type[] MessageTypes => m_ReverseTypeMap;
internal MessageHandler[] MessageHandlers => m_MessageHandlers;
@@ -95,6 +97,16 @@ namespace Unity.Netcode
return m_MessageTypes[t];
}
internal object GetOwner()
{
return m_Owner;
}
internal void SetLocalClientId(ulong id)
{
m_LocalClientId = id;
}
public const int DefaultNonFragmentedMessageMaxSize = 1300 & ~7; // Round down to nearest word aligned size (1296)
public int NonFragmentedMessageMaxSize = DefaultNonFragmentedMessageMaxSize;
public int FragmentedMessageMaxSize = int.MaxValue;
@@ -551,7 +563,7 @@ namespace Unity.Netcode
// Special cases because these are the messages that carry the version info - thus the version info isn't
// populated yet when we get these. The first part of these messages always has to be the version data
// and can't change.
if (typeof(T) != typeof(ConnectionRequestMessage) && typeof(T) != typeof(ConnectionApprovedMessage) && typeof(T) != typeof(DisconnectReasonMessage))
if (typeof(T) != typeof(ConnectionRequestMessage) && typeof(T) != typeof(ConnectionApprovedMessage) && typeof(T) != typeof(DisconnectReasonMessage) && context.SenderId != manager.m_LocalClientId)
{
messageVersion = manager.GetMessageVersion(typeof(T), context.SenderId, true);
if (messageVersion < 0)
@@ -808,6 +820,12 @@ namespace Unity.Netcode
return SendMessage(ref message, delivery, new PointerListWrapper<ulong>((ulong*)clientIds.GetUnsafePtr(), clientIds.Length));
}
internal unsafe int SendMessage<T>(ref T message, NetworkDelivery delivery, in NativeList<ulong> clientIds)
where T : INetworkMessage
{
return SendMessage(ref message, delivery, new PointerListWrapper<ulong>((ulong*)clientIds.GetUnsafePtr(), clientIds.Length));
}
internal unsafe void ProcessSendQueues()
{
if (StopProcessing)

View File

@@ -21,12 +21,36 @@ namespace Unity.Netcode
/// <summary>
/// <para>Represents the common base class for Rpc attributes.</para>
/// </summary>
public abstract class RpcAttribute : Attribute
[AttributeUsage(AttributeTargets.Method)]
public class RpcAttribute : Attribute
{
// Must match the set of parameters below
public struct RpcAttributeParams
{
public RpcDelivery Delivery;
public bool RequireOwnership;
public bool DeferLocal;
public bool AllowTargetOverride;
}
// Must match the fields in RemoteAttributeParams
/// <summary>
/// Type of RPC delivery method
/// </summary>
public RpcDelivery Delivery = RpcDelivery.Reliable;
public bool RequireOwnership;
public bool DeferLocal;
public bool AllowTargetOverride;
public RpcAttribute(SendTo target)
{
}
// To get around an issue with the release validator, RuntimeAccessModifiersILPP will make this 'public'
private RpcAttribute()
{
}
}
/// <summary>
@@ -36,10 +60,12 @@ namespace Unity.Netcode
[AttributeUsage(AttributeTargets.Method)]
public class ServerRpcAttribute : RpcAttribute
{
/// <summary>
/// Whether or not the ServerRpc should only be run if executed by the owner of the object
/// </summary>
public bool RequireOwnership = true;
public new bool RequireOwnership;
public ServerRpcAttribute() : base(SendTo.Server)
{
}
}
/// <summary>
@@ -47,5 +73,11 @@ namespace Unity.Netcode
/// <para>A ClientRpc marked method will be fired by the server but executed on clients.</para>
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public class ClientRpcAttribute : RpcAttribute { }
public class ClientRpcAttribute : RpcAttribute
{
public ClientRpcAttribute() : base(SendTo.NotServer)
{
}
}
}

View File

@@ -3,6 +3,60 @@ using Unity.Collections;
namespace Unity.Netcode
{
public enum LocalDeferMode
{
Default,
Defer,
SendImmediate
}
/// <summary>
/// Generic RPC
/// </summary>
public struct RpcSendParams
{
public BaseRpcTarget Target;
public LocalDeferMode LocalDeferMode;
public static implicit operator RpcSendParams(BaseRpcTarget target) => new RpcSendParams { Target = target };
public static implicit operator RpcSendParams(LocalDeferMode deferMode) => new RpcSendParams { LocalDeferMode = deferMode };
}
/// <summary>
/// The receive parameters for server-side remote procedure calls
/// </summary>
public struct RpcReceiveParams
{
/// <summary>
/// Server-Side RPC
/// The client identifier of the sender
/// </summary>
public ulong SenderClientId;
}
/// <summary>
/// Server-Side RPC
/// Can be used with any sever-side remote procedure call
/// Note: typically this is use primarily for the <see cref="ServerRpcReceiveParams"/>
/// </summary>
public struct RpcParams
{
/// <summary>
/// The server RPC send parameters (currently a place holder)
/// </summary>
public RpcSendParams Send;
/// <summary>
/// The client RPC receive parameters provides you with the sender's identifier
/// </summary>
public RpcReceiveParams Receive;
public static implicit operator RpcParams(RpcSendParams send) => new RpcParams { Send = send };
public static implicit operator RpcParams(BaseRpcTarget target) => new RpcParams { Send = new RpcSendParams { Target = target } };
public static implicit operator RpcParams(LocalDeferMode deferMode) => new RpcParams { Send = new RpcSendParams { LocalDeferMode = deferMode } };
public static implicit operator RpcParams(RpcReceiveParams receive) => new RpcParams { Receive = receive };
}
/// <summary>
/// Server-Side RPC
/// Place holder. <see cref="ServerRpcParams"/>
@@ -99,6 +153,7 @@ namespace Unity.Netcode
internal struct __RpcParams
#pragma warning restore IDE1006 // restore naming rule violation check
{
public RpcParams Ext;
public ServerRpcParams Server;
public ClientRpcParams Client;
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b02186acd1144e20acbd0dcb69b14938
timeCreated: 1697824888

View File

@@ -0,0 +1,57 @@
using System;
namespace Unity.Netcode
{
public abstract class BaseRpcTarget : IDisposable
{
protected NetworkManager m_NetworkManager;
private bool m_Locked;
internal void Lock()
{
m_Locked = true;
}
internal void Unlock()
{
m_Locked = false;
}
internal BaseRpcTarget(NetworkManager manager)
{
m_NetworkManager = manager;
}
protected void CheckLockBeforeDispose()
{
if (m_Locked)
{
throw new Exception($"RPC targets obtained through {nameof(RpcTargetUse)}.{RpcTargetUse.Temp} may not be disposed.");
}
}
public abstract void Dispose();
internal abstract void Send(NetworkBehaviour behaviour, ref RpcMessage message, NetworkDelivery delivery, RpcParams rpcParams);
private protected void SendMessageToClient(NetworkBehaviour behaviour, ulong clientId, ref RpcMessage message, NetworkDelivery delivery)
{
#if DEVELOPMENT_BUILD || UNITY_EDITOR
var size =
#endif
behaviour.NetworkManager.MessageManager.SendMessage(ref message, delivery, clientId);
#if DEVELOPMENT_BUILD || UNITY_EDITOR
if (NetworkBehaviour.__rpc_name_table[behaviour.GetType()].TryGetValue(message.Metadata.NetworkRpcMethodId, out var rpcMethodName))
{
behaviour.NetworkManager.NetworkMetrics.TrackRpcSent(
clientId,
behaviour.NetworkObject,
rpcMethodName,
behaviour.__getTypeName(),
size);
}
#endif
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 07c2620262e24eb5a426b521c09b3091
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,37 @@
namespace Unity.Netcode
{
internal class ClientsAndHostRpcTarget : BaseRpcTarget
{
private BaseRpcTarget m_UnderlyingTarget;
public override void Dispose()
{
m_UnderlyingTarget = null;
}
internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, NetworkDelivery delivery, RpcParams rpcParams)
{
if (m_UnderlyingTarget == null)
{
// NotServer treats a host as being a server and will not send to it
// ClientsAndHost sends to everyone who runs any client logic
// So if the server is a host, this target includes it (as hosts run client logic)
// If the server is not a host, this target leaves it out, ergo the selection of NotServer.
if (behaviour.NetworkManager.ServerIsHost)
{
m_UnderlyingTarget = behaviour.RpcTarget.Everyone;
}
else
{
m_UnderlyingTarget = behaviour.RpcTarget.NotServer;
}
}
m_UnderlyingTarget.Send(behaviour, ref message, delivery, rpcParams);
}
internal ClientsAndHostRpcTarget(NetworkManager manager) : base(manager)
{
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c9f883d678ec4715b160dd9497d5f42d
timeCreated: 1699481382

View File

@@ -0,0 +1,34 @@
namespace Unity.Netcode
{
internal class DirectSendRpcTarget : BaseRpcTarget, IIndividualRpcTarget
{
public BaseRpcTarget Target => this;
internal ulong ClientId;
public override void Dispose()
{
CheckLockBeforeDispose();
}
internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, NetworkDelivery delivery, RpcParams rpcParams)
{
SendMessageToClient(behaviour, ClientId, ref message, delivery);
}
public void SetClientId(ulong clientId)
{
ClientId = clientId;
}
internal DirectSendRpcTarget(NetworkManager manager) : base(manager)
{
}
internal DirectSendRpcTarget(ulong clientId, NetworkManager manager) : base(manager)
{
ClientId = clientId;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 077544cfd0b94cfc8a2a55d3828b74bb
timeCreated: 1697824873

View File

@@ -0,0 +1,26 @@
namespace Unity.Netcode
{
internal class EveryoneRpcTarget : BaseRpcTarget
{
private NotServerRpcTarget m_NotServerRpcTarget;
private ServerRpcTarget m_ServerRpcTarget;
public override void Dispose()
{
m_NotServerRpcTarget.Dispose();
m_ServerRpcTarget.Dispose();
}
internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, NetworkDelivery delivery, RpcParams rpcParams)
{
m_NotServerRpcTarget.Send(behaviour, ref message, delivery, rpcParams);
m_ServerRpcTarget.Send(behaviour, ref message, delivery, rpcParams);
}
internal EveryoneRpcTarget(NetworkManager manager) : base(manager)
{
m_NotServerRpcTarget = new NotServerRpcTarget(manager);
m_ServerRpcTarget = new ServerRpcTarget(manager);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 675d4a5c79fc47078092ac15d255745d
timeCreated: 1697824941

View File

@@ -0,0 +1,9 @@
namespace Unity.Netcode
{
internal interface IGroupRpcTarget
{
void Add(ulong clientId);
void Clear();
BaseRpcTarget Target { get; }
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: beb19a6bb1334252a89b21c8490f7cbe
timeCreated: 1697825109

View File

@@ -0,0 +1,8 @@
namespace Unity.Netcode
{
internal interface IIndividualRpcTarget
{
void SetClientId(ulong clientId);
BaseRpcTarget Target { get; }
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c658d9641f564d9890bef4f558f1cea6
timeCreated: 1697825115

View File

@@ -0,0 +1,67 @@
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
namespace Unity.Netcode
{
internal class LocalSendRpcTarget : BaseRpcTarget
{
public override void Dispose()
{
}
internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, NetworkDelivery delivery, RpcParams rpcParams)
{
var networkManager = behaviour.NetworkManager;
var context = new NetworkContext
{
SenderId = m_NetworkManager.LocalClientId,
Timestamp = networkManager.RealTimeProvider.RealTimeSinceStartup,
SystemOwner = networkManager,
// header information isn't valid since it's not a real message.
// RpcMessage doesn't access this stuff so it's just left empty.
Header = new NetworkMessageHeader(),
SerializedHeaderSize = 0,
MessageSize = 0
};
int length;
if (rpcParams.Send.LocalDeferMode == LocalDeferMode.Defer)
{
using var serializedWriter = new FastBufferWriter(message.WriteBuffer.Length + UnsafeUtility.SizeOf<RpcMetadata>(), Allocator.Temp, int.MaxValue);
message.Serialize(serializedWriter, message.Version);
using var reader = new FastBufferReader(serializedWriter, Allocator.None);
context.Header = new NetworkMessageHeader
{
MessageSize = (uint)reader.Length,
MessageType = m_NetworkManager.MessageManager.GetMessageType(typeof(RpcMessage))
};
behaviour.NetworkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnNextFrame, 0, reader, ref context);
length = reader.Length;
}
else
{
using var tempBuffer = new FastBufferReader(message.WriteBuffer, Allocator.None);
message.ReadBuffer = tempBuffer;
message.Handle(ref context);
length = tempBuffer.Length;
}
#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,
behaviour.NetworkObject,
rpcMethodName,
behaviour.__getTypeName(),
length);
}
#endif
}
internal LocalSendRpcTarget(NetworkManager manager) : base(manager)
{
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c3b290cdc20d4d2293652ec79652962a
timeCreated: 1697824985

View File

@@ -0,0 +1,67 @@
namespace Unity.Netcode
{
internal class NotMeRpcTarget : BaseRpcTarget
{
private IGroupRpcTarget m_GroupSendTarget;
private ServerRpcTarget m_ServerRpcTarget;
public override void Dispose()
{
m_ServerRpcTarget.Dispose();
if (m_GroupSendTarget != null)
{
m_GroupSendTarget.Target.Dispose();
m_GroupSendTarget = null;
}
}
internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, NetworkDelivery delivery, RpcParams rpcParams)
{
if (m_GroupSendTarget == null)
{
if (behaviour.IsServer)
{
m_GroupSendTarget = new RpcTargetGroup(m_NetworkManager);
}
else
{
m_GroupSendTarget = new ProxyRpcTargetGroup(m_NetworkManager);
}
}
m_GroupSendTarget.Clear();
if (behaviour.IsServer)
{
foreach (var clientId in behaviour.NetworkObject.Observers)
{
if (clientId == behaviour.NetworkManager.LocalClientId)
{
continue;
}
m_GroupSendTarget.Add(clientId);
}
}
else
{
foreach (var clientId in m_NetworkManager.ConnectedClientsIds)
{
if (clientId == behaviour.NetworkManager.LocalClientId)
{
continue;
}
m_GroupSendTarget.Add(clientId);
}
}
m_GroupSendTarget.Target.Send(behaviour, ref message, delivery, rpcParams);
if (!behaviour.IsServer)
{
m_ServerRpcTarget.Send(behaviour, ref message, delivery, rpcParams);
}
}
internal NotMeRpcTarget(NetworkManager manager) : base(manager)
{
m_ServerRpcTarget = new ServerRpcTarget(manager);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 99cd5e8be7bd454bab700ee08b8dad7b
timeCreated: 1697824966

View File

@@ -0,0 +1,83 @@
namespace Unity.Netcode
{
internal class NotOwnerRpcTarget : BaseRpcTarget
{
private IGroupRpcTarget m_GroupSendTarget;
private ServerRpcTarget m_ServerRpcTarget;
private LocalSendRpcTarget m_LocalSendRpcTarget;
public override void Dispose()
{
m_ServerRpcTarget.Dispose();
m_LocalSendRpcTarget.Dispose();
if (m_GroupSendTarget != null)
{
m_GroupSendTarget.Target.Dispose();
m_GroupSendTarget = null;
}
}
internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, NetworkDelivery delivery, RpcParams rpcParams)
{
if (m_GroupSendTarget == null)
{
if (behaviour.IsServer)
{
m_GroupSendTarget = new RpcTargetGroup(m_NetworkManager);
}
else
{
m_GroupSendTarget = new ProxyRpcTargetGroup(m_NetworkManager);
}
}
m_GroupSendTarget.Clear();
if (behaviour.IsServer)
{
foreach (var clientId in behaviour.NetworkObject.Observers)
{
if (clientId == behaviour.OwnerClientId)
{
continue;
}
if (clientId == behaviour.NetworkManager.LocalClientId)
{
m_LocalSendRpcTarget.Send(behaviour, ref message, delivery, rpcParams);
continue;
}
m_GroupSendTarget.Add(clientId);
}
}
else
{
foreach (var clientId in m_NetworkManager.ConnectedClientsIds)
{
if (clientId == behaviour.OwnerClientId)
{
continue;
}
if (clientId == behaviour.NetworkManager.LocalClientId)
{
m_LocalSendRpcTarget.Send(behaviour, ref message, delivery, rpcParams);
continue;
}
m_GroupSendTarget.Add(clientId);
}
}
m_GroupSendTarget.Target.Send(behaviour, ref message, delivery, rpcParams);
if (behaviour.OwnerClientId != NetworkManager.ServerClientId)
{
m_ServerRpcTarget.Send(behaviour, ref message, delivery, rpcParams);
}
}
internal NotOwnerRpcTarget(NetworkManager manager) : base(manager)
{
m_ServerRpcTarget = new ServerRpcTarget(manager);
m_LocalSendRpcTarget = new LocalSendRpcTarget(manager);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: d7bc66c5253b44d09ad978ea9e51c96f
timeCreated: 1698789420

View File

@@ -0,0 +1,72 @@
namespace Unity.Netcode
{
internal class NotServerRpcTarget : BaseRpcTarget
{
private IGroupRpcTarget m_GroupSendTarget;
private LocalSendRpcTarget m_LocalSendRpcTarget;
public override void Dispose()
{
m_LocalSendRpcTarget.Dispose();
if (m_GroupSendTarget != null)
{
m_GroupSendTarget.Target.Dispose();
m_GroupSendTarget = null;
}
}
internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, NetworkDelivery delivery, RpcParams rpcParams)
{
if (m_GroupSendTarget == null)
{
if (behaviour.IsServer)
{
m_GroupSendTarget = new RpcTargetGroup(m_NetworkManager);
}
else
{
m_GroupSendTarget = new ProxyRpcTargetGroup(m_NetworkManager);
}
}
m_GroupSendTarget.Clear();
if (behaviour.IsServer)
{
foreach (var clientId in behaviour.NetworkObject.Observers)
{
if (clientId == NetworkManager.ServerClientId)
{
continue;
}
m_GroupSendTarget.Add(clientId);
}
}
else
{
foreach (var clientId in m_NetworkManager.ConnectedClientsIds)
{
if (clientId == NetworkManager.ServerClientId)
{
continue;
}
if (clientId == behaviour.NetworkManager.LocalClientId)
{
m_LocalSendRpcTarget.Send(behaviour, ref message, delivery, rpcParams);
continue;
}
m_GroupSendTarget.Add(clientId);
}
}
m_GroupSendTarget.Target.Send(behaviour, ref message, delivery, rpcParams);
}
internal NotServerRpcTarget(NetworkManager manager) : base(manager)
{
m_LocalSendRpcTarget = new LocalSendRpcTarget(manager);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c63787afe52f45ffbd5d801f78e7c0d6
timeCreated: 1697824954

View File

@@ -0,0 +1,54 @@
namespace Unity.Netcode
{
internal class OwnerRpcTarget : BaseRpcTarget
{
private IIndividualRpcTarget m_UnderlyingTarget;
private LocalSendRpcTarget m_LocalRpcTarget;
private ServerRpcTarget m_ServerRpcTarget;
public override void Dispose()
{
m_LocalRpcTarget.Dispose();
if (m_UnderlyingTarget != null)
{
m_UnderlyingTarget.Target.Dispose();
m_UnderlyingTarget = null;
}
}
internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, NetworkDelivery delivery, RpcParams rpcParams)
{
if (behaviour.OwnerClientId == behaviour.NetworkManager.LocalClientId)
{
m_LocalRpcTarget.Send(behaviour, ref message, delivery, rpcParams);
return;
}
if (behaviour.OwnerClientId == NetworkManager.ServerClientId)
{
m_ServerRpcTarget.Send(behaviour, ref message, delivery, rpcParams);
return;
}
if (m_UnderlyingTarget == null)
{
if (behaviour.NetworkManager.IsServer)
{
m_UnderlyingTarget = new DirectSendRpcTarget(m_NetworkManager);
}
else
{
m_UnderlyingTarget = new ProxyRpcTarget(behaviour.OwnerClientId, m_NetworkManager);
}
}
m_UnderlyingTarget.SetClientId(behaviour.OwnerClientId);
m_UnderlyingTarget.Target.Send(behaviour, ref message, delivery, rpcParams);
}
internal OwnerRpcTarget(NetworkManager manager) : base(manager)
{
m_LocalRpcTarget = new LocalSendRpcTarget(manager);
m_ServerRpcTarget = new ServerRpcTarget(manager);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 23c4d52455fc419aaf03094617894257
timeCreated: 1697824972

View File

@@ -0,0 +1,16 @@
namespace Unity.Netcode
{
internal class ProxyRpcTarget : ProxyRpcTargetGroup, IIndividualRpcTarget
{
internal ProxyRpcTarget(ulong clientId, NetworkManager manager) : base(manager)
{
Add(clientId);
}
public void SetClientId(ulong clientId)
{
Clear();
Add(clientId);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 86002805bb9e422e8b71581d1325357f
timeCreated: 1697825007

View File

@@ -0,0 +1,100 @@
using System;
using System.Collections.Generic;
using Unity.Collections;
namespace Unity.Netcode
{
internal class ProxyRpcTargetGroup : BaseRpcTarget, IDisposable, IGroupRpcTarget
{
public BaseRpcTarget Target => this;
private ServerRpcTarget m_ServerRpcTarget;
private LocalSendRpcTarget m_LocalSendRpcTarget;
private bool m_Disposed;
public NativeList<ulong> TargetClientIds;
internal HashSet<ulong> Ids = new HashSet<ulong>();
internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, NetworkDelivery delivery, RpcParams rpcParams)
{
var proxyMessage = new ProxyMessage { Delivery = delivery, TargetClientIds = TargetClientIds.AsArray(), WrappedMessage = message };
#if DEVELOPMENT_BUILD || UNITY_EDITOR
var size =
#endif
behaviour.NetworkManager.MessageManager.SendMessage(ref proxyMessage, delivery, NetworkManager.ServerClientId);
#if DEVELOPMENT_BUILD || UNITY_EDITOR
if (NetworkBehaviour.__rpc_name_table[behaviour.GetType()].TryGetValue(message.Metadata.NetworkRpcMethodId, out var rpcMethodName))
{
foreach (var clientId in TargetClientIds)
{
behaviour.NetworkManager.NetworkMetrics.TrackRpcSent(
clientId,
behaviour.NetworkObject,
rpcMethodName,
behaviour.__getTypeName(),
size);
}
}
#endif
if (Ids.Contains(NetworkManager.ServerClientId))
{
m_ServerRpcTarget.Send(behaviour, ref message, delivery, rpcParams);
}
if (Ids.Contains(m_NetworkManager.LocalClientId))
{
m_LocalSendRpcTarget.Send(behaviour, ref message, delivery, rpcParams);
}
}
internal ProxyRpcTargetGroup(NetworkManager manager) : base(manager)
{
TargetClientIds = new NativeList<ulong>(Allocator.Persistent);
m_ServerRpcTarget = new ServerRpcTarget(manager);
m_LocalSendRpcTarget = new LocalSendRpcTarget(manager);
}
public override void Dispose()
{
CheckLockBeforeDispose();
if (!m_Disposed)
{
TargetClientIds.Dispose();
m_Disposed = true;
m_ServerRpcTarget.Dispose();
m_LocalSendRpcTarget.Dispose();
}
}
public void Add(ulong clientId)
{
if (!Ids.Contains(clientId))
{
Ids.Add(clientId);
if (clientId != NetworkManager.ServerClientId && clientId != m_NetworkManager.LocalClientId)
{
TargetClientIds.Add(clientId);
}
}
}
public void Remove(ulong clientId)
{
Ids.Remove(clientId);
for (var i = 0; i < TargetClientIds.Length; ++i)
{
if (TargetClientIds[i] == clientId)
{
TargetClientIds.RemoveAt(i);
break;
}
}
}
public void Clear()
{
Ids.Clear();
TargetClientIds.Clear();
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 5728dbab532e46a88127510b4ec75af9
timeCreated: 1697825000

View File

@@ -0,0 +1,564 @@
using System.Collections.Generic;
using Unity.Collections;
namespace Unity.Netcode
{
/// <summary>
/// Configuration for the default method by which an RPC is communicated across the network
/// </summary>
public enum SendTo
{
/// <summary>
/// Send to the NetworkObject's current owner.
/// Will execute locally if the local process is the owner.
/// </summary>
Owner,
/// <summary>
/// Send to everyone but the current owner, filtered to the current observer list.
/// Will execute locally if the local process is not the owner.
/// </summary>
NotOwner,
/// <summary>
/// Send to the server, regardless of ownership.
/// Will execute locally if invoked on the server.
/// </summary>
Server,
/// <summary>
/// Send to everyone but the server, filtered to the current observer list.
/// Will NOT send to a server running in host mode - it is still treated as a server.
/// If you want to send to servers when they are host, but not when they are dedicated server, use
/// <see cref="ClientsAndHost"/>.
/// <br />
/// <br />
/// Will execute locally if invoked on a client.
/// Will NOT execute locally if invoked on a server running in host mode.
/// </summary>
NotServer,
/// <summary>
/// Execute this RPC locally.
/// <br />
/// <br />
/// Normally this is no different from a standard function call.
/// <br />
/// <br />
/// Using the DeferLocal parameter of the attribute or the LocalDeferMode override in RpcSendParams,
/// this can allow an RPC to be processed on localhost with a one-frame delay as if it were sent over
/// the network.
/// </summary>
Me,
/// <summary>
/// Send this RPC to everyone but the local machine, filtered to the current observer list.
/// </summary>
NotMe,
/// <summary>
/// Send this RPC to everone, filtered to the current observer list.
/// Will execute locally.
/// </summary>
Everyone,
/// <summary>
/// Send this RPC to all clients, including the host, if a host exists.
/// If the server is running in host mode, this is the same as <see cref="Everyone" />.
/// If the server is running in dedicated server mode, this is the same as <see cref="NotServer" />.
/// </summary>
ClientsAndHost,
/// <summary>
/// This RPC cannot be sent without passing in a target in RpcSendParams.
/// </summary>
SpecifiedInParams
}
public enum RpcTargetUse
{
Temp,
Persistent
}
/// <summary>
/// Implementations of the various <see cref="SendTo"/> options, as well as additional runtime-only options
/// <see cref="Single"/>,
/// <see cref="Group(NativeArray{ulong})"/>,
/// <see cref="Group(NativeList{ulong})"/>,
/// <see cref="Group(ulong[])"/>,
/// <see cref="Group{T}(T)"/>, <see cref="Not(ulong)"/>,
/// <see cref="Not(NativeArray{ulong})"/>,
/// <see cref="Not(NativeList{ulong})"/>,
/// <see cref="Not(ulong[])"/>, and
/// <see cref="Not{T}(T)"/>
/// </summary>
public class RpcTarget
{
private NetworkManager m_NetworkManager;
internal RpcTarget(NetworkManager manager)
{
m_NetworkManager = manager;
Everyone = new EveryoneRpcTarget(manager);
Owner = new OwnerRpcTarget(manager);
NotOwner = new NotOwnerRpcTarget(manager);
Server = new ServerRpcTarget(manager);
NotServer = new NotServerRpcTarget(manager);
NotMe = new NotMeRpcTarget(manager);
Me = new LocalSendRpcTarget(manager);
ClientsAndHost = new ClientsAndHostRpcTarget(manager);
m_CachedProxyRpcTargetGroup = new ProxyRpcTargetGroup(manager);
m_CachedTargetGroup = new RpcTargetGroup(manager);
m_CachedDirectSendTarget = new DirectSendRpcTarget(manager);
m_CachedProxyRpcTarget = new ProxyRpcTarget(0, manager);
m_CachedProxyRpcTargetGroup.Lock();
m_CachedTargetGroup.Lock();
m_CachedDirectSendTarget.Lock();
m_CachedProxyRpcTarget.Lock();
}
public void Dispose()
{
Everyone.Dispose();
Owner.Dispose();
NotOwner.Dispose();
Server.Dispose();
NotServer.Dispose();
NotMe.Dispose();
Me.Dispose();
ClientsAndHost.Dispose();
m_CachedProxyRpcTargetGroup.Unlock();
m_CachedTargetGroup.Unlock();
m_CachedDirectSendTarget.Unlock();
m_CachedProxyRpcTarget.Unlock();
m_CachedProxyRpcTargetGroup.Dispose();
m_CachedTargetGroup.Dispose();
m_CachedDirectSendTarget.Dispose();
m_CachedProxyRpcTarget.Dispose();
}
/// <summary>
/// Send to the NetworkObject's current owner.
/// Will execute locally if the local process is the owner.
/// </summary>
public BaseRpcTarget Owner;
/// <summary>
/// Send to everyone but the current owner, filtered to the current observer list.
/// Will execute locally if the local process is not the owner.
/// </summary>
public BaseRpcTarget NotOwner;
/// <summary>
/// Send to the server, regardless of ownership.
/// Will execute locally if invoked on the server.
/// </summary>
public BaseRpcTarget Server;
/// <summary>
/// Send to everyone but the server, filtered to the current observer list.
/// Will NOT send to a server running in host mode - it is still treated as a server.
/// If you want to send to servers when they are host, but not when they are dedicated server, use
/// <see cref="SendTo.ClientsAndHost"/>.
/// <br />
/// <br />
/// Will execute locally if invoked on a client.
/// Will NOT execute locally if invoked on a server running in host mode.
/// </summary>
public BaseRpcTarget NotServer;
/// <summary>
/// Execute this RPC locally.
/// <br />
/// <br />
/// Normally this is no different from a standard function call.
/// <br />
/// <br />
/// Using the DeferLocal parameter of the attribute or the LocalDeferMode override in RpcSendParams,
/// this can allow an RPC to be processed on localhost with a one-frame delay as if it were sent over
/// the network.
/// </summary>
public BaseRpcTarget Me;
/// <summary>
/// Send this RPC to everyone but the local machine, filtered to the current observer list.
/// </summary>
public BaseRpcTarget NotMe;
/// <summary>
/// Send this RPC to everone, filtered to the current observer list.
/// Will execute locally.
/// </summary>
public BaseRpcTarget Everyone;
/// <summary>
/// Send this RPC to all clients, including the host, if a host exists.
/// If the server is running in host mode, this is the same as <see cref="Everyone" />.
/// If the server is running in dedicated server mode, this is the same as <see cref="NotServer" />.
/// </summary>
public BaseRpcTarget ClientsAndHost;
/// <summary>
/// Send to a specific single client ID.
/// </summary>
/// <param name="clientId"></param>
/// <param name="use"><see cref="RpcTargetUse.Temp"/> will return a cached target, which should not be stored as it will
/// be overwritten in future calls to Single(). Do not call Dispose() on Temp targets.<br /><br /><see cref="RpcTargetUse.Persistent"/> will
/// return a new target, which can be stored, but should not be done frequently because it results in a GC allocation. You must call Dispose() on Persistent targets when you are done with them.</param>
/// <returns></returns>
public BaseRpcTarget Single(ulong clientId, RpcTargetUse use)
{
if (clientId == m_NetworkManager.LocalClientId)
{
return Me;
}
if (m_NetworkManager.IsServer || clientId == NetworkManager.ServerClientId)
{
if (use == RpcTargetUse.Persistent)
{
return new DirectSendRpcTarget(clientId, m_NetworkManager);
}
m_CachedDirectSendTarget.SetClientId(clientId);
return m_CachedDirectSendTarget;
}
if (use == RpcTargetUse.Persistent)
{
return new ProxyRpcTarget(clientId, m_NetworkManager);
}
m_CachedProxyRpcTarget.SetClientId(clientId);
return m_CachedProxyRpcTarget;
}
/// <summary>
/// Send to everyone EXCEPT a specific single client ID.
/// </summary>
/// <param name="excludedClientId"></param>
/// <param name="use"><see cref="RpcTargetUse.Temp"/> will return a cached target, which should not be stored as it will
/// be overwritten in future calls to Not() or Group(). Do not call Dispose() on Temp targets.<br /><br /><see cref="RpcTargetUse.Persistent"/> will
/// return a new target, which can be stored, but should not be done frequently because it results in a GC allocation. You must call Dispose() on Persistent targets when you are done with them.</param>
/// <returns></returns>
public BaseRpcTarget Not(ulong excludedClientId, RpcTargetUse use)
{
IGroupRpcTarget target;
if (m_NetworkManager.IsServer)
{
if (use == RpcTargetUse.Persistent)
{
target = new RpcTargetGroup(m_NetworkManager);
}
else
{
target = m_CachedTargetGroup;
}
}
else
{
if (use == RpcTargetUse.Persistent)
{
target = new ProxyRpcTargetGroup(m_NetworkManager);
}
else
{
target = m_CachedProxyRpcTargetGroup;
}
}
target.Clear();
foreach (var clientId in m_NetworkManager.ConnectedClientsIds)
{
if (clientId != excludedClientId)
{
target.Add(clientId);
}
}
// If ServerIsHost, ConnectedClientIds already contains ServerClientId and this would duplicate it.
if (!m_NetworkManager.ServerIsHost && excludedClientId != NetworkManager.ServerClientId)
{
target.Add(NetworkManager.ServerClientId);
}
return target.Target;
}
/// <summary>
/// Sends to a group of client IDs.
/// NativeArrays can be trivially constructed using Allocator.Temp, making this an efficient
/// Group method if the group list is dynamically constructed.
/// </summary>
/// <param name="clientIds"></param>
/// <param name="use"><see cref="RpcTargetUse.Temp"/> will return a cached target, which should not be stored as it will
/// be overwritten in future calls to Not() or Group(). Do not call Dispose() on Temp targets.<br /><br /><see cref="RpcTargetUse.Persistent"/> will
/// return a new target, which can be stored, but should not be done frequently because it results in a GC allocation. You must call Dispose() on Persistent targets when you are done with them.</param>
/// <returns></returns>
public BaseRpcTarget Group(NativeArray<ulong> clientIds, RpcTargetUse use)
{
IGroupRpcTarget target;
if (m_NetworkManager.IsServer)
{
if (use == RpcTargetUse.Persistent)
{
target = new RpcTargetGroup(m_NetworkManager);
}
else
{
target = m_CachedTargetGroup;
}
}
else
{
if (use == RpcTargetUse.Persistent)
{
target = new ProxyRpcTargetGroup(m_NetworkManager);
}
else
{
target = m_CachedProxyRpcTargetGroup;
}
}
target.Clear();
foreach (var clientId in clientIds)
{
target.Add(clientId);
}
return target.Target;
}
/// <summary>
/// Sends to a group of client IDs.
/// NativeList can be trivially constructed using Allocator.Temp, making this an efficient
/// Group method if the group list is dynamically constructed.
/// </summary>
/// <param name="clientIds"></param>
/// <param name="use"><see cref="RpcTargetUse.Temp"/> will return a cached target, which should not be stored as it will
/// be overwritten in future calls to Not() or Group(). Do not call Dispose() on Temp targets.<br /><br /><see cref="RpcTargetUse.Persistent"/> will
/// return a new target, which can be stored, but should not be done frequently because it results in a GC allocation. You must call Dispose() on Persistent targets when you are done with them.</param>
/// <returns></returns>
public BaseRpcTarget Group(NativeList<ulong> clientIds, RpcTargetUse use)
{
var asArray = clientIds.AsArray();
return Group(asArray, use);
}
/// <summary>
/// Sends to a group of client IDs.
/// Constructing arrays requires garbage collected allocations. This override is only recommended
/// if you either have no strict performance requirements, or have the group of client IDs cached so
/// it is not created each time.
/// </summary>
/// <param name="clientIds"></param>
/// <param name="use"><see cref="RpcTargetUse.Temp"/> will return a cached target, which should not be stored as it will
/// be overwritten in future calls to Not() or Group(). Do not call Dispose() on Temp targets.<br /><br /><see cref="RpcTargetUse.Persistent"/> will
/// return a new target, which can be stored, but should not be done frequently because it results in a GC allocation. You must call Dispose() on Persistent targets when you are done with them.</param>
/// <returns></returns>
public BaseRpcTarget Group(ulong[] clientIds, RpcTargetUse use)
{
return Group(new NativeArray<ulong>(clientIds, Allocator.Temp), use);
}
/// <summary>
/// Sends to a group of client IDs.
/// This accepts any IEnumerable type, such as List&lt;ulong&gt;, but cannot be called without
/// a garbage collected allocation (even if the type itself is a struct type, due to boxing).
/// This override is only recommended if you either have no strict performance requirements,
/// or have the group of client IDs cached so it is not created each time.
/// </summary>
/// <param name="clientIds"></param>
/// <param name="use"><see cref="RpcTargetUse.Temp"/> will return a cached target, which should not be stored as it will
/// be overwritten in future calls to Not() or Group(). Do not call Dispose() on Temp targets.<br /><br /><see cref="RpcTargetUse.Persistent"/> will
/// return a new target, which can be stored, but should not be done frequently because it results in a GC allocation. You must call Dispose() on Persistent targets when you are done with them.</param>
/// <returns></returns>
public BaseRpcTarget Group<T>(T clientIds, RpcTargetUse use) where T : IEnumerable<ulong>
{
IGroupRpcTarget target;
if (m_NetworkManager.IsServer)
{
if (use == RpcTargetUse.Persistent)
{
target = new RpcTargetGroup(m_NetworkManager);
}
else
{
target = m_CachedTargetGroup;
}
}
else
{
if (use == RpcTargetUse.Persistent)
{
target = new ProxyRpcTargetGroup(m_NetworkManager);
}
else
{
target = m_CachedProxyRpcTargetGroup;
}
}
target.Clear();
foreach (var clientId in clientIds)
{
target.Add(clientId);
}
return target.Target;
}
/// <summary>
/// Sends to everyone EXCEPT a group of client IDs.
/// NativeArrays can be trivially constructed using Allocator.Temp, making this an efficient
/// Group method if the group list is dynamically constructed.
/// </summary>
/// <param name="excludedClientIds"></param>
/// <param name="use"><see cref="RpcTargetUse.Temp"/> will return a cached target, which should not be stored as it will
/// be overwritten in future calls to Not() or Group(). Do not call Dispose() on Temp targets.<br /><br /><see cref="RpcTargetUse.Persistent"/> will
/// return a new target, which can be stored, but should not be done frequently because it results in a GC allocation. You must call Dispose() on Persistent targets when you are done with them.</param>
/// <returns></returns>
public BaseRpcTarget Not(NativeArray<ulong> excludedClientIds, RpcTargetUse use)
{
IGroupRpcTarget target;
if (m_NetworkManager.IsServer)
{
if (use == RpcTargetUse.Persistent)
{
target = new RpcTargetGroup(m_NetworkManager);
}
else
{
target = m_CachedTargetGroup;
}
}
else
{
if (use == RpcTargetUse.Persistent)
{
target = new ProxyRpcTargetGroup(m_NetworkManager);
}
else
{
target = m_CachedProxyRpcTargetGroup;
}
}
target.Clear();
using var asASet = new NativeHashSet<ulong>(excludedClientIds.Length, Allocator.Temp);
foreach (var clientId in excludedClientIds)
{
asASet.Add(clientId);
}
foreach (var clientId in m_NetworkManager.ConnectedClientsIds)
{
if (!asASet.Contains(clientId))
{
target.Add(clientId);
}
}
// If ServerIsHost, ConnectedClientIds already contains ServerClientId and this would duplicate it.
if (!m_NetworkManager.ServerIsHost && !asASet.Contains(NetworkManager.ServerClientId))
{
target.Add(NetworkManager.ServerClientId);
}
return target.Target;
}
/// <summary>
/// Sends to everyone EXCEPT a group of client IDs.
/// NativeList can be trivially constructed using Allocator.Temp, making this an efficient
/// Group method if the group list is dynamically constructed.
/// </summary>
/// <param name="excludedClientIds"></param>
/// <param name="use"><see cref="RpcTargetUse.Temp"/> will return a cached target, which should not be stored as it will
/// be overwritten in future calls to Not() or Group(). Do not call Dispose() on Temp targets.<br /><br /><see cref="RpcTargetUse.Persistent"/> will
/// return a new target, which can be stored, but should not be done frequently because it results in a GC allocation. You must call Dispose() on Persistent targets when you are done with them.</param>
/// <returns></returns>
public BaseRpcTarget Not(NativeList<ulong> excludedClientIds, RpcTargetUse use)
{
var asArray = excludedClientIds.AsArray();
return Not(asArray, use);
}
/// <summary>
/// Sends to everyone EXCEPT a group of client IDs.
/// Constructing arrays requires garbage collected allocations. This override is only recommended
/// if you either have no strict performance requirements, or have the group of client IDs cached so
/// it is not created each time.
/// </summary>
/// <param name="excludedClientIds"></param>
/// <param name="use"><see cref="RpcTargetUse.Temp"/> will return a cached target, which should not be stored as it will
/// be overwritten in future calls to Not() or Group(). Do not call Dispose() on Temp targets.<br /><br /><see cref="RpcTargetUse.Persistent"/> will
/// return a new target, which can be stored, but should not be done frequently because it results in a GC allocation. You must call Dispose() on Persistent targets when you are done with them.</param>
/// <returns></returns>
public BaseRpcTarget Not(ulong[] excludedClientIds, RpcTargetUse use)
{
return Not(new NativeArray<ulong>(excludedClientIds, Allocator.Temp), use);
}
/// <summary>
/// Sends to everyone EXCEPT a group of client IDs.
/// This accepts any IEnumerable type, such as List&lt;ulong&gt;, but cannot be called without
/// a garbage collected allocation (even if the type itself is a struct type, due to boxing).
/// This override is only recommended if you either have no strict performance requirements,
/// or have the group of client IDs cached so it is not created each time.
/// </summary>
/// <param name="excludedClientIds"></param>
/// <param name="use"><see cref="RpcTargetUse.Temp"/> will return a cached target, which should not be stored as it will
/// be overwritten in future calls to Not() or Group(). Do not call Dispose() on Temp targets.<br /><br /><see cref="RpcTargetUse.Persistent"/> will
/// return a new target, which can be stored, but should not be done frequently because it results in a GC allocation. You must call Dispose() on Persistent targets when you are done with them.</param>
/// <returns></returns>
public BaseRpcTarget Not<T>(T excludedClientIds, RpcTargetUse use) where T : IEnumerable<ulong>
{
IGroupRpcTarget target;
if (m_NetworkManager.IsServer)
{
if (use == RpcTargetUse.Persistent)
{
target = new RpcTargetGroup(m_NetworkManager);
}
else
{
target = m_CachedTargetGroup;
}
}
else
{
if (use == RpcTargetUse.Persistent)
{
target = new ProxyRpcTargetGroup(m_NetworkManager);
}
else
{
target = m_CachedProxyRpcTargetGroup;
}
}
target.Clear();
using var asASet = new NativeHashSet<ulong>(m_NetworkManager.ConnectedClientsIds.Count, Allocator.Temp);
foreach (var clientId in excludedClientIds)
{
asASet.Add(clientId);
}
foreach (var clientId in m_NetworkManager.ConnectedClientsIds)
{
if (!asASet.Contains(clientId))
{
target.Add(clientId);
}
}
// If ServerIsHost, ConnectedClientIds already contains ServerClientId and this would duplicate it.
if (!m_NetworkManager.ServerIsHost && !asASet.Contains(NetworkManager.ServerClientId))
{
target.Add(NetworkManager.ServerClientId);
}
return target.Target;
}
private ProxyRpcTargetGroup m_CachedProxyRpcTargetGroup;
private RpcTargetGroup m_CachedTargetGroup;
private DirectSendRpcTarget m_CachedDirectSendTarget;
private ProxyRpcTarget m_CachedProxyRpcTarget;
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 1b26d0227e71408b918ae25ca2a0179b
timeCreated: 1699555535

View File

@@ -0,0 +1,80 @@
using System.Collections.Generic;
namespace Unity.Netcode
{
internal class RpcTargetGroup : BaseRpcTarget, IGroupRpcTarget
{
public BaseRpcTarget Target => this;
internal List<BaseRpcTarget> Targets = new List<BaseRpcTarget>();
private LocalSendRpcTarget m_LocalSendRpcTarget;
private HashSet<ulong> m_Ids = new HashSet<ulong>();
private Stack<DirectSendRpcTarget> m_TargetCache = new Stack<DirectSendRpcTarget>();
public override void Dispose()
{
CheckLockBeforeDispose();
foreach (var target in Targets)
{
target.Dispose();
}
foreach (var target in m_TargetCache)
{
target.Dispose();
}
m_LocalSendRpcTarget.Dispose();
}
internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, NetworkDelivery delivery, RpcParams rpcParams)
{
foreach (var target in Targets)
{
target.Send(behaviour, ref message, delivery, rpcParams);
}
}
public void Add(ulong clientId)
{
if (!m_Ids.Contains(clientId))
{
m_Ids.Add(clientId);
if (clientId == m_NetworkManager.LocalClientId)
{
Targets.Add(m_LocalSendRpcTarget);
}
else
{
if (m_TargetCache.Count == 0)
{
Targets.Add(new DirectSendRpcTarget(m_NetworkManager) { ClientId = clientId });
}
else
{
var target = m_TargetCache.Pop();
target.ClientId = clientId;
Targets.Add(target);
}
}
}
}
public void Clear()
{
m_Ids.Clear();
foreach (var target in Targets)
{
if (target is DirectSendRpcTarget directSendRpcTarget)
{
m_TargetCache.Push(directSendRpcTarget);
}
}
Targets.Clear();
}
internal RpcTargetGroup(NetworkManager manager) : base(manager)
{
m_LocalSendRpcTarget = new LocalSendRpcTarget(manager);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 7f8c0fc053b64a588c99dd7d706d9f0a
timeCreated: 1697824991

View File

@@ -0,0 +1,36 @@
namespace Unity.Netcode
{
internal class ServerRpcTarget : BaseRpcTarget
{
private BaseRpcTarget m_UnderlyingTarget;
public override void Dispose()
{
if (m_UnderlyingTarget != null)
{
m_UnderlyingTarget.Dispose();
m_UnderlyingTarget = null;
}
}
internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, NetworkDelivery delivery, RpcParams rpcParams)
{
if (m_UnderlyingTarget == null)
{
if (behaviour.NetworkManager.IsServer)
{
m_UnderlyingTarget = new LocalSendRpcTarget(m_NetworkManager);
}
else
{
m_UnderlyingTarget = new DirectSendRpcTarget(m_NetworkManager) { ClientId = NetworkManager.ServerClientId };
}
}
m_UnderlyingTarget.Send(behaviour, ref message, delivery, rpcParams);
}
internal ServerRpcTarget(NetworkManager manager) : base(manager)
{
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c911725afb6d44f3bb1a1d567d9dee0f
timeCreated: 1697824979