using System.Collections.Generic; using Unity.Collections; namespace Unity.Netcode { /// /// Configuration for the default method by which an RPC is communicated across the network /// public enum SendTo { /// /// Send to the NetworkObject's current owner. /// Will execute locally if the local process is the owner. /// Owner, /// /// Send to everyone but the current owner, filtered to the current observer list. /// Will execute locally if the local process is not the owner. /// NotOwner, /// /// Send to the server, regardless of ownership. /// Will execute locally if invoked on the server. /// Server, /// /// 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 /// . ///
///
/// Will execute locally if invoked on a client. /// Will NOT execute locally if invoked on a server running in host mode. ///
NotServer, /// /// Execute this RPC locally. ///
///
/// Normally this is no different from a standard function call. ///
///
/// 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. ///
Me, /// /// Send this RPC to everyone but the local machine, filtered to the current observer list. /// NotMe, /// /// Send this RPC to everone, filtered to the current observer list. /// Will execute locally. /// Everyone, /// /// 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 . /// If the server is running in dedicated server mode, this is the same as . /// ClientsAndHost, /// /// 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. /// Authority, /// /// 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. /// NotAuthority, /// /// This RPC cannot be sent without passing in a target in RpcSendParams. /// SpecifiedInParams } /// /// This parameter configures a performance optimization. This optimization is not valid in all situations.
/// Because BaseRpcTarget is a managed type, allocating a new one is expensive, as it puts pressure on the garbage collector. ///
/// /// When using a allocation type for the RPC target(s):
/// You typically don't need to worry about persisting the generated. /// When using a allocation type for the RPC target(s):
/// You will want to use , which returns , during initialization (i.e. ) and it to a property.
/// Then, When invoking the RPC, you would use your which is a persisted allocation of a given set of client identifiers. /// !! Important !!
/// You will want to invoke of any persisted properties created via when despawning or destroying the associated component's . Not doing so will result in small memory leaks. ///
public enum RpcTargetUse { /// /// Creates a temporary used for the frame an decorated method is invoked. /// Temp, /// /// Creates a persisted that does not change and will persist until is called. /// Persistent } /// /// Implementations of the various options, as well as additional runtime-only options /// , /// , /// , /// , /// , , /// , /// , /// , and /// /// 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); Authority = new AuthorityRpcTarget(manager); NotAuthority = new NotAuthorityRpcTarget(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(); Authority.Dispose(); NotAuthority.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(); } /// /// Send to the NetworkObject's current owner. /// Will execute locally if the local process is the owner. /// public BaseRpcTarget Owner; /// /// Send to everyone but the current owner, filtered to the current observer list. /// Will execute locally if the local process is not the owner. /// public BaseRpcTarget NotOwner; /// /// Send to the server, regardless of ownership. /// Will execute locally if invoked on the server. /// public BaseRpcTarget Server; /// /// 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 /// . ///
///
/// Will execute locally if invoked on a client. /// Will NOT execute locally if invoked on a server running in host mode. ///
public BaseRpcTarget NotServer; /// /// Execute this RPC locally. ///
///
/// Normally this is no different from a standard function call. ///
///
/// 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. ///
public BaseRpcTarget Me; /// /// Send this RPC to everyone but the local machine, filtered to the current observer list. /// public BaseRpcTarget NotMe; /// /// Send this RPC to everone, filtered to the current observer list. /// Will execute locally. /// public BaseRpcTarget Everyone; /// /// 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 . /// If the server is running in dedicated server mode, this is the same as . /// public BaseRpcTarget ClientsAndHost; /// /// 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. /// public BaseRpcTarget Authority; /// /// 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. /// public BaseRpcTarget NotAuthority; /// /// Send to a specific single client ID. /// /// /// 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.

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. /// 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; } /// /// Send to everyone EXCEPT a specific single client ID. /// /// /// 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.

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. /// 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; } /// /// 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. /// /// /// 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.

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. /// public BaseRpcTarget Group(NativeArray 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; } /// /// 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. /// /// /// 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.

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. /// public BaseRpcTarget Group(NativeList clientIds, RpcTargetUse use) { var asArray = clientIds.AsArray(); return Group(asArray, use); } /// /// 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. /// /// /// 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.

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. /// public BaseRpcTarget Group(ulong[] clientIds, RpcTargetUse use) { return Group(new NativeArray(clientIds, Allocator.Temp), use); } /// /// Sends to a group of client IDs. /// This accepts any IEnumerable type, such as List<ulong>, 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. /// /// /// 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.

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. /// public BaseRpcTarget Group(T clientIds, RpcTargetUse use) where T : IEnumerable { 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; } /// /// 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. /// /// /// 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.

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. /// public BaseRpcTarget Not(NativeArray 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(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; } /// /// 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. /// /// /// 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.

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. /// public BaseRpcTarget Not(NativeList excludedClientIds, RpcTargetUse use) { var asArray = excludedClientIds.AsArray(); return Not(asArray, use); } /// /// 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. /// /// /// 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.

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. /// public BaseRpcTarget Not(ulong[] excludedClientIds, RpcTargetUse use) { return Not(new NativeArray(excludedClientIds, Allocator.Temp), use); } /// /// Sends to everyone EXCEPT a group of client IDs. /// This accepts any IEnumerable type, such as List<ulong>, 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. /// /// /// 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.

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. /// public BaseRpcTarget Not(T excludedClientIds, RpcTargetUse use) where T : IEnumerable { 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(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; } }