quantum-space-buddies/QuantumUNET/QNetworkBehaviour.cs
2021-12-02 19:41:19 -08:00

608 lines
15 KiB
C#

using QuantumUNET.Components;
using QuantumUNET.Logging;
using QuantumUNET.Messages;
using QuantumUNET.Transport;
using System;
using System.Collections.Generic;
using System.Net.Sockets;
using UnityEngine;
namespace QuantumUNET
{
public class QNetworkBehaviour : MonoBehaviour
{
public bool LocalPlayerAuthority => MyView.LocalPlayerAuthority;
public bool IsServer => MyView.IsServer;
public bool IsClient => MyView.IsClient;
public bool IsLocalPlayer => MyView.IsLocalPlayer;
public bool HasAuthority => MyView.HasAuthority;
public QNetworkInstanceId NetId => MyView.NetId;
public QNetworkConnection ConnectionToServer => MyView.ConnectionToServer;
public QNetworkConnection ConnectionToClient => MyView.ConnectionToClient;
public short PlayerControllerId => MyView.PlayerControllerId;
protected uint SyncVarDirtyBits { get; private set; }
protected bool SyncVarHookGuard { get; set; }
public QNetworkIdentity NetIdentity => MyView;
private QNetworkIdentity MyView
{
get
{
QNetworkIdentity myView;
if (gameObject == null)
{
QLog.FatalError($"Trying to get QNetworkIdentity of a null gameobject?");
return null;
}
if (m_MyView == null)
{
m_MyView = GetComponent<QNetworkIdentity>();
if (m_MyView == null)
{
QLog.FatalError($"There is no QNetworkIdentity on this object (name={name}). Please add one.");
}
myView = m_MyView;
}
else
{
myView = m_MyView;
}
return myView;
}
}
protected void ClientSendUpdateVars()
{
var writer = new QNetworkWriter();
writer.StartMessage(QMsgType.ClientUpdateVars);
writer.Write(NetId);
writer.Write(GetType().Name);
if (OnSerialize(writer, false))
{
ClearAllDirtyBits();
writer.FinishMessage();
QClientScene.readyConnection.SendWriter(writer, GetNetworkChannel());
}
}
protected void SendCommandInternal(QNetworkWriter writer, int channelId, string cmdName)
{
if (!IsLocalPlayer && !HasAuthority)
{
QLog.Warning("Trying to send command for object without authority.");
}
else if (QClientScene.readyConnection == null)
{
QLog.Error($"Send command attempted with no client running [client={ConnectionToServer}].");
}
else
{
writer.FinishMessage();
QClientScene.readyConnection.SendWriter(writer, channelId);
}
}
public virtual bool InvokeCommand(int cmdHash, QNetworkReader reader) => InvokeCommandDelegate(cmdHash, reader);
protected void SendRPCInternal(QNetworkWriter writer, int channelId, string rpcName)
{
if (!IsServer)
{
QLog.Warning("ClientRpc call on un-spawned object");
return;
}
writer.FinishMessage();
QNetworkServer.SendWriterToReady(gameObject, writer, channelId);
}
protected void SendTargetRPCInternal(QNetworkConnection conn, QNetworkWriter writer, int channelId, string rpcName)
{
if (!IsServer)
{
QLog.Warning("TargetRpc call on un-spawned object");
return;
}
writer.FinishMessage();
conn.SendWriter(writer, channelId);
}
public virtual bool InvokeRPC(int cmdHash, QNetworkReader reader) => InvokeRpcDelegate(cmdHash, reader);
protected void SendEventInternal(QNetworkWriter writer, int channelId, string eventName)
{
if (!QNetworkServer.active)
{
QLog.Error($"Tried to send event {eventName} on channel {channelId} but QSBNetworkServer isn't active.");
return;
}
writer.FinishMessage();
QNetworkServer.SendWriterToReady(gameObject, writer, channelId);
}
public virtual bool InvokeSyncEvent(int cmdHash, QNetworkReader reader) => InvokeSyncEventDelegate(cmdHash, reader);
public virtual bool InvokeSyncList(int cmdHash, QNetworkReader reader) => InvokeSyncListDelegate(cmdHash, reader);
protected static void RegisterCommandDelegate(Type invokeClass, int cmdHash, CmdDelegate func)
{
if (!s_CmdHandlerDelegates.ContainsKey(cmdHash))
{
var invoker = new Invoker
{
invokeType = UNetInvokeType.Command,
invokeClass = invokeClass,
invokeFunction = func
};
s_CmdHandlerDelegates[cmdHash] = invoker;
}
}
protected static void RegisterRpcDelegate(Type invokeClass, int cmdHash, CmdDelegate func)
{
if (!s_CmdHandlerDelegates.ContainsKey(cmdHash))
{
var invoker = new Invoker
{
invokeType = UNetInvokeType.ClientRpc,
invokeClass = invokeClass,
invokeFunction = func
};
s_CmdHandlerDelegates[cmdHash] = invoker;
}
}
protected static void RegisterEventDelegate(Type invokeClass, int cmdHash, CmdDelegate func)
{
if (!s_CmdHandlerDelegates.ContainsKey(cmdHash))
{
var invoker = new Invoker
{
invokeType = UNetInvokeType.SyncEvent,
invokeClass = invokeClass,
invokeFunction = func
};
s_CmdHandlerDelegates[cmdHash] = invoker;
}
}
protected static void RegisterSyncListDelegate(Type invokeClass, int cmdHash, CmdDelegate func)
{
if (!s_CmdHandlerDelegates.ContainsKey(cmdHash))
{
var invoker = new Invoker
{
invokeType = UNetInvokeType.SyncList,
invokeClass = invokeClass,
invokeFunction = func
};
s_CmdHandlerDelegates[cmdHash] = invoker;
}
}
internal static string GetInvoker(int cmdHash)
{
string result;
if (!s_CmdHandlerDelegates.ContainsKey(cmdHash))
{
result = null;
}
else
{
var invoker = s_CmdHandlerDelegates[cmdHash];
result = invoker.DebugString();
}
return result;
}
internal static bool GetInvokerForHashCommand(int cmdHash, out Type invokeClass, out CmdDelegate invokeFunction)
=> GetInvokerForHash(cmdHash, UNetInvokeType.Command, out invokeClass, out invokeFunction);
internal static bool GetInvokerForHashClientRpc(int cmdHash, out Type invokeClass, out CmdDelegate invokeFunction)
=> GetInvokerForHash(cmdHash, UNetInvokeType.ClientRpc, out invokeClass, out invokeFunction);
internal static bool GetInvokerForHashSyncList(int cmdHash, out Type invokeClass, out CmdDelegate invokeFunction)
=> GetInvokerForHash(cmdHash, UNetInvokeType.SyncList, out invokeClass, out invokeFunction);
internal static bool GetInvokerForHashSyncEvent(int cmdHash, out Type invokeClass, out CmdDelegate invokeFunction)
=> GetInvokerForHash(cmdHash, UNetInvokeType.SyncEvent, out invokeClass, out invokeFunction);
private static bool GetInvokerForHash(int cmdHash, UNetInvokeType invokeType, out Type invokeClass, out CmdDelegate invokeFunction)
{
bool result;
if (!s_CmdHandlerDelegates.TryGetValue(cmdHash, out var invoker))
{
QLog.Error($"GetInvokerForHash hash:{cmdHash} not found");
invokeClass = null;
invokeFunction = null;
result = false;
}
else if (invoker == null)
{
QLog.Error($"GetInvokerForHash hash:{cmdHash} invoker null");
invokeClass = null;
invokeFunction = null;
result = false;
}
else if (invoker.invokeType != invokeType)
{
QLog.Error($"GetInvokerForHash hash:{cmdHash} mismatched invokeType");
invokeClass = null;
invokeFunction = null;
result = false;
}
else
{
invokeClass = invoker.invokeClass;
invokeFunction = invoker.invokeFunction;
result = true;
}
return result;
}
internal static void DumpInvokers()
{
QLog.Log($"DumpInvokers size:{s_CmdHandlerDelegates.Count}");
foreach (var keyValuePair in s_CmdHandlerDelegates)
{
QLog.Log($" Invoker:{keyValuePair.Value.invokeClass}:{keyValuePair.Value.invokeFunction.GetMethodName()} {keyValuePair.Value.invokeType} {keyValuePair.Key}");
}
}
internal bool ContainsCommandDelegate(int cmdHash)
=> s_CmdHandlerDelegates.ContainsKey(cmdHash);
internal bool InvokeCommandDelegate(int cmdHash, QNetworkReader reader)
{
bool result;
if (!s_CmdHandlerDelegates.ContainsKey(cmdHash))
{
result = false;
}
else
{
var invoker = s_CmdHandlerDelegates[cmdHash];
if (invoker.invokeType != UNetInvokeType.Command)
{
result = false;
}
else
{
if (GetType() != invoker.invokeClass)
{
if (!GetType().IsSubclassOf(invoker.invokeClass))
{
return false;
}
}
invoker.invokeFunction(this, reader);
result = true;
}
}
return result;
}
internal bool InvokeRpcDelegate(int cmdHash, QNetworkReader reader)
{
bool result;
if (!s_CmdHandlerDelegates.ContainsKey(cmdHash))
{
result = false;
}
else
{
var invoker = s_CmdHandlerDelegates[cmdHash];
if (invoker.invokeType != UNetInvokeType.ClientRpc)
{
result = false;
}
else
{
if (GetType() != invoker.invokeClass)
{
if (!GetType().IsSubclassOf(invoker.invokeClass))
{
return false;
}
}
invoker.invokeFunction(this, reader);
result = true;
}
}
return result;
}
internal bool InvokeSyncEventDelegate(int cmdHash, QNetworkReader reader)
{
bool result;
if (!s_CmdHandlerDelegates.ContainsKey(cmdHash))
{
result = false;
}
else
{
var invoker = s_CmdHandlerDelegates[cmdHash];
if (invoker.invokeType != UNetInvokeType.SyncEvent)
{
result = false;
}
else
{
invoker.invokeFunction(this, reader);
result = true;
}
}
return result;
}
internal bool InvokeSyncListDelegate(int cmdHash, QNetworkReader reader)
{
bool result;
if (!s_CmdHandlerDelegates.ContainsKey(cmdHash))
{
result = false;
}
else
{
var invoker = s_CmdHandlerDelegates[cmdHash];
if (invoker.invokeType != UNetInvokeType.SyncList)
{
result = false;
}
else if (GetType() != invoker.invokeClass)
{
result = false;
}
else
{
invoker.invokeFunction(this, reader);
result = true;
}
}
return result;
}
internal static string GetCmdHashHandlerName(int cmdHash)
{
string result;
if (!s_CmdHandlerDelegates.ContainsKey(cmdHash))
{
result = cmdHash.ToString();
}
else
{
var invoker = s_CmdHandlerDelegates[cmdHash];
result = $"{invoker.invokeType}:{invoker.invokeFunction.GetMethodName()}";
}
return result;
}
private static string GetCmdHashPrefixName(int cmdHash, string prefix)
{
string result;
if (!s_CmdHandlerDelegates.ContainsKey(cmdHash))
{
result = cmdHash.ToString();
}
else
{
var invoker = s_CmdHandlerDelegates[cmdHash];
var text = invoker.invokeFunction.GetMethodName();
var num = text.IndexOf(prefix);
if (num > -1)
{
text = text.Substring(prefix.Length);
}
result = text;
}
return result;
}
internal static string GetCmdHashCmdName(int cmdHash)
=> GetCmdHashPrefixName(cmdHash, "InvokeCmd");
internal static string GetCmdHashRpcName(int cmdHash)
=> GetCmdHashPrefixName(cmdHash, "InvokeRpc");
internal static string GetCmdHashEventName(int cmdHash)
=> GetCmdHashPrefixName(cmdHash, "InvokeSyncEvent");
internal static string GetCmdHashListName(int cmdHash)
=> GetCmdHashPrefixName(cmdHash, "InvokeSyncList");
protected void SetSyncVarGameObject(GameObject newGameObject, ref GameObject gameObjectField, uint dirtyBit, ref QNetworkInstanceId netIdField)
{
if (!SyncVarHookGuard)
{
QNetworkInstanceId networkInstanceId = default;
if (newGameObject != null)
{
var component = newGameObject.GetComponent<QNetworkIdentity>();
if (component != null)
{
networkInstanceId = component.NetId;
if (networkInstanceId.IsEmpty())
{
QLog.Warning(
$"SetSyncVarGameObject GameObject {newGameObject} has a zero netId. Maybe it is not spawned yet?");
}
}
}
QNetworkInstanceId networkInstanceId2 = default;
if (gameObjectField != null)
{
networkInstanceId2 = gameObjectField.GetComponent<QNetworkIdentity>().NetId;
}
if (networkInstanceId != networkInstanceId2)
{
QLog.Log(
$"SetSyncVar GameObject {GetType().Name} bit [{dirtyBit}] netfieldId:{networkInstanceId2}->{networkInstanceId}");
SetDirtyBit(dirtyBit);
gameObjectField = newGameObject;
netIdField = networkInstanceId;
}
}
}
protected bool SetSyncVar<T>(T value, ref T fieldValue, uint dirtyBit)
{
var shouldSet = false;
if (value == null)
{
if (fieldValue != null)
{
shouldSet = true;
}
}
else
{
shouldSet = !value.Equals(fieldValue);
}
if (shouldSet)
{
QLog.Log($"SetSyncVar {GetType().Name} bit [{dirtyBit}] {fieldValue}->{value}");
SetDirtyBit(dirtyBit);
fieldValue = value;
}
return shouldSet;
}
public void SetDirtyBit(uint dirtyBit) => SyncVarDirtyBits |= dirtyBit;
public void ClearAllDirtyBits()
{
m_LastSendTime = Time.time;
SyncVarDirtyBits = 0U;
}
internal int GetDirtyChannel()
{
if (Time.time - m_LastSendTime > GetNetworkSendInterval())
{
if (SyncVarDirtyBits != 0U)
{
return GetNetworkChannel();
}
}
return -1;
}
public virtual bool OnSerialize(QNetworkWriter writer, bool initialState)
{
if (!initialState)
{
writer.WritePackedUInt32(0U);
}
return false;
}
public virtual void OnDeserialize(QNetworkReader reader, bool initialState)
{
if (!initialState)
{
reader.ReadPackedUInt32();
}
}
public virtual void PreStartClient()
{
}
public virtual void OnNetworkDestroy()
{
}
public virtual void OnStartServer()
{
}
public virtual void OnStartClient()
{
}
public virtual void OnStartLocalPlayer()
{
}
public virtual void OnStartAuthority()
{
}
public virtual void OnStopAuthority()
{
}
public virtual bool OnRebuildObservers(HashSet<QNetworkConnection> observers, bool initialize) => false;
public virtual void OnSetLocalVisibility(bool vis)
{
}
public virtual bool OnCheckObserver(QNetworkConnection conn) => true;
public virtual int GetNetworkChannel() => 0;
public virtual float GetNetworkSendInterval() => 0.1f;
private float m_LastSendTime;
private QNetworkIdentity m_MyView;
private static readonly Dictionary<int, Invoker> s_CmdHandlerDelegates = new();
public delegate void CmdDelegate(QNetworkBehaviour obj, QNetworkReader reader);
protected delegate void EventDelegate(List<Delegate> targets, QNetworkReader reader);
protected enum UNetInvokeType
{
Command,
ClientRpc,
SyncEvent,
SyncList
}
protected class Invoker
{
public string DebugString() =>
$"{invokeType}:{invokeClass}:{invokeFunction.GetMethodName()}";
public UNetInvokeType invokeType;
public Type invokeClass;
public CmdDelegate invokeFunction;
}
}
internal static class DotNetCompatibility
{
internal static string GetMethodName(this Delegate func) => func.Method.Name;
internal static Type GetBaseType(this Type type) => type.BaseType;
internal static string GetErrorCode(this SocketException e) => e.ErrorCode.ToString();
}
}