using FishNet.Connection;
using FishNet.Documenting;
using FishNet.Managing.Logging;
using FishNet.Managing.Transporting;
using FishNet.Object.Delegating;
using FishNet.Serializing;
using FishNet.Serializing.Helping;
using FishNet.Transporting;
using GameKit.Utilities;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using UnityEngine;
namespace FishNet.Object
{
public abstract partial class NetworkBehaviour : MonoBehaviour
{
#region Types.
private struct BufferedRpc
{
public PooledWriter Writer;
public Channel Channel;
public DataOrderType OrderType;
public BufferedRpc(PooledWriter writer, Channel channel, DataOrderType orderType)
{
Writer = writer;
Channel = channel;
OrderType = orderType;
}
}
#endregion
#region Private.
///
/// Registered ServerRpc methods.
///
private readonly Dictionary _serverRpcDelegates = new Dictionary();
///
/// Registered ObserversRpc methods.
///
private readonly Dictionary _observersRpcDelegates = new Dictionary();
///
/// Registered TargetRpc methods.
///
private readonly Dictionary _targetRpcDelegates = new Dictionary();
///
/// Number of total RPC methods for scripts in the same inheritance tree for this instance.
///
private uint _rpcMethodCount;
///
/// Size of every rpcHash for this networkBehaviour.
///
private byte _rpcHashSize = 1;
///
/// RPCs buffered for new clients.
///
private Dictionary _bufferedRpcs = new Dictionary();
///
/// Connections to exclude from RPCs, such as ExcludeOwner or ExcludeServer.
///
private HashSet _networkConnectionCache = new HashSet();
#endregion
#region Const.
///
/// This is an estimated value of what the maximum possible size of a RPC could be.
/// Realistically this value is much smaller but this value is used as a buffer.
///
private const int MAXIMUM_RPC_HEADER_SIZE = 10;
#endregion
///
/// Called when buffered RPCs should be sent.
///
internal void SendBufferedRpcs(NetworkConnection conn)
{
TransportManager tm = _networkObjectCache.NetworkManager.TransportManager;
foreach (BufferedRpc bRpc in _bufferedRpcs.Values)
tm.SendToClient((byte)bRpc.Channel, bRpc.Writer.GetArraySegment(), conn, true, bRpc.OrderType);
}
///
/// Registers a RPC method.
///
///
///
[APIExclude]
[CodegenMakePublic]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void RegisterServerRpc(uint hash, ServerRpcDelegate del)
{
if (_serverRpcDelegates.TryGetValueIL2CPP(hash, out ServerRpcDelegate currentDelegate))
{
FishNet.Managing.NetworkManager.StaticLogError($"ServerRpc hash {hash} registered multiple times. First registration by {currentDelegate.Method.DeclaringType.GetType().FullName}. New registration by {GetType().FullName}.");
}
else
{
_serverRpcDelegates[hash] = del;
IncreaseRpcMethodCount();
}
}
///
/// Registers a RPC method.
///
///
///
[APIExclude]
[CodegenMakePublic]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void RegisterObserversRpc(uint hash, ClientRpcDelegate del)
{
if (_observersRpcDelegates.TryGetValueIL2CPP(hash, out ClientRpcDelegate currentDelegate))
{
FishNet.Managing.NetworkManager.StaticLogError($"ObserverRpc hash {hash} registered multiple times. First registration by {currentDelegate.Method.DeclaringType.GetType().FullName}. New registration by {GetType().FullName}.");
}
else
{
_observersRpcDelegates[hash] = del;
IncreaseRpcMethodCount();
}
}
///
/// Registers a RPC method.
///
///
///
[APIExclude]
[CodegenMakePublic]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void RegisterTargetRpc(uint hash, ClientRpcDelegate del)
{
if (_targetRpcDelegates.TryGetValueIL2CPP(hash, out ClientRpcDelegate currentDelegate))
{
FishNet.Managing.NetworkManager.StaticLogError($"TargetRpc hash {hash} registered multiple times. First registration by {currentDelegate.Method.DeclaringType.GetType().FullName}. New registration by {GetType().FullName}.");
}
else
{
_targetRpcDelegates[hash] = del;
IncreaseRpcMethodCount();
}
}
///
/// Increases rpcMethodCount and rpcHashSize.
///
private void IncreaseRpcMethodCount()
{
_rpcMethodCount++;
if (_rpcMethodCount <= byte.MaxValue)
_rpcHashSize = 1;
else
_rpcHashSize = 2;
}
///
/// Clears all buffered RPCs for this NetworkBehaviour.
///
public void ClearBuffedRpcs()
{
foreach (BufferedRpc bRpc in _bufferedRpcs.Values)
bRpc.Writer.Store();
_bufferedRpcs.Clear();
}
///
/// Reads a RPC hash.
///
///
///
private uint ReadRpcHash(PooledReader reader)
{
if (_rpcHashSize == 1)
return reader.ReadByte();
else
return reader.ReadUInt16();
}
///
/// Called when a ServerRpc is received.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void OnServerRpc(PooledReader reader, NetworkConnection sendingClient, Channel channel)
{
uint methodHash = ReadRpcHash(reader);
if (sendingClient == null)
{
_networkObjectCache.NetworkManager.LogError($"NetworkConnection is null. ServerRpc {methodHash} on object {gameObject.name} [id {ObjectId}] will not complete. Remainder of packet may become corrupt.");
return;
}
if (_serverRpcDelegates.TryGetValueIL2CPP(methodHash, out ServerRpcDelegate data))
data.Invoke(reader, channel, sendingClient);
else
_networkObjectCache.NetworkManager.LogWarning($"ServerRpc not found for hash {methodHash} on object {gameObject.name} [id {ObjectId}]. Remainder of packet may become corrupt.");
}
///
/// Called when an ObserversRpc is received.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void OnObserversRpc(uint? methodHash, PooledReader reader, Channel channel)
{
if (methodHash == null)
methodHash = ReadRpcHash(reader);
if (_observersRpcDelegates.TryGetValueIL2CPP(methodHash.Value, out ClientRpcDelegate del))
del.Invoke(reader, channel);
else
_networkObjectCache.NetworkManager.LogWarning($"ObserversRpc not found for hash {methodHash.Value} on object {gameObject.name} [id {ObjectId}] . Remainder of packet may become corrupt.");
}
///
/// Called when an TargetRpc is received.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void OnTargetRpc(uint? methodHash, PooledReader reader, Channel channel)
{
if (methodHash == null)
methodHash = ReadRpcHash(reader);
if (_targetRpcDelegates.TryGetValueIL2CPP(methodHash.Value, out ClientRpcDelegate del))
del.Invoke(reader, channel);
else
_networkObjectCache.NetworkManager.LogWarning($"TargetRpc not found for hash {methodHash.Value} on object {gameObject.name} [id {ObjectId}] . Remainder of packet may become corrupt.");
}
///
/// Sends a RPC to server.
///
///
///
///
[CodegenMakePublic]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected internal void SendServerRpc(uint hash, PooledWriter methodWriter, Channel channel, DataOrderType orderType)
{
if (!IsSpawnedWithWarning())
return;
_transportManagerCache.CheckSetReliableChannel(methodWriter.Length + MAXIMUM_RPC_HEADER_SIZE, ref channel);
PooledWriter writer = CreateRpc(hash, methodWriter, PacketId.ServerRpc, channel);
_networkObjectCache.NetworkManager.TransportManager.SendToServer((byte)channel, writer.GetArraySegment(), true, orderType);
writer.StoreLength();
}
///
/// Sends a RPC to observers.
///
///
///
///
[APIExclude]
[CodegenMakePublic] //Make internal.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected internal void SendObserversRpc(uint hash, PooledWriter methodWriter, Channel channel, DataOrderType orderType, bool bufferLast, bool excludeServer, bool excludeOwner)
{
if (!IsSpawnedWithWarning())
return;
_transportManagerCache.CheckSetReliableChannel(methodWriter.Length + MAXIMUM_RPC_HEADER_SIZE, ref channel);
PooledWriter writer;
#if UNITY_EDITOR || DEVELOPMENT_BUILD
if (NetworkManager.DebugManager.ObserverRpcLinks && _rpcLinks.TryGetValueIL2CPP(hash, out RpcLinkType link))
#else
if (_rpcLinks.TryGetValueIL2CPP(hash, out RpcLinkType link))
#endif
writer = CreateLinkedRpc(link, methodWriter, channel);
else
writer = CreateRpc(hash, methodWriter, PacketId.ObserversRpc, channel);
SetNetworkConnectionCache(excludeServer, excludeOwner);
_networkObjectCache.NetworkManager.TransportManager.SendToClients((byte)channel, writer.GetArraySegment(), _networkObjectCache.Observers, _networkConnectionCache, true, orderType);
/* If buffered then dispose of any already buffered
* writers and replace with new one. Writers should
* automatically dispose when references are lost
* anyway but better safe than sorry. */
if (bufferLast)
{
if (_bufferedRpcs.TryGetValueIL2CPP(hash, out BufferedRpc result))
result.Writer.StoreLength();
_bufferedRpcs[hash] = new BufferedRpc(writer, channel, orderType);
}
//If not buffered then dispose immediately.
else
{
writer.StoreLength();
}
}
///
/// Sends a RPC to target.
///
[CodegenMakePublic] //Make internal.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected internal void SendTargetRpc(uint hash, PooledWriter methodWriter, Channel channel, DataOrderType orderType, NetworkConnection target, bool excludeServer, bool validateTarget = true)
{
if (!IsSpawnedWithWarning())
return;
_transportManagerCache.CheckSetReliableChannel(methodWriter.Length + MAXIMUM_RPC_HEADER_SIZE, ref channel);
if (validateTarget)
{
if (target == null)
{
_networkObjectCache.NetworkManager.LogWarning($"Action cannot be completed as no Target is specified.");
return;
}
else
{
//If target is not an observer.
if (!_networkObjectCache.Observers.Contains(target))
{
_networkObjectCache.NetworkManager.LogWarning($"Action cannot be completed as Target is not an observer for object {gameObject.name} [id {ObjectId}].");
return;
}
}
}
//Excluding server.
if (excludeServer && target.IsLocalClient)
return;
PooledWriter writer;
#if UNITY_EDITOR || DEVELOPMENT_BUILD
if (NetworkManager.DebugManager.TargetRpcLinks && _rpcLinks.TryGetValueIL2CPP(hash, out RpcLinkType link))
#else
if (_rpcLinks.TryGetValueIL2CPP(hash, out RpcLinkType link))
#endif
writer = CreateLinkedRpc(link, methodWriter, channel);
else
writer = CreateRpc(hash, methodWriter, PacketId.TargetRpc, channel);
_networkObjectCache.NetworkManager.TransportManager.SendToClient((byte)channel, writer.GetArraySegment(), target, true, orderType);
writer.Store();
}
///
/// Adds excluded connections to ExcludedRpcConnections.
///
private void SetNetworkConnectionCache(bool addClientHost, bool addOwner)
{
_networkConnectionCache.Clear();
if (addClientHost && IsClient)
_networkConnectionCache.Add(LocalConnection);
if (addOwner && Owner.IsValid)
_networkConnectionCache.Add(Owner);
}
///
/// Returns if spawned and throws a warning if not.
///
///
private bool IsSpawnedWithWarning()
{
bool result = this.IsSpawned;
if (!result)
_networkObjectCache.NetworkManager.LogWarning($"Action cannot be completed as object {gameObject.name} [Id {ObjectId}] is not spawned.");
return result;
}
///
/// Writes a full RPC and returns the writer.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private PooledWriter CreateRpc(uint hash, PooledWriter methodWriter, PacketId packetId, Channel channel)
{
int rpcHeaderBufferLength = GetEstimatedRpcHeaderLength();
int methodWriterLength = methodWriter.Length;
//Writer containing full packet.
PooledWriter writer = WriterPool.Retrieve(rpcHeaderBufferLength + methodWriterLength);
writer.WritePacketId(packetId);
writer.WriteNetworkBehaviour(this);
//Only write length if reliable.
if (channel == Channel.Reliable)
writer.WriteLength(methodWriterLength + _rpcHashSize);
//Hash and data.
WriteRpcHash(hash, writer);
writer.WriteArraySegment(methodWriter.GetArraySegment());
return writer;
}
///
/// Writes rpcHash to writer.
///
///
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void WriteRpcHash(uint hash, PooledWriter writer)
{
if (_rpcHashSize == 1)
writer.WriteByte((byte)hash);
else
writer.WriteUInt16((byte)hash);
}
}
}