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