using FishNet.Connection;
using FishNet.Documenting;
using FishNet.Managing.Logging;
using FishNet.Managing.Transporting;
using FishNet.Object.Delegating;
using FishNet.Serializing;
using FishNet.Transporting;
using FishNet.Utility.Extension;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using UnityEngine;
namespace FishNet.Object
{
public abstract partial class NetworkBehaviour : MonoBehaviour
{
#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();
#endregion
///
/// Called when buffered RPCs should be sent.
///
internal void SendBufferedRpcs(NetworkConnection conn)
{
TransportManager tm = _networkObjectCache.NetworkManager.TransportManager;
foreach ((PooledWriter writer, Channel ch) in _bufferedRpcs.Values)
tm.SendToClient((byte)ch, writer.GetArraySegment(), conn);
}
///
/// Registers a RPC method.
/// Internal use.
///
///
///
[APIExclude] //codegen this can be made protected internal then set public via codegen
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected internal void RegisterServerRpc(uint hash, ServerRpcDelegate del)
{
bool contains = _serverRpcDelegates.ContainsKey(hash);
_serverRpcDelegates[hash] = del;
if (!contains)
IncreaseRpcMethodCount();
}
///
/// Registers a RPC method.
/// Internal use.
///
///
///
[APIExclude] //codegen this can be made protected internal then set public via codegen
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected internal void RegisterObserversRpc(uint hash, ClientRpcDelegate del)
{
bool contains = _observersRpcDelegates.ContainsKey(hash);
_observersRpcDelegates[hash] = del;
if (!contains)
IncreaseRpcMethodCount();
}
///
/// Registers a RPC method.
/// Internal use.
///
///
///
[APIExclude] //codegen this can be made protected internal then set public via codegen
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected internal void RegisterTargetRpc(uint hash, ClientRpcDelegate del)
{
bool contains = _targetRpcDelegates.ContainsKey(hash);
_targetRpcDelegates[hash] = del;
if (!contains)
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 ((PooledWriter writer, Channel _) in _bufferedRpcs.Values)
writer.Dispose();
_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)
{
if (_networkObjectCache.NetworkManager.CanLog(LoggingType.Error))
Debug.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(this, reader, channel, sendingClient);
}
else
{
if (_networkObjectCache.NetworkManager.CanLog(LoggingType.Warning))
Debug.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(this, reader, channel);
}
else
{
if (_networkObjectCache.NetworkManager.CanLog(LoggingType.Warning))
Debug.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(this, reader, channel);
}
else
{
if (_networkObjectCache.NetworkManager.CanLog(LoggingType.Warning))
Debug.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.
/// Internal use.
///
///
///
///
[APIExclude] //codegen this can be made internal then set public via codegen
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SendServerRpc(uint hash, PooledWriter methodWriter, Channel channel)
{
if (!IsSpawnedWithWarning())
return;
PooledWriter writer = CreateRpc(hash, methodWriter, PacketId.ServerRpc, channel);
_networkObjectCache.NetworkManager.TransportManager.SendToServer((byte)channel, writer.GetArraySegment());
writer.Dispose();
}
// ///
// /// Sends a RPC to observers.
// /// Internal use.
// ///
// ///
// ///
// ///
// [APIExclude] //codegen this can be made internal then set public via codegen
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
// protected internal bool InternalPrepareObserversRpc(uint hash, PooledWriter writer, Channel channel, bool buffered)
// {
// if (!IsSpawnedWithWarning())
// return false;
//#if UNITY_EDITOR || DEVELOPMENT_BUILD
// if (NetworkManager.DebugManager.ObserverRpcLinks && _rpcLinks.TryGetValueIL2CPP(hash, out RpcLinkType link))
//#else
// if (_rpcLinks.TryGetValueIL2CPP(hash, out RpcLinkType link))
//#endif
// CreateLinkedRpcHeader(link, writer);
// else
// CreateRpcHeader(hash, writer, PacketId.ObserversRpc);
// _networkObjectCache.NetworkManager.TransportManager.SendToClients((byte)channel, writer.GetArraySegment(), _networkObjectCache.Observers);
// InternalBufferObserversRpc(hash, writer, channel);
// return true;
// }
// ///
// /// Buffers an ObserverRPC.
// ///
// protected internal void InternalBufferObserversRpc(uint hash, PooledWriter writer, Channel channel)
// {
// /* 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 (_bufferedRpcs.TryGetValueIL2CPP(hash, out (PooledWriter pw, Channel ch) result))
// result.pw.Dispose();
// _bufferedRpcs[hash] = (writer, channel);
// }
///
/// Sends a RPC to observers.
/// Internal use.
///
///
///
///
[APIExclude] //codegen this can be made internal then set public via codegen
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SendObserversRpc(uint hash, PooledWriter methodWriter, Channel channel, bool buffered)
{
if (!IsSpawnedWithWarning())
return;
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);
_networkObjectCache.NetworkManager.TransportManager.SendToClients((byte)channel, writer.GetArraySegment(), _networkObjectCache.Observers);
/* 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 (buffered)
{
if (_bufferedRpcs.TryGetValueIL2CPP(hash, out (PooledWriter pw, Channel ch) result))
result.pw.Dispose();
_bufferedRpcs[hash] = (writer, channel);
}
//If not buffered then dispose immediately.
else
{
writer.Dispose();
}
}
///
/// Sends a RPC to target.
/// Internal use.
///
///
///
///
///
[APIExclude] //codegen this can be made internal then set public via codegen
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SendTargetRpc(uint hash, PooledWriter methodWriter, Channel channel, NetworkConnection target)
{
if (!IsSpawnedWithWarning())
return;
/* These checks could be codegened in to save a very very small amount of performance
* by performing them before the serializer is written, but the odds of these failing
* are very low and I'd rather keep the complexity out of codegen. */
if (target == null)
{
if (_networkObjectCache.NetworkManager.CanLog(LoggingType.Warning))
Debug.LogWarning($"Action cannot be completed as no Target is specified.");
return;
}
else
{
/* If not using observers, sending to owner,
* or observers contains target. */
//bool canSendTotarget = (!_networkObjectCache.UsingObservers ||
// _networkObjectCache.OwnerId == target.ClientId ||
// _networkObjectCache.Observers.Contains(target));
bool canSendTotarget = _networkObjectCache.OwnerId == target.ClientId || _networkObjectCache.Observers.Contains(target);
if (!canSendTotarget)
{
if (_networkObjectCache.NetworkManager.CanLog(LoggingType.Warning))
Debug.LogWarning($"Action cannot be completed as Target is not an observer for object {gameObject.name} [id {ObjectId}].");
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);
writer.Dispose();
}
///
/// Returns if spawned and throws a warning if not.
///
///
private bool IsSpawnedWithWarning()
{
bool result = this.IsSpawned;
if (!result)
{
if (_networkObjectCache.NetworkManager.CanLog(LoggingType.Warning))
Debug.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 void CreateRpcHeader(uint hash, PooledWriter writer, PacketId packetId)
//{
// writer.WritePacketId(packetId);
// writer.WriteNetworkBehaviour(this);
// WriteRpcHash(hash, writer);
//}
///
/// Writes a full RPC and returns the writer.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private PooledWriter CreateRpc(uint hash, PooledWriter methodWriter, PacketId packetId, Channel channel)
{
//Writer containing full packet.
PooledWriter writer = WriterPool.GetWriter();
writer.WritePacketId(packetId);
writer.WriteNetworkBehaviour(this);
//Only write length if reliable.
if (channel == Channel.Reliable)
writer.WriteLength(methodWriter.Length + _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);
}
}
}