diff --git a/BITKit.csproj b/BITKit.csproj index 56abe9b..1743760 100644 --- a/BITKit.csproj +++ b/BITKit.csproj @@ -39,6 +39,8 @@ + + diff --git a/Src/Core/Binary/BITBinary.cs b/Src/Core/Binary/BITBinary.cs index 5997070..fec3264 100644 --- a/Src/Core/Binary/BITBinary.cs +++ b/Src/Core/Binary/BITBinary.cs @@ -73,7 +73,8 @@ namespace BITKit if(string.IsNullOrEmpty(arrayTypeName) || arrayType is null) throw new Exception($"未找到类型:{arrayTypeName}"); //创建数组实例 - var array = Array.CreateInstance(arrayType, reader.ReadInt32()); + var arrayLength = reader.ReadInt32(); + var array = Array.CreateInstance(arrayType, arrayLength); //循环Reader for (var i = 0; i < array.Length; i++) { diff --git a/Src/Core/Kcp/KCPNet.cs b/Src/Core/Kcp/KCPNet.cs index 21e7182..21bf9c0 100644 --- a/Src/Core/Kcp/KCPNet.cs +++ b/Src/Core/Kcp/KCPNet.cs @@ -7,10 +7,9 @@ namespace BITKit.Net public static readonly KcpConfig Config = new KcpConfig( NoDelay: true, DualMode: false, - Interval: 8, // 1ms so at interval code at least runs. + Interval: 1, // 1ms so at interval code at least runs. Timeout: 8000, CongestionWindow: false - ) { SendWindowSize = Kcp.WND_SND * 1000, diff --git a/Src/Core/Kcp/KcpNetClient.cs b/Src/Core/Kcp/KcpNetClient.cs index 66ca6d3..bda7362 100644 --- a/Src/Core/Kcp/KcpNetClient.cs +++ b/Src/Core/Kcp/KcpNetClient.cs @@ -13,6 +13,7 @@ using System.Net; using System.Numerics; using System.Reflection; using System.Text; +using System.Threading; using BITKit.Net.Examples; using Microsoft.Extensions.Logging; using Newtonsoft.Json; @@ -22,6 +23,7 @@ namespace BITKit.Net { public class KcpNetClient:INetClient,INetProvider { + private readonly NetProviderCommon _common = new(); private readonly ILogger _logger; public KcpNetClient(ILogger logger) { @@ -40,15 +42,24 @@ namespace BITKit.Net Id = x.Id; }); _isConnected.AddListener(ConnectionCallback); + + AddCommandListener(F); + + return; + UniTask F(SimplePing p) + { + p.EndTime = DateTime.Now; + return UniTask.FromResult(p); + } } public event Action OnStartConnect; public event Action OnConnected; public event Action OnDisconnected; public event Action OnConnectedFailed; + public uint TickRate { get; set; } = 8; public bool IsConnected => _isConnected; public bool IsConnecting { get; private set; } - public double RpcTimeOut { get; set; } = 5; public bool AutoReconnect { get; set; } = true; public float2 Traffic { get; private set; } public bool ManualTick { get; set; } @@ -68,25 +79,17 @@ namespace BITKit.Net AutoReset = true }; - private readonly GenericEvent _events = new(); private readonly ValidHandle _isConnected = new(); private bool _userConnected; private int _index = int.MinValue; - private readonly ConcurrentDictionary _p2p = new(); - private readonly ConcurrentDictionary>> _rpc = new(); - private readonly ConcurrentDictionary _rpcMethods = new(); - private readonly ConcurrentDictionary _rpcEvents = new(); - private readonly ConcurrentDictionary _rpcHandles = new(); + private DateTime _lastHeartbeat = DateTime.Now; private DateTime _now = DateTime.Now; private TimeSpan _interval = TimeSpan.FromMilliseconds(100); private string _connectedAddress = "127.0.0.1"; private ushort _connectedPort = 27014; - private readonly byte[] _heartBeat = new byte[] { (byte)NetCommandType.Heartbeat }; - - private async void ConnectionCallback(bool x) { await BITApp.SwitchToMainThread(); @@ -136,6 +139,7 @@ namespace BITKit.Net return false; } _userConnected = true; + //如果address是域名,解析为Ip if (address.Contains(".")) { @@ -170,19 +174,15 @@ namespace BITKit.Net _timer.Start(); _interval = TimeSpan.FromMilliseconds(_timer.Interval); - - HandShake(); - await BITApp.SwitchToMainThread(); - - - for (var i = 0; i < 5; i++) - { - _client.Send(new[] { (byte)NetCommandType.Heartbeat }, KcpChannel.Reliable); - Traffic += new float2(1, 0); + if (_client.connected) + { + HandShake(); + _client.Send(new[] { (byte)NetCommandType.Heartbeat }, KcpChannel.Reliable); + Traffic += new float2(1, 0); + } _client.Tick(); await Task.Delay(100); - } if (_client.connected) { @@ -192,6 +192,7 @@ namespace BITKit.Net _connectedAddress = address; _connectedPort = port; + return _client.connected; } @@ -247,7 +248,7 @@ namespace BITKit.Net if (BITApp.SynchronizationContext is not null) await UniTask.SwitchToSynchronizationContext(BITApp.SynchronizationContext, BITApp.CancellationToken); - _events.Invoke(command.GetType().FullName, command); + _common.Events.Invoke(command.GetType().FullName, command); } catch (OperationCanceledException) { @@ -272,21 +273,24 @@ namespace BITKit.Net case NetCommandType.ReturnToClient: var id = reader.ReadInt32(); - - - try { - if (reader.ReadBoolean()) + if (_common.P2P.TryRemove(id, out var source)) { - var value = BITBinary.Read(reader); - _p2p.TryAdd(id,value); + if (reader.ReadBoolean()) + { + var value = BITBinary.Read(reader); + source.TrySetResult(value); + } + else + { + var message = reader.ReadString(); + source.TrySetException(new Exception(message)); + } } else { - var message = reader.ReadString(); - - _p2p.TryAdd(id,new Exception(message)); + _logger.LogWarning($"ID为{id}的请求未注册回调"); } } catch (Exception e) @@ -314,10 +318,10 @@ namespace BITKit.Net var pars = BITBinary.Read(reader).As(); - if (_rpcMethods.TryGetValue(path, out var methodInfo)) + if (_common.RpcMethods.TryGetValue(path, out var methodInfo)) { var isAwaitable = methodInfo.ReturnType.GetMethod(nameof(Task.GetAwaiter)) != null; - var handle = _rpcHandles[path]; + var handle = _common.RpcHandles[path]; try { @@ -342,9 +346,9 @@ namespace BITKit.Net returnWriter.Write(true); BITBinary.Write(returnWriter, value); } - else if (_rpcEvents.TryGetValue(path, out var eventInfo)) + else if (_common.RpcEvents.TryGetValue(path, out var eventInfo)) { - var handle = _rpcHandles[path]; + var handle = _common.RpcHandles[path]; var fieldInfo = handle.GetType().GetField(eventInfo.Name,ReflectionHelper.Flags)!; var eventDelegate = fieldInfo.GetValue(handle) as MulticastDelegate; @@ -364,7 +368,7 @@ namespace BITKit.Net { var commandObj = BITBinary.Read(reader) .As()[0]; - var func = _rpc[commandObj.GetType()!.FullName!]; + var func = _common.Rpc[commandObj.GetType()!.FullName!]; value = await func.As>>().Invoke(commandObj); } @@ -392,11 +396,11 @@ namespace BITKit.Net { var rpcName = reader.ReadString(); var pars = BITBinary.Read(reader).As(); - if (_rpcMethods.TryGetValue(rpcName, out var methodInfo)) + if (_common.RpcMethods.TryGetValue(rpcName, out var methodInfo)) { try { - methodInfo.Invoke(_rpcHandles[rpcName], pars); + methodInfo.Invoke(_common.RpcHandles[rpcName], pars); } catch (TargetException targetException) { @@ -434,9 +438,9 @@ namespace BITKit.Net } } - else if (_rpcEvents.TryGetValue(rpcName, out var eventInfo)) + else if (_common.RpcEvents.TryGetValue(rpcName, out var eventInfo)) { - var handle = _rpcHandles[rpcName]; + var handle = _common.RpcHandles[rpcName]; var fieldInfo = handle.GetType().GetField(eventInfo.Name,ReflectionHelper.Flags)!; var eventDelegate = fieldInfo.GetValue(handle) as MulticastDelegate; @@ -517,13 +521,13 @@ namespace BITKit.Net _commandQueue.Enqueue(bytes); } - public async UniTask GetFromServer(string path = default,params object[] pars) + public async UniTask GetFromServer(string path = default, params object[] pars) { if (IsConnected is false) { throw new NetOfflineException(); } - //await UniTask.SwitchToThreadPool(); + var id = _index++; var ms = new MemoryStream(); var writer = new BinaryWriter(ms); @@ -538,95 +542,63 @@ namespace BITKit.Net writer.Write(true); writer.Write(path); } - BITBinary.Write(writer,pars); - + + if (pars.Length is 0) + { + pars = new[] { System.Activator.CreateInstance() as object }; + } + + BITBinary.Write(writer, pars); + var bytes = ms.ToArray(); - + await ms.DisposeAsync(); await writer.DisposeAsync(); - - _commandQueue.Enqueue(bytes); - var startTime = _now; - - - while (true) - { - if (IsConnected is false) - { - throw new NetOfflineException(); - } - if ((_now - startTime).TotalSeconds > RpcTimeOut) - { - if (string.IsNullOrEmpty(path)) - { - throw new TimeoutException("请求超时或已断开连接"); - - } - throw new TimeoutException($"请求超时或已断开连接,请求为{path}"); - } - if (_p2p.TryRemove(id, out var value)) - { - await BITApp.SwitchToMainThread(); - if (value is Exception e) - { - throw new InGameException(e.Message); - } - if (UniTask.CompletedTask is T t) - { - return t; - } - return value.As(); - } - await Task.Delay(_interval); + _commandQueue.Enqueue(bytes); + + var source = new UniTaskCompletionSource(); + + _common.P2P.TryAdd(id, source); + + var timeoutCts = new CancellationTokenSource(); + timeoutCts.CancelAfter(5000); // 设置超时时间 + + var value = await source.Task.AttachExternalCancellation(timeoutCts.Token); + + if (value is Exception e) + { + throw new InGameException(e.Message); } + + if (UniTask.CompletedTask is T t) + { + return t; + } + + return value.As(); } - + public UniTask GetFromClient(int id,string path, params object[] pars) { throw new NotImplementedException(); } - public void AddRpcHandle(object rpcHandle) { - var reportBuilder = new StringBuilder(); - - reportBuilder.AppendLine($"正在通过反射注册{rpcHandle.GetType().Name}"); - foreach (var methodInfo in rpcHandle.GetType().GetMethods()) - { - var att = methodInfo.GetCustomAttribute(); - if (att is null) continue; - _rpcMethods.AddOrUpdate(methodInfo.Name, methodInfo, (s, info) => methodInfo); - _rpcHandles.AddOrUpdate(methodInfo.Name, rpcHandle, (s, o) => rpcHandle); - - reportBuilder.AppendLine($"Add [{methodInfo.Name}] as MethodInfo"); - } - - foreach (var eventInfo in rpcHandle.GetType().GetEvents()) - { - var att = eventInfo.GetCustomAttribute(); - if (att is null) continue; - - _rpcEvents.TryAdd(eventInfo.Name, eventInfo); - _rpcHandles.TryAdd(eventInfo.Name, rpcHandle); - - reportBuilder.AppendLine($"Add [{eventInfo.Name}] as EventInfo"); - } - - _logger.LogInformation(reportBuilder.ToString()); + _common.AddRpcHandle(rpcHandle); } public void AddCommandListener(Action handle) { - _events.AddListener(handle); + _common. Events.AddListener(handle); } public void AddCommandListener(Func> func) { - _rpc.TryAdd(typeof(T).FullName, F); + _common. Rpc.TryAdd(typeof(T).FullName, F); return; async UniTask F(object o) @@ -637,12 +609,12 @@ namespace BITKit.Net public void RemoveCommandListener(Func> func) { - _rpc.TryRemove(typeof(T).FullName, out _); + _common. Rpc.TryRemove(typeof(T).FullName, out _); } public void RemoveCommandListener(Action handle) { - _events.RemoveListener(handle); + _common. Events.RemoveListener(handle); } public void SendRT(string rpcName, params object[] pars) @@ -709,7 +681,7 @@ namespace BITKit.Net _client.Send(bytes, KcpChannel.Reliable); } Traffic+=new float2(1,0); - _client.Send(_heartBeat, KcpChannel.Unreliable); + _client.Send(_common.HeartBeat, KcpChannel.Unreliable); } else { diff --git a/Src/Core/Kcp/KcpNetServer.cs b/Src/Core/Kcp/KcpNetServer.cs index 246938c..795af41 100644 --- a/Src/Core/Kcp/KcpNetServer.cs +++ b/Src/Core/Kcp/KcpNetServer.cs @@ -11,23 +11,27 @@ using System.Linq; using System.Net; using System.Net.Sockets; using System.Reflection; +using System.Threading; using System.Threading.Tasks; using System.Windows.Markup; using BITKit.Net.Examples; +using Microsoft.Extensions.Logging; namespace BITKit.Net { public class KCPNetServer:INetServer,INetProvider { + private readonly NetProviderCommon _common = new(); + private readonly ILogger _logger; public string Name { get; set; } = "Default"; - public int TickRate { get; set; } = 16; + public uint TickRate { get; set; } = 8; public bool ManualTick { get; set; } public event Action OnClientConnected; public event Action OnClientDisconnected; public event Action OnStartServer; public event Action OnStopServer; private readonly KcpServer server; - private readonly GenericEvent _events = new(); + private bool _isStarted; private readonly Timer _timer = new(100) { @@ -35,25 +39,14 @@ namespace BITKit.Net }; private int _index = 1001; - private readonly ConcurrentDictionary _p2p = new(); - private readonly ConcurrentDictionary>> _rpc = new(); - private readonly ConcurrentDictionary _rpcMethods = new(); - private readonly ConcurrentDictionary _rpcEvents = new(); - private readonly ConcurrentDictionary _rpcHandles = new(); - private readonly ConcurrentDictionary _lastHeartbeat = new(); - private readonly ConcurrentQueue<(int id,byte[] bytes)> _sendQueue = new(); - private readonly ConcurrentDictionary _dropCount = new(); - - private DateTime _now=DateTime.Now; - private TimeSpan _interval=TimeSpan.FromSeconds(0.32); - - private readonly byte[] _heartBeat = new byte[] { (byte)NetCommandType.Heartbeat }; + private DateTime _now=DateTime.UtcNow; [NetRpc] public event Action OnNetRpcTest; - public KCPNetServer() + public KCPNetServer(ILogger logger) { + _logger = logger; server = new KcpServer( OnConnected, OnData, @@ -70,7 +63,7 @@ namespace BITKit.Net OnNetRpcTest += (_int, _float, _bool) => { - BIT4Log.Log($"已收到Rpc测试:{_int},{_float},{_bool}"); + _logger.LogInformation($"已收到Rpc测试:{_int},{_float},{_bool}"); }; return; @@ -93,23 +86,18 @@ namespace BITKit.Net foreach (var id in Connections.Keys.ToArray()) { - server.Send(id,_heartBeat , KcpChannel.Unreliable); - if (!_lastHeartbeat.TryGetValue(id, out var time)) continue; + server.Send(id,_common.HeartBeat , KcpChannel.Unreliable); + if (!_common.LastHeartbeat.TryGetValue(id, out var time)) continue; if (!((_now - time).TotalSeconds > 3)) continue; server.Disconnect(id); - _lastHeartbeat.TryRemove(id); - BIT4Log.Log($"{Name}:链接{id}超时,已断开"); + _common.LastHeartbeat.TryRemove(id); + _logger.LogInformation($"{Name}:链接{id}超时,已断开"); } if (server.IsActive() is false) return; - - server.Tick(); - - //BIT4Log.Log($"{Name}目前有{server.connections.Count}个链接"); - - _dropCount.Clear(); - while (_sendQueue.TryDequeue(out var value)) + _common.DropCount.Clear(); + while (_common.SendQueue.TryDequeue(out var value)) { if (server.connections.ContainsKey(value.id)) @@ -120,13 +108,15 @@ namespace BITKit.Net { int UpdateValueFactory(int i, int i1) => i1 + value.bytes.Length; - _dropCount.AddOrUpdate(value.id,value.bytes.Length,UpdateValueFactory); + _common.DropCount.AddOrUpdate(value.id,value.bytes.Length,UpdateValueFactory); } } - foreach (var (id,length) in _dropCount) + foreach (var (id,length) in _common.DropCount) { - BIT4Log.Log($"未找到链接:{id},已丢弃字节数量:{length}"); + _logger.LogInformation($"未找到链接:{id},已丢弃字节数量:{length}"); } + + server.Tick(); } catch (SocketException) { @@ -134,7 +124,7 @@ namespace BITKit.Net } catch (Exception exception) { - BIT4Log.LogException(exception); + _logger.LogCritical(exception,exception.Message); } } @@ -148,7 +138,6 @@ namespace BITKit.Net if (TickRate > 0) { _timer.Interval = 1000f / TickRate; - _interval = TimeSpan.FromSeconds(1.0 / TickRate); } OnStartServer?.Invoke(); @@ -156,7 +145,7 @@ namespace BITKit.Net if (ManualTick is false) _timer.Start(); _isStarted = true; - BIT4Log.Log($"已启动KCP服务器:{port}"); + _logger.LogInformation($"已启动KCP服务器:{port}"); } else { @@ -172,7 +161,7 @@ namespace BITKit.Net server.Stop(); OnStopServer?.Invoke(); _timer.Stop(); - BIT4Log.Log($"已停止KCP服务器"); + _logger.LogInformation($"已停止KCP服务器"); } else { @@ -186,7 +175,7 @@ namespace BITKit.Net using var writer = new BinaryWriter(ms); writer.Write((byte)NetCommandType.Message); writer.Write(message); - _sendQueue.Enqueue((id,ms.ToArray())); + _common.SendQueue.Enqueue((id,ms.ToArray())); } public void SendMessageToAll(string message) @@ -211,7 +200,7 @@ namespace BITKit.Net } catch (SocketException) { - BIT4Log.Log("有用户断开连接,如有异常请检查"); + _logger.LogInformation("有用户断开连接,如有异常请检查"); } } @@ -225,30 +214,23 @@ namespace BITKit.Net private void OnConnectedInternel(int id) { + //server.connections[id].peer.kcp.SetInterval(TickRate); OnClientConnected?.Invoke(id); ClientCommand(id,new NetClientAllocateIdCommand { Id = id, Ip = server.connections[id].remoteEndPoint.ToString() }); - BIT4Log.Log($"{id}已连接到:{Name}"); + _logger.LogInformation($"{id}已连接到:{Name}"); SendMessageToClient(id, $"成功连接到服务器:{Name}"); } private void OnConnected(int Id) { - // OnClientConnected?.Invoke(Id); - // ClientCommand(Id,new NetClientAllocateIdCommand - // { - // Id = Id, - // Ip = server.connections[Id].remoteEndPoint.ToString() - // }); - // BIT4Log.Log($"{Id}已连接到:{Name}"); - // SendMessageToClient(Id, $"成功连接到服务器:{Name}"); } private void OnDisconnect(int Id) { OnClientDisconnected?.Invoke(Id); - BIT4Log.Log($"{Id}已断开"); + _logger.LogInformation($"{Id}已断开"); } private void OnData(int Id, ArraySegment bytes, KcpChannel channel) @@ -275,44 +257,44 @@ namespace BITKit.Net switch (type) { case NetCommandType.Message: - BIT4Log.Log($"已收到消息,{Id}:{reader.ReadString()}"); + _logger.LogInformation($"已收到消息,{Id}:{reader.ReadString()}"); break; case NetCommandType.Command: var command = BITBinary.Read(reader); if (command is object[] { Length: 1 } objs) command = objs[0]; //BIT4Log.Log($"已收到指令:{command},值:\n{JsonConvert.SerializeObject(command, Formatting.Indented)}"); - _events.Invoke(command.GetType().FullName, command); + _common.Events.Invoke(command.GetType().FullName, command); (int Id, object Command) tuple = (Id, command); - _events.InvokeDirect(command.GetType().FullName, tuple); + _common.Events.InvokeDirect(command.GetType().FullName, tuple); break; case NetCommandType.Heartbeat: if (Connections.ContainsKey(Id)) { - _lastHeartbeat.AddOrUpdate(Id,OnAdd,OnUpdate); + if (_common.LastHeartbeat.ContainsKey(Id)) + { + _common.LastHeartbeat[Id] = _now; + } + else + { + _common.LastHeartbeat.TryAdd(Id, _now); + OnConnectedInternel(Id); + } } + break; - DateTime OnAdd(int arg) - { - OnConnectedInternel(Id); - return _now; - } - DateTime OnUpdate(int arg1, DateTime arg2) - { - return _now; - } case NetCommandType.AllClientCommand: foreach (var id in server.connections.Keys.ToArray()) { - _sendQueue.Enqueue((id,bytes.ToArray())); + _common.SendQueue.Enqueue((id,bytes.ToArray())); } break; case NetCommandType.TargetCommand: var targetId = reader.ReadInt32(); - _sendQueue.Enqueue((targetId,bytes.ToArray())); + _common.SendQueue.Enqueue((targetId,bytes.ToArray())); break; case NetCommandType.Ping: - _sendQueue.Enqueue((Id,new byte[] { (byte)NetCommandType.Ping })); + _common.SendQueue.Enqueue((Id,new byte[] { (byte)NetCommandType.Ping })); break; case NetCommandType.GetFromServer: { @@ -329,10 +311,10 @@ namespace BITKit.Net var pars = BITBinary.Read(reader).As(); object value = null; - if (_rpcMethods.TryGetValue(path, out var methodInfo)) + if (_common.RpcMethods.TryGetValue(path, out var methodInfo)) { var isAwaitable = methodInfo.ReturnType.GetMethod(nameof(Task.GetAwaiter)) != null; - var handle = _rpcHandles[path]; + var handle = _common.RpcHandles[path]; if (methodInfo.GetParameters().Length is 0) { @@ -374,7 +356,7 @@ namespace BITKit.Net returnWriter.Write(e.Message); var _bytes = returnMS.ToArray(); - _sendQueue.Enqueue((Id,_bytes)); + _common.SendQueue.Enqueue((Id,_bytes)); if (e is InGameException inGameException) { @@ -386,9 +368,9 @@ namespace BITKit.Net } returnWriter.Write(true); - }else if (_rpcEvents.TryGetValue(path, out var eventInfo)) + }else if (_common.RpcEvents.TryGetValue(path, out var eventInfo)) { - var handle = _rpcHandles[path]; + var handle = _common.RpcHandles[path]; var fieldInfo = handle.GetType().GetField(eventInfo.Name,ReflectionHelper.Flags)!; var eventDelegate = fieldInfo.GetValue(handle) as MulticastDelegate; @@ -409,10 +391,11 @@ namespace BITKit.Net } else { - var commandObj = BITBinary.Read(reader) - .As()[0]; + var commandObjs = BITBinary.Read(reader); + + var commandObj = commandObjs.As()[0]; var funcName = commandObj.GetType()!.FullName!; - if (_rpc.TryGetValue(funcName, out var func)) + if (_common.Rpc.TryGetValue(funcName, out var func)) { var value = await func.As>>().Invoke(commandObj); returnWriter.Write(true); @@ -425,8 +408,7 @@ namespace BITKit.Net } { - var _bytes = returnMS.ToArray(); - _sendQueue.Enqueue((Id,_bytes)); + _common.SendQueue.Enqueue((Id,returnMS.ToArray())); } } @@ -434,21 +416,31 @@ namespace BITKit.Net case NetCommandType.ReturnToServer: { var id = reader.ReadInt32(); - if (reader.ReadBoolean()) + + if (_common.P2P.TryRemove(id, out var source)) { - var value = BITBinary.Read(reader); - _p2p.TryAdd(id, value); + if (reader.ReadBoolean()) + { + var value = BITBinary.Read(reader); + source.TrySetResult(value); + } + else + { + var message = reader.ReadString(); + source.TrySetException(new Exception(message)); + } } else { - var message = reader.ReadString(); - _p2p.TryAdd(id, new Exception(message)); + _logger.LogWarning($"ID为{id}的请求未注册回调"); } + + } break; default: - BIT4Log.Log($"未知消息类型:{type},字节:{(byte)type}"); - BIT4Log.Log( + _logger.LogInformation($"未知消息类型:{type},字节:{(byte)type}"); + _logger.LogInformation( $"已收到:({Id}, {BitConverter.ToString(bytes.Array, bytes.Offset, bytes.Count)} @ {channel})"); break; } @@ -466,12 +458,12 @@ namespace BITKit.Net private void OnError(int Id, ErrorCode errorCode, string message) { - BIT4Log.Log($"异常:{errorCode},{message}"); + _logger.LogInformation($"异常:{errorCode},{message}"); } public void ServerCommand(T command = default) { - _events.Invoke(command); + _common. Events.Invoke(command); } public void AllClientCommand(T command = default) @@ -488,7 +480,7 @@ namespace BITKit.Net using var writer = new BinaryWriter(ms); writer.Write((byte)NetCommandType.Command); BITBinary.Write(writer,command); - _sendQueue.Enqueue((id,ms.ToArray())); + _common.SendQueue.Enqueue((id,ms.ToArray())); } public UniTask GetFromServer(string path=default,params object[] pars) @@ -520,16 +512,19 @@ namespace BITKit.Net await ms.DisposeAsync(); await writer.DisposeAsync(); - _sendQueue.Enqueue((id,bytes)); - var startTime = _now; - while (true) - { - var time = _now - startTime; - if(time.TotalSeconds>5) - throw new TimeoutException($"等待超时,Id:{id},时间{time.TotalSeconds}"); + _common.SendQueue.Enqueue((id,bytes)); - if (_p2p.TryRemove(index, out var value)) - { + var source = new UniTaskCompletionSource(); + + + _common.P2P.TryAdd(index, source); + + var timeoutCts = new CancellationTokenSource(); + timeoutCts.CancelAfter(5000); // 设置超时时间 + + + var value =await source.Task.AttachExternalCancellation(timeoutCts.Token); + //await BITApp.SwitchToMainThread(); if (value is Exception e) { @@ -544,9 +539,6 @@ namespace BITKit.Net return default; } return (T)value; - } - await Task.Delay(_interval); - } } public void AddRpcHandle(object rpcHandle) @@ -555,16 +547,16 @@ namespace BITKit.Net { var att = methodInfo.GetCustomAttribute(true); if(att is null)continue; - _rpcMethods.TryAdd(methodInfo.Name, methodInfo); - _rpcHandles.TryAdd(methodInfo.Name, rpcHandle); + _common.RpcMethods.TryAdd(methodInfo.Name, methodInfo); + _common.RpcHandles.TryAdd(methodInfo.Name, rpcHandle); } foreach (var eventInfo in rpcHandle.GetType().GetEvents()) { var att = eventInfo.GetCustomAttribute(true); if(att is null)continue; - _rpcEvents.TryAdd(eventInfo.Name, eventInfo); - _rpcHandles.TryAdd(eventInfo.Name, rpcHandle); + _common. RpcEvents.TryAdd(eventInfo.Name, eventInfo); + _common. RpcHandles.TryAdd(eventInfo.Name, rpcHandle); } } @@ -582,12 +574,12 @@ namespace BITKit.Net public void AddCommandListener(Action handle) { - _events.AddListener(handle); + _common.Events.AddListener(handle); } public void AddCommandListener(Func> func) { - _rpc.TryAdd(typeof(T).FullName, F); + _common.Rpc.TryAdd(typeof(T).FullName, F); return; async UniTask F(object o) @@ -597,11 +589,11 @@ namespace BITKit.Net } public void RemoveCommandListener(Func> func) { - _rpc.TryRemove(typeof(T).FullName, out _); + _common. Rpc.TryRemove(typeof(T).FullName, out _); } public void AddCommandListenerWithId(Action handle) { - _events.AddListenerDirect(typeof(T).FullName, Callback); + _common.Events.AddListenerDirect(typeof(T).FullName, Callback); return; void Callback(object value) @@ -624,7 +616,7 @@ namespace BITKit.Net public void RemoveCommandListener(Action handle) { - _events.RemoveListener(handle); + _common. Events.RemoveListener(handle); } public void RemoveCommandListener(Func func) @@ -645,7 +637,7 @@ namespace BITKit.Net writer.Write(rpcName); BITBinary.Write(writer,pars); var bytes = ms.ToArray(); - _sendQueue.Enqueue((id,bytes)); + _common.SendQueue.Enqueue((id,bytes)); } public void SendAllRT(string rpcName, params object[] pars) @@ -658,7 +650,7 @@ namespace BITKit.Net var bytes = ms.ToArray(); foreach (var id in server.connections.Keys) { - _sendQueue.Enqueue((id,bytes)); + _common.SendQueue.Enqueue((id,bytes)); } } } diff --git a/Src/Core/Mod/ModService.cs b/Src/Core/Mod/ModService.cs index 7aa0f43..e700e1e 100644 --- a/Src/Core/Mod/ModService.cs +++ b/Src/Core/Mod/ModService.cs @@ -225,7 +225,6 @@ namespace BITKit.Mod await Load(assembly); continue; } -#if UNITY_5_3_OR_NEWER case ".cs": { var code = await File.ReadAllTextAsync(fileInfo.FullName); @@ -233,7 +232,6 @@ namespace BITKit.Mod await Load(assembly, fileInfo.DirectoryName); continue; } -#endif } } diff --git a/Src/Core/Net/Commands/NetClientPing.cs b/Src/Core/Net/Commands/NetClientPing.cs index 677537a..9ca1b9f 100644 --- a/Src/Core/Net/Commands/NetClientPing.cs +++ b/Src/Core/Net/Commands/NetClientPing.cs @@ -1,6 +1,9 @@ +using MemoryPack; + namespace BITKit.Net.Examples { - public struct NetClientPingCommand + [MemoryPackable] + public partial struct NetClientPingCommand { public int Ping; } diff --git a/Src/Core/Net/Commands/NetCommandExamples.cs b/Src/Core/Net/Commands/NetCommandExamples.cs index 19327f5..e2a5ee5 100644 --- a/Src/Core/Net/Commands/NetCommandExamples.cs +++ b/Src/Core/Net/Commands/NetCommandExamples.cs @@ -1,9 +1,13 @@ using System; using System.Data; +using System.Runtime.InteropServices; +using MemoryPack; namespace BITKit.Net.Examples { - public struct LogTime + [MemoryPackable] + [StructLayout(LayoutKind.Auto)] + public partial struct LogTime { public Guid Id; public DateTime CreateTime; @@ -15,7 +19,9 @@ namespace BITKit.Net.Examples } #endif } - public struct SimplePing + [MemoryPackable] + [StructLayout(LayoutKind.Auto)] + public partial struct SimplePing { public DateTime StartTime; public DateTime EndTime; diff --git a/Src/Core/Net/NetProvider.cs b/Src/Core/Net/NetProvider.cs index 2524e2b..8c2202a 100644 --- a/Src/Core/Net/NetProvider.cs +++ b/Src/Core/Net/NetProvider.cs @@ -6,8 +6,11 @@ */ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Net; +using System.Reflection; +using System.Text; using Cysharp.Threading.Tasks; #if UNITY using UnityEngine; @@ -91,6 +94,7 @@ namespace BITKit /// public interface INetProvider { + public uint TickRate { get; set; } /// /// 向服务端发送指令 /// @@ -324,7 +328,7 @@ namespace BITKit /// 服务端地址 /// 端口 /// - UniTask Connect(string address = "localhost", ushort port = 27014); + UniTask Connect(string address = "127.0.0.1", ushort port = 27014); /// /// 向服务端发送消息 @@ -332,4 +336,48 @@ namespace BITKit /// 消息 void SendServerMessage(string message); } + + public class NetProviderCommon + { + public readonly GenericEvent Events = new(); + + public readonly ConcurrentDictionary> P2P = new(); + public readonly ConcurrentDictionary>> Rpc = new(); + public readonly ConcurrentDictionary RpcMethods = new(); + public readonly ConcurrentDictionary RpcEvents = new(); + public readonly ConcurrentDictionary RpcHandles = new(); + public readonly ConcurrentDictionary LastHeartbeat = new(); + + public readonly ConcurrentQueue<(int id,byte[] bytes)> SendQueue = new(); + public readonly ConcurrentDictionary DropCount = new(); + + public readonly byte[] HeartBeat = new byte[] { (byte)NetCommandType.Heartbeat }; + + public void AddRpcHandle(object rpcHandle) + { + var reportBuilder = new StringBuilder(); + + reportBuilder.AppendLine($"正在通过反射注册{rpcHandle.GetType().Name}"); + foreach (var methodInfo in rpcHandle.GetType().GetMethods()) + { + var att = methodInfo.GetCustomAttribute(); + if (att is null) continue; + RpcMethods.AddOrUpdate(methodInfo.Name, methodInfo, (s, info) => methodInfo); + RpcHandles.AddOrUpdate(methodInfo.Name, rpcHandle, (s, o) => rpcHandle); + + reportBuilder.AppendLine($"Add [{methodInfo.Name}] as MethodInfo"); + } + + foreach (var eventInfo in rpcHandle.GetType().GetEvents()) + { + var att = eventInfo.GetCustomAttribute(); + if (att is null) continue; + + RpcEvents.TryAdd(eventInfo.Name, eventInfo); + RpcHandles.TryAdd(eventInfo.Name, rpcHandle); + + reportBuilder.AppendLine($"Add [{eventInfo.Name}] as EventInfo"); + } + } + } } diff --git a/Src/Core/Sharp/BITSharp.cs b/Src/Core/Sharp/BITSharp.cs index 3161fa2..0c5df96 100644 --- a/Src/Core/Sharp/BITSharp.cs +++ b/Src/Core/Sharp/BITSharp.cs @@ -5,17 +5,18 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; +using System.Numerics; using System.Reflection; using System.Runtime.InteropServices; using System.Text; using System.Threading; using BITKit.Mod; - +using Microsoft.CodeAnalysis.CSharp.Scripting; +using Microsoft.CodeAnalysis.Emit; +using Microsoft.CodeAnalysis.Scripting; #if UNITY_5_3_OR_NEWER using System.Collections; -using BITKit.UX; using UnityEngine; -using UnityEngine.Rendering; using static BITKit.Mod.DotNetSdkRoslynService; #endif #if UNITY_EDITOR @@ -60,170 +61,162 @@ namespace BITKit assemblies ??= AppDomain.CurrentDomain.GetAssemblies(); return assemblies.Select(assembly => assembly.GetType(fullName, false)).FirstOrDefault(type => type is not null); } -#if UNITY_5_3_OR_NEWER - public static Assembly Compile(params string[] codes) + public static IReadOnlyCollection GetReferencedAssemblies(Type type) { - var outputPath = PathHelper.GetTempFilePath(); - - try + var result = new HashSet(); + foreach (var fieldInfo in type.GetFields()) { - if (Installed is false) - { - throw new FileNotFoundException($"未从当前路径找到Roslyn:\n{CSCPath}"); - } - - var argumentBuilder = new StringBuilder(); - - var tempList = new List { outputPath }; - - foreach (var code in codes) - { - var temp = PathHelper.GetTempFilePath(); - - File.WriteAllText(temp,code); - - argumentBuilder.Append(" "); - argumentBuilder.Append(temp); - - tempList.Add(temp); - } - - argumentBuilder.Append(" -target:library"); - - argumentBuilder.Append(" /unsafe"); - - foreach (var x in ReferencedAssemblies) - { - argumentBuilder.Append(" "); - argumentBuilder.Append("-r:"); - argumentBuilder.Append(x); - //argumentBuilder.Append(Path.Combine(dllPath, x)); - } - - argumentBuilder.Append(" "); - argumentBuilder.Append($"-out:{outputPath}"); - - BIT4Log.Log("已创建编译参数:"); - BIT4Log.Log(argumentBuilder.ToString()); - - BIT4Log.Log($"dotnet {CSCPath} {argumentBuilder}"); - - //Original - // var StartInfo = new ProcessStartInfo - // { - // //FileName = MCSPath, - // FileName = "dotnet", - // Arguments =$"{CSCPath} {argumentBuilder}" , - // UseShellExecute = true, - // CreateNoWindow = false, - // }; - - var startInfo = new ProcessStartInfo - { - //FileName = MCSPath, - FileName = "dotnet", - Arguments =$"{CSCPath} {argumentBuilder}" , - UseShellExecute = false, - CreateNoWindow = true, - RedirectStandardOutput = true, - RedirectStandardError = true, - StandardErrorEncoding = System.Text.Encoding.GetEncoding("gb2312"), - StandardInputEncoding = System.Text.Encoding.GetEncoding("gb2312"), - StandardOutputEncoding = System.Text.Encoding.GetEncoding("gb2312"), - }; - - - var process = new Process() - { - StartInfo = startInfo, - }; - var reportBuilder = new StringBuilder(); - process.OutputDataReceived += (sender, args) => - { - //BIT4Log.Log(args.Data); - reportBuilder.AppendLine(args.Data); - }; - process.ErrorDataReceived += (sender, args) => - { - //BIT4Log.Warning(args.Data); - reportBuilder.AppendLine($"{args.Data}"); - }; - - process.Start(); - BIT4Log.Log("已启动"); - - process.BeginErrorReadLine(); - process.BeginOutputReadLine(); + result.Add(fieldInfo.FieldType.Assembly); + } + + foreach (var propertyInfo in type.GetProperties()) + { + result.Add(propertyInfo.PropertyType.Assembly); + } + foreach (var methodInfo in type.GetMethods()) + { + result.Add(methodInfo.ReturnType.Assembly); + foreach (var argument in methodInfo.GetGenericArguments()) + { + result.Add(argument.Assembly); + } + } + return result; + } + + public static string GenerateCodeFromInterface(Type type,string namespaceName = null) + { + if (type.IsInterface is false) throw new InvalidDataException("The type have to be interface"); + + var codeBuilder = new StringBuilder(); + + var nameSpaces = new HashSet(); + + HashSet allTypes = new(); + HashSet propertyMethods = new(); + + foreach (var propertyInfo in type.GetProperties()) + { + if (propertyInfo.GetMethod is not null) + { + propertyMethods.Add(propertyInfo.GetMethod); + } + + if (propertyInfo.SetMethod is not null) + { + propertyMethods.Add(propertyInfo.SetMethod); + } + } + + foreach (var method in type.GetMethods()) + { + if (method.DeclaringType is not null) + { + nameSpaces.Add(method.DeclaringType.Namespace); + } + nameSpaces.Add(method.ReturnType.Namespace); + foreach (var argument in method.GetGenericArguments()) + { + nameSpaces.Add(argument.Namespace); + } + } + + nameSpaces.Add("System"); + nameSpaces.Add("System.Collections.Concurrent"); + nameSpaces.Add("System.Collections.Generic"); + - while (process.HasExited is false) - { - Thread.Sleep(100); - if (BITApp.CancellationToken.IsCancellationRequested) throw new OperationCanceledException("程序已退出,取消编译"); - } - - var bytes = File.ReadAllBytes(outputPath); - if(process.ExitCode is not 0) + codeBuilder.AppendLine(string.Join('\n', nameSpaces.Select(x=>$"using {x};"))); + + + //codeBuilder.AppendLine("namespace BITSharpGen"); + //codeBuilder.AppendLine("{"); + + codeBuilder.AppendLine($"public class @{type.Name}Gen : {type.Name}"); + codeBuilder.AppendLine("{"); + + + foreach (var propertyInfo in type.GetProperties()) + { + codeBuilder.AppendLine($"public {CSharpName(propertyInfo.PropertyType)} {propertyInfo.Name}" + "{ get;set; }"); + } + + // 遍历接口的所有成员,生成方法的默认实现 + foreach (var method in type.GetMethods()) + { + if(propertyMethods.Contains(method))continue; + string methodName = method.Name; + string parameters = string.Join(", ", method.GetParameters() + .Select(p => $"{CSharpName(p.ParameterType)} {p.Name}")); + + // 检查是否为泛型方法 + if (method.IsGenericMethod) + { + // 处理泛型方法 + var genericArgs = string.Join(", ", method.GetGenericArguments() + .Select(arg => CSharpName(arg))); + + if (method.ReturnType == typeof(void)) { - BIT4Log.LogException(new Exception($"编译失败:{process.ExitCode}")); + codeBuilder.AppendLine($" public void {methodName}<{genericArgs}>({parameters}) {{ }}"); } else { - foreach (var x in tempList) - { - File.Delete(x); - } + codeBuilder.AppendLine($" public {CSharpName(method.ReturnType)} {methodName}<{genericArgs}>({parameters}) {{ return default({CSharpName(method.ReturnType)}); }}"); } - BIT4Log.Log(reportBuilder); - - return Assembly.Load(bytes); + } + else + { + if (method.ReturnType == typeof(void)) + { + codeBuilder.AppendLine($" public void {methodName}({parameters}) {{ }}"); // Void 方法默认空实现 + } + else + { + // 返回默认值 + codeBuilder.AppendLine($" public {CSharpName(method.ReturnType)} {methodName}({parameters}) {{ return default({CSharpName(method.ReturnType)}); }}"); + } + + } } - catch (Exception e) - { - throw new Exception($"编译失败:{e}"); - } - - // var codeProvider = new CSharpCodeProvider(); - // var parameters = new CompilerParameters - // { - // GenerateInMemory = true, - // CompilerOptions = "/unsafe", - // OutputAssembly = outputPath, - // }; - // parameters.ReferencedAssemblies.Add("System.dll"); - // foreach (var x in ReferencedAssemblies) - // { - // parameters.ReferencedAssemblies.Add(x); - // BIT4Log.Log($"添加引用:"); - // BIT4Log.Log(x); - // } - // - // BIT4Log.Log($"程序集输出路径:{outputPath}"); - // - // var results = codeProvider.CompileAssemblyFromSource(parameters, codes); - // - // if (results.Errors.Count <= 0) - // { - // BIT4Log.Log($"编译成功:{results.CompiledAssembly.FullName}"); - // - // codeProvider.Dispose(); - // return results.CompiledAssembly; - // } - // - // foreach (CompilerError error in results.Errors) - // { - // var sb = new StringBuilder(); - // sb.AppendLine(error.FileName); - // sb.AppendLine($"Error ({error.ErrorNumber}): {error.ErrorText}"); - // sb.AppendLine($"Line: {error.Line}, Column: {error.Column}"); - // sb.AppendLine($"Is Warning: {error.IsWarning}"); - // BIT4Log.LogException(new Exception(sb.ToString())); - // } - // - // throw new Exception("编译失败"); + + + //codeBuilder.AppendLine("\t}"); + + codeBuilder.AppendLine("}"); + + return codeBuilder.ToString(); + } + + public static string CSharpName(Type type) + { + var sb = new StringBuilder(); + var name = type.Name; + if (!type.IsGenericType) return name; + sb.Append(name.Substring(0, name.IndexOf('`'))); + sb.Append("<"); + sb.Append(string.Join(", ", type.GetGenericArguments() + .Select(t => CSharpName(t)))); + sb.Append(">"); + return sb.ToString(); + } + + public static Assembly Compile(string code,ScriptOptions scriptOptions=null) + { + scriptOptions ??= ScriptOptions.Default; + + var script = CSharpScript.Create(code,scriptOptions); + script.Compile(); + + using var ms = new MemoryStream(); + var result = script.GetCompilation().Emit(ms); + ms.Seek(0, SeekOrigin.Begin); + var assembly = Assembly.Load(ms.ToArray()); // 加载程序集 + + return assembly; } -#endif } } \ No newline at end of file diff --git a/Src/Core/Ticker/TimerTicker.cs b/Src/Core/Ticker/TimerTicker.cs index 31583d9..faf2d26 100644 --- a/Src/Core/Ticker/TimerTicker.cs +++ b/Src/Core/Ticker/TimerTicker.cs @@ -43,11 +43,12 @@ namespace BITKit throw new NotImplementedException(); } - public void Start() + public TimerTicker Start() { _deltaTime = 1f / TickRate; _timer.Interval = TimeSpan.FromSeconds(_deltaTime).TotalMilliseconds; _timer.Enabled = true; + return this; } private void OnTick() { diff --git a/Src/Core/Utility/StopWatcher.cs b/Src/Core/Utility/StopWatcher.cs index 6a634e3..d9b1e6c 100644 --- a/Src/Core/Utility/StopWatcher.cs +++ b/Src/Core/Utility/StopWatcher.cs @@ -9,11 +9,11 @@ namespace BITKit private readonly Stopwatch _stopwatch = new Stopwatch(); private readonly ILogger _logger; private readonly string _message; - public StopWatcher(ILogger logger,string message) + public StopWatcher(ILogger logger,string message=null) { _logger = logger; _message = message; - + _logger.LogInformation($"开始计时:{message}"); _stopwatch.Start(); diff --git a/Src/Core/kcp2k/kcp/Kcp.cs b/Src/Core/kcp2k/kcp/Kcp.cs index 851faaa..3a330eb 100644 --- a/Src/Core/kcp2k/kcp/Kcp.cs +++ b/Src/Core/kcp2k/kcp/Kcp.cs @@ -1058,9 +1058,9 @@ namespace kcp2k // ikcp_interval public void SetInterval(uint interval) { - // clamp interval between 10 and 5000 + // clamp interval between 10 and 5000 if (interval > 5000) interval = 5000; - else if (interval < 10) interval = 10; + else if (interval < 1) interval = 1; this.interval = interval; } @@ -1086,9 +1086,9 @@ namespace kcp2k if (interval >= 0) { - // clamp interval between 10 and 5000 + // clamp interval between 1 and 5000 if (interval > 5000) interval = 5000; - else if (interval < 10) interval = 10; + else if (interval < 1) interval = 1; this.interval = interval; }