This commit is contained in:
parent
4b72602bfa
commit
41715e4413
|
@ -20,10 +20,10 @@ namespace BITKit
|
|||
OnNextLine = null;
|
||||
}
|
||||
#endif
|
||||
public static event Action<string> OnLog;
|
||||
public static event Action<Exception> OnException;
|
||||
public static event Action<string> OnWarning;
|
||||
public static event Action OnNextLine;
|
||||
public static event Action<string> OnLog = Console.WriteLine;
|
||||
public static event Action<Exception> OnException = Console.WriteLine;
|
||||
public static event Action<string> OnWarning = Console.WriteLine;
|
||||
public static event Action OnNextLine=Console.WriteLine;
|
||||
#if UNITY_5_3_OR_NEWER
|
||||
[HideInCallstack]
|
||||
#endif
|
||||
|
|
|
@ -89,6 +89,11 @@ namespace BITKit
|
|||
|
||||
object ReadInternel(BinaryReader reader)
|
||||
{
|
||||
if (reader.ReadBoolean() is false)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var typeName = reader.ReadString();
|
||||
|
||||
if (netReaders.TryGetValue(typeName, out var netReader))
|
||||
|
@ -165,6 +170,11 @@ namespace BITKit
|
|||
return;
|
||||
void WriteInterel(BinaryWriter writer, object value)
|
||||
{
|
||||
if (value is null)
|
||||
{
|
||||
writer.Write(false);
|
||||
return;
|
||||
}
|
||||
var typeName = value.GetType().FullName;
|
||||
writer.Write(typeName!);
|
||||
if (netReaders.TryGetValue(typeName, out var netReader))
|
||||
|
|
|
@ -37,20 +37,7 @@ namespace BITKit.Net
|
|||
);
|
||||
_timer.Elapsed += Tick;
|
||||
_logger.LogInformation("已创建KCP客户端");
|
||||
AddCommandListener<NetClientAllocateIdCommand>(x =>
|
||||
{
|
||||
Id = x.Id;
|
||||
});
|
||||
_isConnected.AddListener(ConnectionCallback);
|
||||
|
||||
AddCommandListener<SimplePing>(F);
|
||||
|
||||
return;
|
||||
UniTask<SimplePing> F(SimplePing p)
|
||||
{
|
||||
p.EndTime = DateTime.Now;
|
||||
return UniTask.FromResult(p);
|
||||
}
|
||||
}
|
||||
|
||||
public event Action OnStartConnect;
|
||||
|
@ -84,7 +71,6 @@ namespace BITKit.Net
|
|||
private bool _userConnected;
|
||||
private int _index = int.MinValue;
|
||||
|
||||
private DateTime _lastHeartbeat = DateTime.Now;
|
||||
private DateTime _now = DateTime.Now;
|
||||
private TimeSpan _interval = TimeSpan.FromMilliseconds(100);
|
||||
private string _connectedAddress = "127.0.0.1";
|
||||
|
@ -168,7 +154,8 @@ namespace BITKit.Net
|
|||
await UniTask.SwitchToThreadPool();
|
||||
try
|
||||
{
|
||||
_lastHeartbeat = DateTime.Now;
|
||||
_common.LastHeartbeat.GetOrCreate(0);
|
||||
_common.LastHeartbeat[0] = DateTime.Now;
|
||||
|
||||
_client.Connect(address, port);
|
||||
|
||||
|
@ -193,6 +180,8 @@ namespace BITKit.Net
|
|||
_connectedAddress = address;
|
||||
_connectedPort = port;
|
||||
|
||||
|
||||
|
||||
return _client.connected;
|
||||
}
|
||||
|
||||
|
@ -220,271 +209,33 @@ namespace BITKit.Net
|
|||
try
|
||||
{
|
||||
Traffic+=new float2(0,bytes.Count);
|
||||
OnDataInternel(bytes, channel);
|
||||
OnDataInternal(bytes, channel);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogCritical(e,e.Message);
|
||||
}
|
||||
}
|
||||
private async void OnDataInternel(ArraySegment<byte> bytes, KcpChannel channel)
|
||||
private void OnDataInternal(ArraySegment<byte> bytes, KcpChannel channel)
|
||||
{
|
||||
using var ms = new MemoryStream(bytes.ToArray());
|
||||
using var reader = new BinaryReader(ms);
|
||||
|
||||
var type = (NetCommandType)ms.ReadByte();
|
||||
switch (type)
|
||||
{
|
||||
case NetCommandType.Message:
|
||||
_logger.LogInformation($"已收到消息:{reader.ReadString()}");
|
||||
break;
|
||||
case NetCommandType.AllClientCommand:
|
||||
case NetCommandType.Command:
|
||||
var command = BITBinary.Read(reader);
|
||||
if (command is object[] { Length: 1 } objs) command = objs[0];
|
||||
//BIT4Log.Log<KcpClient>($"已收到指令:{command},值:\n{JsonConvert.SerializeObject(command, Formatting.Indented)}");
|
||||
try
|
||||
{
|
||||
if (BITApp.SynchronizationContext is not null)
|
||||
await UniTask.SwitchToSynchronizationContext(BITApp.SynchronizationContext,
|
||||
BITApp.CancellationToken);
|
||||
_common.Events.Invoke(command.GetType().FullName, command);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
|
||||
_common.OnDataInternal(0,bytes,channel);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogCritical(e,e.Message);
|
||||
}
|
||||
|
||||
break;
|
||||
case NetCommandType.Heartbeat:
|
||||
if (Id is not -1)
|
||||
{
|
||||
_isConnected.AddElement(this);
|
||||
}
|
||||
_lastHeartbeat = DateTime.Now;
|
||||
break;
|
||||
case NetCommandType.Ping:
|
||||
Ping = (int)(DateTime.Now - _lastPingTime).TotalMilliseconds;
|
||||
break;
|
||||
case NetCommandType.ReturnToClient:
|
||||
|
||||
var id = reader.ReadInt32();
|
||||
try
|
||||
{
|
||||
if (_common.P2P.TryRemove(id, out var source))
|
||||
{
|
||||
if (reader.ReadBoolean())
|
||||
{
|
||||
var value = BITBinary.Read(reader);
|
||||
source.TrySetResult(value);
|
||||
}
|
||||
else
|
||||
{
|
||||
var message = reader.ReadString();
|
||||
source.TrySetException(new Exception(message));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning($"ID为{id}的请求未注册回调");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogWarning($"请求返回失败:{id}");
|
||||
_logger.LogCritical(e,e.Message);
|
||||
}
|
||||
break;
|
||||
case NetCommandType.GetFromClient:
|
||||
try
|
||||
{
|
||||
var requestId = reader.ReadInt32();
|
||||
|
||||
|
||||
using var returnMS = new MemoryStream();
|
||||
await using var returnWriter = new BinaryWriter(returnMS);
|
||||
returnWriter.Write((byte)NetCommandType.ReturnToServer);
|
||||
returnWriter.Write(requestId);
|
||||
try
|
||||
{
|
||||
object value = null;
|
||||
if (reader.ReadBoolean())
|
||||
{
|
||||
var path = reader.ReadString();
|
||||
var pars = BITBinary.Read(reader).As<object[]>();
|
||||
|
||||
|
||||
if (_common.RpcMethods.TryGetValue(path, out var methodInfo))
|
||||
{
|
||||
var isAwaitable = methodInfo.ReturnType.GetMethod(nameof(Task.GetAwaiter)) != null;
|
||||
var handle = _common.RpcHandles[path];
|
||||
|
||||
try
|
||||
{
|
||||
if (isAwaitable)
|
||||
{
|
||||
dynamic result = methodInfo.Invoke(handle, pars)!;
|
||||
|
||||
value = await result;
|
||||
}
|
||||
else
|
||||
{
|
||||
value = methodInfo.Invoke(handle, pars);
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
_logger.LogWarning(path);
|
||||
throw;
|
||||
}
|
||||
|
||||
|
||||
returnWriter.Write(true);
|
||||
BITBinary.Write(returnWriter, value);
|
||||
}
|
||||
else if (_common.RpcEvents.TryGetValue(path, out var eventInfo))
|
||||
{
|
||||
var handle = _common.RpcHandles[path];
|
||||
var fieldInfo = handle.GetType().GetField(eventInfo.Name,ReflectionHelper.Flags)!;
|
||||
|
||||
var eventDelegate = fieldInfo.GetValue(handle) as MulticastDelegate;
|
||||
|
||||
foreach (var del in eventDelegate!.GetInvocationList())
|
||||
{
|
||||
del.Method.Invoke(del.Target, pars);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
returnWriter.Write(false);
|
||||
returnWriter.Write($"未找到对应的Rpc方法:{path}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var commandObj = BITBinary.Read(reader)
|
||||
.As<object[]>()[0];
|
||||
var func = _common.Rpc[commandObj.GetType()!.FullName!];
|
||||
value = await func.As<Func<object, UniTask<object>>>().Invoke(commandObj);
|
||||
}
|
||||
|
||||
returnWriter.Write(true);
|
||||
BITBinary.Write(returnWriter, value);
|
||||
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogCritical(e,e.Message);
|
||||
returnWriter.Write(false);
|
||||
returnWriter.Write(e.Message);
|
||||
}
|
||||
var _bytes = returnMS.ToArray();
|
||||
|
||||
_commandQueue.Enqueue(_bytes);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
_logger.LogCritical(e,e.Message);
|
||||
}
|
||||
break;
|
||||
case NetCommandType.AllRpc:
|
||||
case NetCommandType.TargetRpc:
|
||||
{
|
||||
var rpcName = reader.ReadString();
|
||||
var pars = BITBinary.Read(reader).As<object[]>();
|
||||
if (_common.RpcMethods.TryGetValue(rpcName, out var methodInfo))
|
||||
{
|
||||
try
|
||||
{
|
||||
methodInfo.Invoke(_common.RpcHandles[rpcName], pars);
|
||||
}
|
||||
catch (TargetException targetException)
|
||||
{
|
||||
var reportBuilder = new StringBuilder();
|
||||
|
||||
reportBuilder.AppendLine("正在检查参数类型");
|
||||
|
||||
var parameterInfos = methodInfo.GetParameters();
|
||||
|
||||
if (parameterInfos.Length != pars.Length)
|
||||
{
|
||||
reportBuilder.AppendLine("参数数量不匹配,期望:" + parameterInfos.Length + ",实际:" + pars.Length);
|
||||
}
|
||||
|
||||
for (var i = 0; i < pars.Length; i++)
|
||||
{
|
||||
var parameterInfo = parameterInfos[i];
|
||||
var parameter = pars[i];
|
||||
if (parameterInfo.ParameterType != parameter.GetType())
|
||||
{
|
||||
reportBuilder.AppendLine($"参数{i}类型不匹配,期望:{parameterInfo.ParameterType},实际:{parameter.GetType()}");
|
||||
}
|
||||
else
|
||||
{
|
||||
reportBuilder.AppendLine($"参数{parameter.GetType()}类型匹配:{parameterInfo.ParameterType}");
|
||||
}
|
||||
}
|
||||
_logger.LogWarning(reportBuilder.ToString());
|
||||
throw;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogWarning(rpcName);
|
||||
throw;
|
||||
}
|
||||
|
||||
}
|
||||
else if (_common.RpcEvents.TryGetValue(rpcName, out var eventInfo))
|
||||
{
|
||||
var handle = _common.RpcHandles[rpcName];
|
||||
var fieldInfo = handle.GetType().GetField(eventInfo.Name,ReflectionHelper.Flags)!;
|
||||
|
||||
var eventDelegate = fieldInfo.GetValue(handle) as MulticastDelegate;
|
||||
|
||||
if (eventDelegate is null)
|
||||
{
|
||||
|
||||
//BIT4Log.Warning<KcpNetClient>($"未找到对应的事件:{rpcName}");
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var del in eventDelegate.GetInvocationList())
|
||||
{
|
||||
del.Method.Invoke(del.Target, pars);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
_logger.LogWarning($"未找到对应的Rpc方法:{rpcName}");
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
_logger.LogWarning($"未知消息类型:{type},字节:{(byte)type}");
|
||||
if (bytes.Array != null)
|
||||
_logger.LogInformation(
|
||||
$"已收到:({Id}, {BitConverter.ToString(bytes.Array, bytes.Offset, bytes.Count)} @ {channel})");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnConnectedInternal()
|
||||
{
|
||||
// if (BITApp.SynchronizationContext is not null)
|
||||
// await UniTask.SwitchToSynchronizationContext(BITApp.SynchronizationContext);
|
||||
// OnConnected?.Invoke();
|
||||
// _logger.LogInformation("已连接");
|
||||
|
||||
}
|
||||
|
||||
private async void OnDisconnectInternal()
|
||||
{
|
||||
// _logger.LogInformation("连接被断开");
|
||||
DisconnectInternal();
|
||||
}
|
||||
private void OnError(ErrorCode errorCode, string message)
|
||||
|
@ -492,157 +243,6 @@ namespace BITKit.Net
|
|||
_logger.LogInformation($"{_client.remoteEndPoint}异常:{errorCode},{message}");
|
||||
}
|
||||
|
||||
public void ServerCommand<T>(T command = default)
|
||||
{
|
||||
using var ms = new MemoryStream();
|
||||
using var writer = new BinaryWriter(ms);
|
||||
writer.Write((byte)NetCommandType.Command);
|
||||
BITBinary.Write(writer,command);
|
||||
var bytes = ms.ToArray();
|
||||
_commandQueue.Enqueue(bytes);
|
||||
}
|
||||
|
||||
public void AllClientCommand<T>(T command = default)
|
||||
{
|
||||
using var ms = new MemoryStream();
|
||||
using var writer = new BinaryWriter(ms);
|
||||
writer.Write((byte)NetCommandType.AllClientCommand);
|
||||
BITBinary.Write(writer,command);
|
||||
var bytes = ms.ToArray();
|
||||
}
|
||||
|
||||
public void ClientCommand<T>(int id, T command)
|
||||
{
|
||||
using var ms = new MemoryStream();
|
||||
using var writer = new BinaryWriter(ms);
|
||||
writer.Write((byte)NetCommandType.Command);
|
||||
BITBinary.Write(writer,command);
|
||||
var bytes = ms.ToArray();
|
||||
_commandQueue.Enqueue(bytes);
|
||||
}
|
||||
|
||||
public async UniTask<T> GetFromServer<T>(string path = default, params object[] pars)
|
||||
{
|
||||
if (IsConnected is false)
|
||||
{
|
||||
throw new NetOfflineException();
|
||||
}
|
||||
|
||||
var id = _index++;
|
||||
var ms = new MemoryStream();
|
||||
var writer = new BinaryWriter(ms);
|
||||
writer.Write((byte)NetCommandType.GetFromServer);
|
||||
writer.Write(id);
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
writer.Write(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.Write(true);
|
||||
writer.Write(path);
|
||||
}
|
||||
|
||||
if (pars.Length is 0)
|
||||
{
|
||||
pars = new[] { System.Activator.CreateInstance<T>() as object };
|
||||
}
|
||||
|
||||
BITBinary.Write(writer, pars);
|
||||
|
||||
var bytes = ms.ToArray();
|
||||
|
||||
await ms.DisposeAsync();
|
||||
await writer.DisposeAsync();
|
||||
|
||||
_commandQueue.Enqueue(bytes);
|
||||
|
||||
var source = new UniTaskCompletionSource<object>();
|
||||
|
||||
_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<T>();
|
||||
}
|
||||
|
||||
|
||||
|
||||
public UniTask<T> GetFromClient<T>(int id,string path, params object[] pars)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void AddRpcHandle(object rpcHandle)
|
||||
{
|
||||
_common.AddRpcHandle(rpcHandle);
|
||||
}
|
||||
|
||||
public void AddCommandListener<T>(Action<T> handle)
|
||||
{
|
||||
_common. Events.AddListener<T>(handle);
|
||||
}
|
||||
|
||||
public void AddCommandListener<T>(Func<T,UniTask<T>> func)
|
||||
{
|
||||
_common. Rpc.TryAdd(typeof(T).FullName, F);
|
||||
return;
|
||||
|
||||
async UniTask<object> F(object o)
|
||||
{
|
||||
return await func.Invoke((T)o);
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveCommandListener<T>(Func<T,UniTask<T>> func)
|
||||
{
|
||||
_common. Rpc.TryRemove(typeof(T).FullName, out _);
|
||||
}
|
||||
|
||||
public void RemoveCommandListener<T>(Action<T> handle)
|
||||
{
|
||||
_common. Events.RemoveListener<T>(handle);
|
||||
}
|
||||
|
||||
public void SendRT(string rpcName, params object[] pars)
|
||||
{
|
||||
if (IsConnected is false)
|
||||
{
|
||||
throw new NetOfflineException();
|
||||
}
|
||||
using var ms = new MemoryStream();
|
||||
using var writer = new BinaryWriter(ms);
|
||||
writer.Write((byte)NetCommandType.GetFromServer);
|
||||
writer.Write(++_index);
|
||||
writer.Write(true);
|
||||
writer.Write(rpcName);
|
||||
BITBinary.Write(writer,pars);
|
||||
var bytes = ms.ToArray();
|
||||
_commandQueue.Enqueue(bytes);
|
||||
}
|
||||
|
||||
public void SendTargetRT(int id, string rpcName, params object[] pars)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void SendAllRT(string rpcName, params object[] pars)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void SendServerMessage(string message)
|
||||
{
|
||||
|
@ -655,11 +255,6 @@ namespace BITKit.Net
|
|||
_client.Send(bytes, KcpChannel.Reliable);
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
private readonly IntervalUpdate _pingInterval = new(1);
|
||||
|
||||
#endif
|
||||
|
||||
public void Tick()
|
||||
{
|
||||
_now = DateTime.Now;
|
||||
|
@ -668,7 +263,7 @@ namespace BITKit.Net
|
|||
{
|
||||
if (_client.connected)
|
||||
{
|
||||
if (DateTime.Now - _lastHeartbeat > TimeSpan.FromSeconds(5))
|
||||
if (DateTime.Now - _common.LastHeartbeat.GetOrCreate(0) > TimeSpan.FromSeconds(5))
|
||||
{
|
||||
_logger.LogWarning("心跳超时,自动断开");
|
||||
DisconnectInternal();
|
||||
|
@ -713,5 +308,25 @@ namespace BITKit.Net
|
|||
Traffic+=new float2(2,0);
|
||||
_client.Send(new byte[]{0x01, 0x02}, KcpChannel.Reliable);
|
||||
}
|
||||
public T GetRemoteInterface<T>() => _common.GetRemoteInterface<T>();
|
||||
public void Invoke(Memory<byte> bytes)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public UniTask<Memory<byte>> InvokeAsync(Memory<byte> bytes)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void Invoke(IReadOnlyList<byte> bytes)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public UniTask<IReadOnlyList<byte>> InvokeAsync(IReadOnlyList<byte> bytes)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -42,8 +42,6 @@ namespace BITKit.Net
|
|||
|
||||
private DateTime _now=DateTime.UtcNow;
|
||||
|
||||
[NetRpc]
|
||||
public event Action<int, float, bool> OnNetRpcTest;
|
||||
public KCPNetServer(ILogger<KCPNetServer> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
|
@ -55,23 +53,6 @@ namespace BITKit.Net
|
|||
KCPNet.Config
|
||||
);
|
||||
_timer.Elapsed += Tick;
|
||||
//BIT4Log.Log<KCPNetServer>("已创建KCP服务器");
|
||||
|
||||
AddCommandListener<SimplePing>(F);
|
||||
|
||||
AddRpcHandle(this);
|
||||
|
||||
OnNetRpcTest += (_int, _float, _bool) =>
|
||||
{
|
||||
_logger.LogInformation($"已收到Rpc测试:{_int},{_float},{_bool}");
|
||||
};
|
||||
|
||||
return;
|
||||
UniTask<SimplePing> F(SimplePing p)
|
||||
{
|
||||
p.EndTime = DateTime.Now;
|
||||
return UniTask.FromResult(p);
|
||||
}
|
||||
}
|
||||
|
||||
private void Tick(object sender, ElapsedEventArgs e)
|
||||
|
@ -212,15 +193,30 @@ namespace BITKit.Net
|
|||
}
|
||||
}
|
||||
|
||||
public T GetRemoteInterface<T>() => _common.GetRemoteInterface<T>();
|
||||
public void Invoke(Memory<byte> bytes)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public UniTask<Memory<byte>> InvokeAsync(Memory<byte> bytes)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void Invoke(IReadOnlyList<byte> bytes)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public UniTask<IReadOnlyList<byte>> InvokeAsync(IReadOnlyList<byte> bytes)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
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()
|
||||
});
|
||||
_logger.LogInformation($"{id}已连接到:{Name}");
|
||||
SendMessageToClient(id, $"成功连接到服务器:{Name}");
|
||||
}
|
||||
|
@ -237,421 +233,22 @@ namespace BITKit.Net
|
|||
{
|
||||
try
|
||||
{
|
||||
OnDataInternel(Id, bytes, channel);
|
||||
_common.OnDataInternal(Id, bytes, channel);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
BIT4Log.LogException(e);
|
||||
_logger.LogCritical(e, e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnDataInternel(int Id, ArraySegment<byte> bytes, KcpChannel channel)
|
||||
{
|
||||
using var ms = new MemoryStream(bytes.ToArray());
|
||||
using var reader = new BinaryReader(ms);
|
||||
|
||||
try
|
||||
{
|
||||
var type = (NetCommandType)ms.ReadByte();
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case NetCommandType.Message:
|
||||
_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<KCPNetServer>($"已收到指令:{command},值:\n{JsonConvert.SerializeObject(command, Formatting.Indented)}");
|
||||
_common.Events.Invoke(command.GetType().FullName, command);
|
||||
|
||||
(int Id, object Command) tuple = (Id, command);
|
||||
_common.Events.InvokeDirect(command.GetType().FullName, tuple);
|
||||
break;
|
||||
case NetCommandType.Heartbeat:
|
||||
if (Connections.ContainsKey(Id))
|
||||
{
|
||||
if (_common.LastHeartbeat.ContainsKey(Id))
|
||||
{
|
||||
_common.LastHeartbeat[Id] = _now;
|
||||
}
|
||||
else
|
||||
{
|
||||
_common.LastHeartbeat.TryAdd(Id, _now);
|
||||
OnConnectedInternel(Id);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case NetCommandType.AllClientCommand:
|
||||
foreach (var id in server.connections.Keys.ToArray())
|
||||
{
|
||||
_common.SendQueue.Enqueue((id,bytes.ToArray()));
|
||||
}
|
||||
break;
|
||||
case NetCommandType.TargetCommand:
|
||||
var targetId = reader.ReadInt32();
|
||||
_common.SendQueue.Enqueue((targetId,bytes.ToArray()));
|
||||
break;
|
||||
case NetCommandType.Ping:
|
||||
_common.SendQueue.Enqueue((Id,new byte[] { (byte)NetCommandType.Ping }));
|
||||
break;
|
||||
case NetCommandType.GetFromServer:
|
||||
{
|
||||
var requestId = reader.ReadInt32();
|
||||
|
||||
using var returnMS = new MemoryStream();
|
||||
await using var returnWriter = new BinaryWriter(returnMS);
|
||||
returnWriter.Write((byte)NetCommandType.ReturnToClient);
|
||||
returnWriter.Write(requestId);
|
||||
|
||||
if (reader.ReadBoolean())
|
||||
{
|
||||
var path = reader.ReadString();
|
||||
var pars = BITBinary.Read(reader).As<object[]>();
|
||||
object value = null;
|
||||
|
||||
if (_common.RpcMethods.TryGetValue(path, out var methodInfo))
|
||||
{
|
||||
var isAwaitable = methodInfo.ReturnType.GetMethod(nameof(Task.GetAwaiter)) != null;
|
||||
var handle = _common.RpcHandles[path];
|
||||
|
||||
if (methodInfo.GetParameters().Length is 0)
|
||||
{
|
||||
pars = new object[] { };
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (isAwaitable)
|
||||
{
|
||||
dynamic result = methodInfo.Invoke(handle, pars)!;
|
||||
|
||||
if (methodInfo.ReturnType == typeof(void)
|
||||
||
|
||||
methodInfo.ReturnType == typeof(UniTask)
|
||||
||
|
||||
methodInfo.ReturnType == typeof(UniTask<>)
|
||||
)
|
||||
{
|
||||
await result;
|
||||
value = -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
value = await result;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
value = methodInfo.Invoke(handle, pars);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if(e is TargetInvocationException tie)
|
||||
e = tie.InnerException;
|
||||
|
||||
returnWriter.Write(false);
|
||||
returnWriter.Write(e.Message);
|
||||
|
||||
var _bytes = returnMS.ToArray();
|
||||
_common.SendQueue.Enqueue((Id,_bytes));
|
||||
|
||||
if (e is InGameException inGameException)
|
||||
{
|
||||
BIT4Log.Warning<KCPNetServer>(inGameException.Message);
|
||||
return;
|
||||
}
|
||||
BIT4Log.LogException(e);
|
||||
return;
|
||||
}
|
||||
returnWriter.Write(true);
|
||||
|
||||
}else if (_common.RpcEvents.TryGetValue(path, out var eventInfo))
|
||||
{
|
||||
var handle = _common.RpcHandles[path];
|
||||
var fieldInfo = handle.GetType().GetField(eventInfo.Name,ReflectionHelper.Flags)!;
|
||||
|
||||
var eventDelegate = fieldInfo.GetValue(handle) as MulticastDelegate;
|
||||
|
||||
foreach (var del in eventDelegate!.GetInvocationList())
|
||||
{
|
||||
del.Method.Invoke(del.Target, pars);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"未找到对应的Rpc方法:{path}");
|
||||
}
|
||||
if (value is not null)
|
||||
{
|
||||
BITBinary.Write(returnWriter, value);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var commandObjs = BITBinary.Read(reader);
|
||||
|
||||
var commandObj = commandObjs.As<object[]>()[0];
|
||||
var funcName = commandObj.GetType()!.FullName!;
|
||||
if (_common.Rpc.TryGetValue(funcName, out var func))
|
||||
{
|
||||
var value = await func.As<Func<object, UniTask<object>>>().Invoke(commandObj);
|
||||
returnWriter.Write(true);
|
||||
BITBinary.Write(returnWriter, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"未找到对应的Rpc方法:{funcName}");
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
_common.SendQueue.Enqueue((Id,returnMS.ToArray()));
|
||||
}
|
||||
|
||||
}
|
||||
break;
|
||||
case NetCommandType.ReturnToServer:
|
||||
{
|
||||
var id = reader.ReadInt32();
|
||||
|
||||
if (_common.P2P.TryRemove(id, out var source))
|
||||
{
|
||||
if (reader.ReadBoolean())
|
||||
{
|
||||
var value = BITBinary.Read(reader);
|
||||
source.TrySetResult(value);
|
||||
}
|
||||
else
|
||||
{
|
||||
var message = reader.ReadString();
|
||||
source.TrySetException(new Exception(message));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning($"ID为{id}的请求未注册回调");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
break;
|
||||
default:
|
||||
_logger.LogInformation($"未知消息类型:{type},字节:{(byte)type}");
|
||||
_logger.LogInformation(
|
||||
$"已收到:({Id}, {BitConverter.ToString(bytes.Array, bytes.Offset, bytes.Count)} @ {channel})");
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ms.Close();
|
||||
reader.Close();
|
||||
BIT4Log.LogException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private void OnError(int Id, ErrorCode errorCode, string message)
|
||||
{
|
||||
_logger.LogInformation($"异常:{errorCode},{message}");
|
||||
}
|
||||
|
||||
public void ServerCommand<T>(T command = default)
|
||||
{
|
||||
_common. Events.Invoke<T>(command);
|
||||
}
|
||||
|
||||
public void AllClientCommand<T>(T command = default)
|
||||
{
|
||||
foreach (var id in server.connections.Keys.ToArray())
|
||||
{
|
||||
ClientCommand(id, command);
|
||||
}
|
||||
}
|
||||
|
||||
public void ClientCommand<T>(int id, T command)
|
||||
{
|
||||
using var ms = new MemoryStream();
|
||||
using var writer = new BinaryWriter(ms);
|
||||
writer.Write((byte)NetCommandType.Command);
|
||||
BITBinary.Write(writer,command);
|
||||
_common.SendQueue.Enqueue((id,ms.ToArray()));
|
||||
}
|
||||
|
||||
public UniTask<T> GetFromServer<T>(string path=default,params object[] pars)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public async UniTask<T> GetFromClient<T>(int id,string path=default, params object[] pars)
|
||||
{
|
||||
await UniTask.SwitchToThreadPool();
|
||||
var index = _index++;
|
||||
var ms = new MemoryStream();
|
||||
var writer = new BinaryWriter(ms);
|
||||
writer.Write((byte)NetCommandType.GetFromClient);
|
||||
writer.Write(index);
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
writer.Write(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.Write(true);
|
||||
writer.Write(path);
|
||||
}
|
||||
BITBinary.Write(writer,pars);
|
||||
|
||||
var bytes = ms.ToArray();
|
||||
|
||||
await ms.DisposeAsync();
|
||||
await writer.DisposeAsync();
|
||||
|
||||
_common.SendQueue.Enqueue((id,bytes));
|
||||
|
||||
var source = new UniTaskCompletionSource<object>();
|
||||
|
||||
|
||||
_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)
|
||||
{
|
||||
throw e;
|
||||
}
|
||||
if (UniTask.CompletedTask is T t)
|
||||
{
|
||||
return t;
|
||||
}
|
||||
if (typeof(T) == typeof(UniTaskVoid))
|
||||
{
|
||||
return default;
|
||||
}
|
||||
return (T)value;
|
||||
}
|
||||
|
||||
public void AddRpcHandle(object rpcHandle)
|
||||
{
|
||||
foreach (var methodInfo in rpcHandle.GetType().GetMethods())
|
||||
{
|
||||
var att = methodInfo.GetCustomAttribute<NetRpcAttribute>(true);
|
||||
if(att is null)continue;
|
||||
_common.RpcMethods.TryAdd(methodInfo.Name, methodInfo);
|
||||
_common.RpcHandles.TryAdd(methodInfo.Name, rpcHandle);
|
||||
}
|
||||
foreach (var eventInfo in rpcHandle.GetType().GetEvents())
|
||||
{
|
||||
var att = eventInfo.GetCustomAttribute<NetRpcAttribute>(true);
|
||||
if(att is null)continue;
|
||||
|
||||
_common. RpcEvents.TryAdd(eventInfo.Name, eventInfo);
|
||||
_common. RpcHandles.TryAdd(eventInfo.Name, rpcHandle);
|
||||
}
|
||||
|
||||
}
|
||||
[NetRpc]
|
||||
public string MyRpcTest(string hello)
|
||||
{
|
||||
return "Hello World";
|
||||
}
|
||||
[NetRpc]
|
||||
public async UniTask<string> MyRpcTestAsync(string hello)
|
||||
{
|
||||
await Task.Delay(1000);
|
||||
return $"{hello} World";
|
||||
}
|
||||
|
||||
public void AddCommandListener<T>(Action<T> handle)
|
||||
{
|
||||
_common.Events.AddListener<T>(handle);
|
||||
}
|
||||
|
||||
public void AddCommandListener<T>(Func<T,UniTask<T>> func)
|
||||
{
|
||||
_common.Rpc.TryAdd(typeof(T).FullName, F);
|
||||
return;
|
||||
|
||||
async UniTask<object> F(object o)
|
||||
{
|
||||
return await func.Invoke((T)o);
|
||||
}
|
||||
}
|
||||
public void RemoveCommandListener<T>(Func<T,UniTask<T>> func)
|
||||
{
|
||||
_common. Rpc.TryRemove(typeof(T).FullName, out _);
|
||||
}
|
||||
public void AddCommandListenerWithId<T>(Action<int, T> handle)
|
||||
{
|
||||
_common.Events.AddListenerDirect(typeof(T).FullName, Callback);
|
||||
return;
|
||||
|
||||
void Callback(object value)
|
||||
{
|
||||
if (value is ValueTuple<int, object> tuple && tuple.Item2 is T)
|
||||
{
|
||||
handle.Invoke(tuple.Item1, (T)tuple.Item2);
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void KickClient(int id)
|
||||
{
|
||||
server.Disconnect(id);
|
||||
}
|
||||
|
||||
public void RemoveCommandListener<T>(Action<T> handle)
|
||||
{
|
||||
_common. Events.RemoveListener<T>(handle);
|
||||
}
|
||||
|
||||
public void RemoveCommandListener<T>(Func<string, T> func)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void SendRT(string rpcName, params object[] pars)
|
||||
{
|
||||
throw new NotImplementedException("服务端不支持此方法");
|
||||
}
|
||||
|
||||
public void SendTargetRT(int id, string rpcName, params object[] pars)
|
||||
{
|
||||
using var ms = new MemoryStream();
|
||||
using var writer = new BinaryWriter(ms);
|
||||
writer.Write((byte)NetCommandType.TargetRpc);
|
||||
writer.Write(rpcName);
|
||||
BITBinary.Write(writer,pars);
|
||||
var bytes = ms.ToArray();
|
||||
_common.SendQueue.Enqueue((id,bytes));
|
||||
}
|
||||
|
||||
public void SendAllRT(string rpcName, params object[] pars)
|
||||
{
|
||||
using var ms = new MemoryStream();
|
||||
using var writer = new BinaryWriter(ms);
|
||||
writer.Write((byte)NetCommandType.AllRpc);
|
||||
writer.Write(rpcName);
|
||||
BITBinary.Write(writer,pars);
|
||||
var bytes = ms.ToArray();
|
||||
foreach (var id in server.connections.Keys)
|
||||
{
|
||||
_common.SendQueue.Enqueue((id,bytes));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,28 +8,21 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Cysharp.Threading.Tasks;
|
||||
#if UNITY
|
||||
using UnityEngine;
|
||||
#endif
|
||||
using kcp2k;
|
||||
using Microsoft.CodeAnalysis.Scripting;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
|
||||
namespace BITKit
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Method|AttributeTargets.Event)]
|
||||
public sealed class NetRpcAttribute : Attribute
|
||||
{
|
||||
public readonly bool AddTypeNamePrefix;
|
||||
public NetRpcAttribute()
|
||||
{
|
||||
}
|
||||
public NetRpcAttribute(bool addTypeNamePrefix)
|
||||
{
|
||||
AddTypeNamePrefix = addTypeNamePrefix;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 帮助类
|
||||
/// </summary>
|
||||
|
@ -64,21 +57,13 @@ namespace BITKit
|
|||
public enum NetCommandType:byte
|
||||
{
|
||||
Undefined=0,
|
||||
Command=1,
|
||||
TargetCommand=2,
|
||||
File=3,
|
||||
Rpc=4,
|
||||
AllClientCommand=5,
|
||||
Message=6,
|
||||
Heartbeat=7,
|
||||
Ping=8,
|
||||
GetFromServer=9,
|
||||
GetFromClient=10,
|
||||
ReciveFile=11,
|
||||
ReturnToServer=12,
|
||||
ReturnToClient=13,
|
||||
TargetRpc=14,
|
||||
AllRpc=15,
|
||||
Message=1,
|
||||
Heartbeat=2,
|
||||
Rpc,
|
||||
SetPropertyValue,
|
||||
SetFieldValue,
|
||||
WaitTask,
|
||||
ReturnValue,
|
||||
}
|
||||
/// <summary>
|
||||
/// 网络提供服务,包括了基础网络服务,e.g
|
||||
|
@ -94,96 +79,9 @@ namespace BITKit
|
|||
/// </summary>
|
||||
public interface INetProvider
|
||||
{
|
||||
|
||||
public uint TickRate { get; set; }
|
||||
/// <summary>
|
||||
/// 向服务端发送指令
|
||||
/// </summary>
|
||||
/// <typeparam name="T">远程指令类型</typeparam>
|
||||
void ServerCommand<T>(T command = default);
|
||||
|
||||
/// <summary>
|
||||
/// 向所有客户端发送指令
|
||||
/// </summary>
|
||||
/// <typeparam name="T">远程指令类型</typeparam>
|
||||
void AllClientCommand<T>(T command = default);
|
||||
/// <summary>
|
||||
/// 向单个客户端发送指令
|
||||
/// </summary>
|
||||
/// <param name="id">客户端ID</param>
|
||||
/// <param name="command">指令实例</param>
|
||||
/// <typeparam name="T">远程指令类型</typeparam>
|
||||
void ClientCommand<T>(int id, T command);
|
||||
|
||||
/// <summary>
|
||||
/// 从服务端获取数据
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
UniTask<T> GetFromServer<T>(string path = null,params object[] pars);
|
||||
|
||||
/// <summary>
|
||||
/// 从客户端获取数据
|
||||
/// </summary>
|
||||
/// <param name="id">客户端ID</param>
|
||||
/// <param name="addressablePath">可寻址路劲 e.g. Key</param>
|
||||
/// <typeparam name="T">远程指令类型</typeparam>
|
||||
/// <returns></returns>
|
||||
UniTask<T> GetFromClient<T>(int id,string path = null,params object[] pars);
|
||||
|
||||
/// <summary>
|
||||
/// 添加RPC远程服务,服务类型为需要的方法标记[RPC]
|
||||
/// </summary>
|
||||
/// <param name="rpcHandle">RPC服务实例</param>
|
||||
void AddRpcHandle(object rpcHandle);
|
||||
|
||||
/// <summary>
|
||||
/// 监听远程指令
|
||||
/// </summary>
|
||||
/// <param name="handle">远程指令回调</param>
|
||||
/// <typeparam name="T">远程指令类型</typeparam>
|
||||
void AddCommandListener<T>(Action<T> handle);
|
||||
/// <summary>
|
||||
/// 取消监听远程指令
|
||||
/// </summary>
|
||||
/// <param name="handle"></param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
void RemoveCommandListener<T>(Action<T> handle);
|
||||
|
||||
/// <summary>
|
||||
/// 监听远程func
|
||||
/// </summary>
|
||||
/// <param name="func"></param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
void AddCommandListener<T>(Func<T,UniTask<T>> func);
|
||||
|
||||
/// <summary>
|
||||
/// 取消监听远程指令
|
||||
/// </summary>
|
||||
/// <param name="handle">远程指令回调</param>
|
||||
/// <typeparam name="T">远程指令类型</typeparam>
|
||||
void RemoveCommandListener<T>(Func<T,UniTask<T>> func);
|
||||
|
||||
/// <summary>
|
||||
/// 向服务端发送Rpc
|
||||
/// </summary>
|
||||
/// <param name="rpcName">RPC名称</param>
|
||||
/// <param name="pars">RPC参数</param>
|
||||
void SendRT(string rpcName, params object[] pars);
|
||||
|
||||
/// <summary>
|
||||
/// 向指定客户端发送Rpc
|
||||
/// </summary>
|
||||
/// <param name="id">远程客户端Id</param>
|
||||
/// <param name="rpcName">RPC名称</param>
|
||||
/// <param name="pars">RPC参数</param>
|
||||
void SendTargetRT(int id, string rpcName, params object[] pars);
|
||||
|
||||
/// <summary>
|
||||
/// 向所有客户端发送Rpc
|
||||
/// </summary>
|
||||
/// <param name="rpcName">RPC名称</param>
|
||||
/// <param name="pars">RPC参数</param>
|
||||
void SendAllRT(string rpcName, params object[] pars);
|
||||
/// <summary>
|
||||
/// 更新Tick
|
||||
/// </summary>
|
||||
|
@ -192,6 +90,23 @@ namespace BITKit
|
|||
/// 连接协议握手
|
||||
/// </summary>
|
||||
void HandShake();
|
||||
/// <summary>
|
||||
/// 获取远程接口
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
T GetRemoteInterface<T>();
|
||||
/// <summary>
|
||||
/// 远程调用
|
||||
/// </summary>
|
||||
void Invoke(Memory<byte> bytes);
|
||||
/// <summary>
|
||||
/// 异步远程调用,有返回结果
|
||||
/// </summary>
|
||||
/// <param name="bytes"></param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
UniTask<Memory<byte>> InvokeAsync(Memory<byte> bytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -265,12 +180,6 @@ namespace BITKit
|
|||
/// </summary>
|
||||
public IDictionary<int,EndPoint> Connections { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 添加远程命令监听,包括客户端Id
|
||||
/// </summary>
|
||||
/// <param name="handle"></param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
void AddCommandListenerWithId<T>(Action<int,T> handle);
|
||||
/// <summary>
|
||||
/// 踢出客户端
|
||||
/// </summary>
|
||||
|
@ -292,10 +201,6 @@ namespace BITKit
|
|||
/// 通讯接口
|
||||
/// </summary>
|
||||
public INetProvider NetProvider=>this as INetProvider;
|
||||
/// <summary>
|
||||
/// 源物体,用于通过代理直接访问
|
||||
/// </summary>
|
||||
public object Source => this;
|
||||
|
||||
//基本客户端回调
|
||||
public event Action OnStartConnect;
|
||||
|
@ -337,47 +242,257 @@ namespace BITKit
|
|||
void SendServerMessage(string message);
|
||||
}
|
||||
|
||||
public class NetProviderCommon
|
||||
public class NetProviderCommon:INetProvider
|
||||
{
|
||||
public static readonly BITSharp.ICodeGenerator CodeGenerator = new RemoteInterfaceGenerator();
|
||||
|
||||
private class RemoteInterfaceGenerator:BITSharp.CodeGenerator
|
||||
{
|
||||
public INetProvider NetProvider = new NetProviderCommon();
|
||||
|
||||
public override string BeforeGenerate(Type type)
|
||||
{
|
||||
return $"public {nameof(INetProvider)} {nameof(NetProvider)};";
|
||||
}
|
||||
|
||||
public override IReadOnlyList<string> GenerateNamespaces(Type type)
|
||||
{
|
||||
return new string[]
|
||||
{
|
||||
"System.IO",
|
||||
typeof(INetProvider).Namespace,
|
||||
};
|
||||
}
|
||||
|
||||
public override string GenerateMethodContext(MethodInfo methodInfo)
|
||||
{
|
||||
var codeBuilder = new StringBuilder();
|
||||
var parameterInfos = methodInfo.GetParameters();
|
||||
|
||||
using var ms = new MemoryStream();
|
||||
using var writer = new BinaryWriter(ms);
|
||||
|
||||
codeBuilder.AppendLine(" using var ms = new MemoryStream();");
|
||||
codeBuilder.AppendLine(" using var writer = new BinaryWriter(ms);");
|
||||
codeBuilder.AppendLine(" writer.Write((byte)NetCommandType.Rpc);");
|
||||
codeBuilder.AppendLine(" writer.Write((byte)NetCommandType.Rpc);");
|
||||
|
||||
codeBuilder.AppendLine($" writer.Write(\"{methodInfo.DeclaringType!.FullName}\");");
|
||||
codeBuilder.AppendLine($" writer.Write(\"{methodInfo.Name}\");");
|
||||
|
||||
codeBuilder.AppendLine($" writer.Write({parameterInfos.Length});");
|
||||
|
||||
foreach (var parameterInfo in parameterInfos)
|
||||
{
|
||||
codeBuilder.AppendLine($"BITBinary.Write(writer,{parameterInfo.Name});");
|
||||
}
|
||||
|
||||
var isAwaitable = methodInfo.ReturnType.GetMethod(nameof(Task.GetAwaiter)) != null;
|
||||
|
||||
if (isAwaitable)
|
||||
{
|
||||
var generics = methodInfo.ReturnType.CSharpName();
|
||||
|
||||
generics= generics.Replace(nameof(UniTask), string.Empty);
|
||||
generics = generics.Replace(nameof(Task), string.Empty);
|
||||
|
||||
codeBuilder.AppendLine($" return BITBinary.Read{generics}((await NetProvider.InvokeAsync(ms.ToArray())).ToArray());");
|
||||
}
|
||||
else
|
||||
{
|
||||
codeBuilder.AppendLine(base.GenerateMethodContext(methodInfo));
|
||||
}
|
||||
|
||||
|
||||
return codeBuilder.ToString();
|
||||
}
|
||||
|
||||
public override string GenerateProperty(PropertyInfo propertyInfo)
|
||||
{
|
||||
var codeBuilder = new StringBuilder();
|
||||
|
||||
var source = base.GenerateProperty(propertyInfo);
|
||||
|
||||
source = source.Replace("get;", $"\nget=>_{propertyInfo.Name};");
|
||||
source = source.Replace("set;", $"\nset{{\n_{propertyInfo.Name}=value; \n using var ms = new MemoryStream();\n using var writer = new BinaryWriter(ms);\n writer.Write((byte)NetCommandType.Rpc);\n writer.Write((byte)NetCommandType.SetPropertyValue);\n writer.Write(\"{propertyInfo.DeclaringType!.FullName}\");\n writer.Write(\"{propertyInfo.Name}\");\n BITBinary.Write(writer,value);\n NetProvider.Invoke(ms.ToArray());\n }}");
|
||||
|
||||
codeBuilder.AppendLine(source);
|
||||
codeBuilder.AppendLine($"public {propertyInfo.PropertyType.CSharpName()} _{propertyInfo.Name};");
|
||||
|
||||
return codeBuilder.ToString();
|
||||
}
|
||||
}
|
||||
private void SyntaxTest()
|
||||
{
|
||||
/*
|
||||
using var ms = new MemoryStream();
|
||||
using var writer = new BinaryWriter(ms);
|
||||
writer.Write((byte)NetCommandType.Rpc);
|
||||
writer.Write((byte)NetCommandType.SetPropertyValue);
|
||||
writer.Write(nameof(SyntaxTest));
|
||||
BITBinary.Write(writer,1);
|
||||
NetProvider.Invoke(ms.ToArray());
|
||||
*/
|
||||
}
|
||||
|
||||
public IServiceProvider ServiceProvider { get; set; } = new ServiceCollection().BuildServiceProvider();
|
||||
|
||||
public readonly GenericEvent Events = new();
|
||||
|
||||
public readonly ConcurrentDictionary<int, UniTaskCompletionSource<object>> P2P = new();
|
||||
public readonly ConcurrentDictionary<string,Func<object,UniTask<object>>> Rpc = new();
|
||||
public readonly ConcurrentDictionary<string,MethodInfo> RpcMethods = new();
|
||||
public readonly ConcurrentDictionary<string,EventInfo> RpcEvents = new();
|
||||
public readonly ConcurrentDictionary<string,object> RpcHandles = new();
|
||||
public int Timeout=8000;
|
||||
|
||||
public int Index;
|
||||
|
||||
public readonly ConcurrentDictionary<int, UniTaskCompletionSource<Memory<byte>>> P2P = new();
|
||||
public readonly ConcurrentDictionary<int,DateTime> LastHeartbeat = new();
|
||||
public readonly ConcurrentDictionary<string, object> RemoteInterfaces = new();
|
||||
|
||||
public readonly ConcurrentDictionary<Type,object> LocalServices = new();
|
||||
|
||||
public readonly ConcurrentQueue<(int id,byte[] bytes)> SendQueue = new();
|
||||
public readonly ConcurrentDictionary<int,int> DropCount = new();
|
||||
|
||||
public readonly byte[] HeartBeat = new byte[] { (byte)NetCommandType.Heartbeat };
|
||||
|
||||
public void AddRpcHandle(object rpcHandle)
|
||||
public uint TickRate { get; set; }
|
||||
public void Tick()
|
||||
{
|
||||
var reportBuilder = new StringBuilder();
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
reportBuilder.AppendLine($"正在通过反射注册{rpcHandle.GetType().Name}");
|
||||
foreach (var methodInfo in rpcHandle.GetType().GetMethods())
|
||||
public void HandShake()
|
||||
{
|
||||
var att = methodInfo.GetCustomAttribute<NetRpcAttribute>();
|
||||
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");
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
foreach (var eventInfo in rpcHandle.GetType().GetEvents())
|
||||
public async void OnDataInternal(int id,ArraySegment<byte> bytes, KcpChannel channel)
|
||||
{
|
||||
var att = eventInfo.GetCustomAttribute<NetRpcAttribute>();
|
||||
if (att is null) continue;
|
||||
using var ms = new MemoryStream(bytes.ToArray());
|
||||
using var reader = new BinaryReader(ms);
|
||||
|
||||
RpcEvents.TryAdd(eventInfo.Name, eventInfo);
|
||||
RpcHandles.TryAdd(eventInfo.Name, rpcHandle);
|
||||
var type = (NetCommandType)ms.ReadByte();
|
||||
|
||||
reportBuilder.AppendLine($"Add [{eventInfo.Name}] as EventInfo");
|
||||
}
|
||||
var now = DateTime.Now;
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case NetCommandType.Heartbeat:
|
||||
{
|
||||
LastHeartbeat.GetOrCreate(id);
|
||||
LastHeartbeat[id]=now;
|
||||
}
|
||||
break;
|
||||
case NetCommandType.Message:
|
||||
{
|
||||
var message = reader.ReadString();
|
||||
BIT4Log.Log<INetProvider>(message);
|
||||
}
|
||||
break;
|
||||
case NetCommandType.Rpc:
|
||||
{
|
||||
var command = (NetCommandType)ms.ReadByte();
|
||||
|
||||
var serviceName = reader.ReadString();
|
||||
var serviceType = BITSharp.GetTypeFromFullName(serviceName);
|
||||
var service = ServiceProvider.GetRequiredService(serviceType);
|
||||
|
||||
switch (command)
|
||||
{
|
||||
case NetCommandType.SetPropertyValue:
|
||||
{
|
||||
serviceType.GetProperty(reader.ReadString())!.SetValue(service,BITBinary.Read(reader));
|
||||
}
|
||||
break;
|
||||
case NetCommandType.SetFieldValue:
|
||||
{
|
||||
serviceType.GetField(reader.ReadString())!.SetValue(service,BITBinary.Read(reader));
|
||||
}
|
||||
break;
|
||||
case NetCommandType.Rpc:
|
||||
{
|
||||
var methodInfo = serviceType.GetMethod(reader.ReadString())!;
|
||||
var parameterLength = reader.ReadInt32();
|
||||
var parameters = new object[parameterLength];
|
||||
for (var i = 0; i < parameterLength; i++)
|
||||
{
|
||||
parameters[i] = BITBinary.Read(reader);
|
||||
}
|
||||
var isAwaitable = methodInfo.ReturnType.GetMethod(nameof(Task.GetAwaiter)) != null;
|
||||
if (isAwaitable)
|
||||
{
|
||||
dynamic result = methodInfo.Invoke(service, parameters)!;
|
||||
|
||||
object resultValue = null;
|
||||
|
||||
if (methodInfo.ReturnType == typeof(void)
|
||||
||
|
||||
methodInfo.ReturnType == typeof(UniTask)
|
||||
||
|
||||
methodInfo.ReturnType == typeof(UniTask<>)
|
||||
)
|
||||
{
|
||||
await result;
|
||||
resultValue = -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
resultValue = await result;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
methodInfo.Invoke(service, parameters);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
public T GetRemoteInterface<T>()
|
||||
{
|
||||
if(RemoteInterfaces.TryGetValue(typeof(T).FullName!,out var obj))
|
||||
{
|
||||
return (T)obj;
|
||||
}
|
||||
|
||||
var code = CodeGenerator.Generate(typeof(T));
|
||||
|
||||
var options = ScriptOptions.Default;
|
||||
|
||||
var assemblies = BITSharp.GetReferencedAssemblies(typeof(T));
|
||||
|
||||
foreach (var referencedAssembly in assemblies)
|
||||
{
|
||||
options= options.AddReferences(referencedAssembly);
|
||||
}
|
||||
|
||||
var assembly = BITSharp.Compile(code, options);
|
||||
|
||||
var type = assembly.GetExportedTypes().First(x => typeof(T).IsAssignableFrom(x));
|
||||
|
||||
var instance = Activator.CreateInstance(type)!;
|
||||
|
||||
instance.GetType().GetField("NetProvider")!.SetValue(instance,this);
|
||||
|
||||
return (T)instance;
|
||||
}
|
||||
|
||||
public void Invoke(Memory<byte> bytes)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public async UniTask<Memory<byte>> InvokeAsync(Memory<byte> bytes)
|
||||
{
|
||||
var index = Index++;
|
||||
var source = new UniTaskCompletionSource<Memory<byte>>();
|
||||
P2P.TryAdd(index, source);
|
||||
|
||||
var cancelCts = new CancellationTokenSource();
|
||||
cancelCts.CancelAfter(Timeout);
|
||||
|
||||
return await source.Task.AttachExternalCancellation(cancelCts.Token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,31 +2,216 @@ using System;
|
|||
using System.CodeDom.Compiler;
|
||||
using System.Collections.Concurrent;
|
||||
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 System.Threading.Tasks;
|
||||
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 UnityEngine;
|
||||
using static BITKit.Mod.DotNetSdkRoslynService;
|
||||
#endif
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
|
||||
namespace BITKit
|
||||
{
|
||||
public class BITSharp
|
||||
public static class BITSharp
|
||||
{
|
||||
public static ICodeGenerator SharedCodeGenerator = new CodeGenerator();
|
||||
public interface ICodeGenerator
|
||||
{
|
||||
public string Generate(Type type,string className=null);
|
||||
IReadOnlyList<string> GenerateNamespaces(Type type);
|
||||
string BeforeGenerate(Type type);
|
||||
string AfterGenerate(Type type);
|
||||
string GenerateMethod(MethodInfo methodInfo);
|
||||
string GenerateMethodContext(MethodInfo methodInfo);
|
||||
string GenerateField(FieldInfo fieldInfo);
|
||||
string GenerateProperty(PropertyInfo propertyInfo);
|
||||
|
||||
string GenerateEvent(EventInfo eventInfo);
|
||||
}
|
||||
public class CodeGenerator:ICodeGenerator
|
||||
{
|
||||
public virtual string Generate(Type type, string className = null)
|
||||
{
|
||||
if (type.IsInterface is false) throw new InvalidDataException("The type have to be interface");
|
||||
|
||||
var codeBuilder = new StringBuilder();
|
||||
|
||||
var nameSpaces = new HashSet<string>();
|
||||
|
||||
foreach (var propertyInfo in type.GetProperties())
|
||||
{
|
||||
nameSpaces.Add(propertyInfo.PropertyType.Namespace);
|
||||
}
|
||||
|
||||
foreach (var fieldInfo in type.GetFields())
|
||||
{
|
||||
nameSpaces.Add(fieldInfo.FieldType.Namespace);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
foreach (var parameterInfo in method.GetParameters())
|
||||
{
|
||||
nameSpaces.Add(parameterInfo.ParameterType.Namespace);
|
||||
}
|
||||
}
|
||||
|
||||
nameSpaces.Add("System");
|
||||
nameSpaces.Add("System.Collections.Concurrent");
|
||||
nameSpaces.Add("System.Collections.Generic");
|
||||
|
||||
nameSpaces.Add(type.Namespace);
|
||||
|
||||
foreach (var name in GenerateNamespaces(type))
|
||||
{
|
||||
nameSpaces.Add(name);
|
||||
}
|
||||
|
||||
codeBuilder.AppendLine(string.Join('\n', nameSpaces.Where(x=>string.IsNullOrEmpty(x) is false).Select(x=>$"using {x};")));
|
||||
|
||||
|
||||
//codeBuilder.AppendLine("namespace BITSharpGen");
|
||||
//codeBuilder.AppendLine("{");
|
||||
|
||||
codeBuilder.AppendLine($"public class @{type.Name}Gen : {type.Name}");
|
||||
codeBuilder.AppendLine("{");
|
||||
|
||||
codeBuilder.AppendLine(BeforeGenerate(type));
|
||||
|
||||
foreach (var fieldInfo in type.GetFields())
|
||||
{
|
||||
codeBuilder.AppendLine(GenerateField(fieldInfo));
|
||||
}
|
||||
|
||||
foreach (var propertyInfo in type.GetProperties())
|
||||
{
|
||||
codeBuilder.AppendLine(GenerateProperty(propertyInfo));
|
||||
}
|
||||
|
||||
// 遍历接口的所有成员,生成方法的默认实现
|
||||
foreach (var method in type.GetMethods())
|
||||
{
|
||||
codeBuilder.AppendLine(GenerateMethod(method));
|
||||
}
|
||||
|
||||
codeBuilder.AppendLine(AfterGenerate(type));
|
||||
|
||||
codeBuilder.AppendLine("}");
|
||||
|
||||
return codeBuilder.ToString();
|
||||
}
|
||||
|
||||
public virtual IReadOnlyList<string> GenerateNamespaces(Type type)
|
||||
{
|
||||
return ArraySegment<string>.Empty;
|
||||
}
|
||||
|
||||
public virtual string BeforeGenerate(Type type)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
public virtual string AfterGenerate(Type type)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
public virtual string GenerateMethod(MethodInfo methodInfo)
|
||||
{
|
||||
var codeBuilder = new StringBuilder();
|
||||
HashSet<MethodInfo> propertyMethods = new();
|
||||
|
||||
foreach (var propertyInfo in methodInfo.DeclaringType!.GetProperties())
|
||||
{
|
||||
if (propertyInfo.GetMethod is not null)
|
||||
{
|
||||
propertyMethods.Add(propertyInfo.GetMethod);
|
||||
}
|
||||
|
||||
if (propertyInfo.SetMethod is not null)
|
||||
{
|
||||
propertyMethods.Add(propertyInfo.SetMethod);
|
||||
}
|
||||
}
|
||||
|
||||
if(propertyMethods.Contains(methodInfo))return string.Empty;
|
||||
string methodName = methodInfo.Name;
|
||||
string parameters = string.Join(", ", methodInfo.GetParameters()
|
||||
.Select(p => $"{CSharpName(p.ParameterType)} {p.Name}"));
|
||||
|
||||
var isAwaitable = methodInfo.ReturnType.GetMethod(nameof(Task.GetAwaiter)) != null;
|
||||
|
||||
// 检查是否为泛型方法
|
||||
if (methodInfo.IsGenericMethod)
|
||||
{
|
||||
// 处理泛型方法
|
||||
var genericArgs = string.Join(", ", methodInfo.GetGenericArguments()
|
||||
.Select(CSharpName));
|
||||
|
||||
if (methodInfo.ReturnType == typeof(void))
|
||||
{
|
||||
codeBuilder.AppendLine($" public void {methodName}<{genericArgs}>({parameters}) {{ \n{GenerateMethodContext(methodInfo)}\n }}");
|
||||
}
|
||||
else
|
||||
{
|
||||
codeBuilder.AppendLine($" public {(isAwaitable?"async":string.Empty)} {CSharpName(methodInfo.ReturnType)} {methodName}<{genericArgs}>({parameters}) {{ {GenerateMethodContext(methodInfo)} }}");
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
if (methodInfo.ReturnType == typeof(void))
|
||||
{
|
||||
codeBuilder.AppendLine($" public void {methodName}({parameters}) {{ {GenerateMethodContext(methodInfo)}}}"); // Void 方法默认空实现
|
||||
}
|
||||
else
|
||||
{
|
||||
// 返回默认值
|
||||
codeBuilder.AppendLine($" public {(isAwaitable?"async":string.Empty)} {CSharpName(methodInfo.ReturnType)} {methodName}({parameters}) {{ {GenerateMethodContext(methodInfo)} }}");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return codeBuilder.ToString();
|
||||
}
|
||||
|
||||
public virtual string GenerateMethodContext(MethodInfo methodInfo)
|
||||
{
|
||||
return methodInfo.ReturnType == typeof(void) ? string.Empty : $"return default;";
|
||||
}
|
||||
|
||||
public virtual string GenerateField(FieldInfo fieldInfo)
|
||||
{
|
||||
return $"public {CSharpName(fieldInfo.FieldType)} {fieldInfo.Name};";
|
||||
}
|
||||
|
||||
public virtual string GenerateProperty(PropertyInfo propertyInfo)
|
||||
{
|
||||
return $"public {CSharpName(propertyInfo.PropertyType)} {propertyInfo.Name}" + "{ get;set; }";
|
||||
}
|
||||
|
||||
public virtual string GenerateEvent(EventInfo eventInfo)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
#if UNITY_5_3_OR_NEWER
|
||||
[RuntimeInitializeOnLoadMethod]
|
||||
private static void Reload()
|
||||
|
@ -65,6 +250,9 @@ namespace BITKit
|
|||
public static IReadOnlyCollection<Assembly> GetReferencedAssemblies(Type type)
|
||||
{
|
||||
var result = new HashSet<Assembly>();
|
||||
|
||||
result.Add(type.Assembly);
|
||||
|
||||
foreach (var fieldInfo in type.GetFields())
|
||||
{
|
||||
result.Add(fieldInfo.FieldType.Assembly);
|
||||
|
@ -84,114 +272,7 @@ namespace BITKit
|
|||
}
|
||||
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<string>();
|
||||
|
||||
HashSet<Type> allTypes = new();
|
||||
HashSet<MethodInfo> 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");
|
||||
|
||||
|
||||
|
||||
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))
|
||||
{
|
||||
codeBuilder.AppendLine($" public void {methodName}<{genericArgs}>({parameters}) {{ }}");
|
||||
}
|
||||
else
|
||||
{
|
||||
codeBuilder.AppendLine($" public {CSharpName(method.ReturnType)} {methodName}<{genericArgs}>({parameters}) {{ return default({CSharpName(method.ReturnType)}); }}");
|
||||
}
|
||||
|
||||
}
|
||||
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)}); }}");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//codeBuilder.AppendLine("\t}");
|
||||
|
||||
codeBuilder.AppendLine("}");
|
||||
|
||||
return codeBuilder.ToString();
|
||||
}
|
||||
|
||||
public static string CSharpName(Type type)
|
||||
public static string CSharpName(this Type type)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
var name = type.Name;
|
||||
|
@ -213,10 +294,13 @@ namespace BITKit
|
|||
|
||||
using var ms = new MemoryStream();
|
||||
var result = script.GetCompilation().Emit(ms);
|
||||
|
||||
if (!result.Success) throw new Exception(string.Join("\n",result.Diagnostics));
|
||||
ms.Seek(0, SeekOrigin.Begin);
|
||||
var assembly = Assembly.Load(ms.ToArray()); // 加载程序集
|
||||
|
||||
return assembly;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -43,8 +43,6 @@ namespace kcp2k
|
|||
{
|
||||
Log.Warning($"Kcp: failed to set Socket RecvBufSize = {recvBufferSize} SendBufSize = {sendBufferSize}");
|
||||
}
|
||||
|
||||
|
||||
Log.Info($"Kcp: RecvBuf = {initialReceive}=>{socket.ReceiveBufferSize} ({socket.ReceiveBufferSize/initialReceive}x) SendBuf = {initialSend}=>{socket.SendBufferSize} ({socket.SendBufferSize/initialSend}x)");
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
fileFormatVersion: 2
|
||||
guid: bf1c155248e26c446b9f5bd892f84eef
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -1,137 +0,0 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Text;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using Unity.SharpZipLib.Utils;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Experimental.Audio;
|
||||
using UnityEngine.Networking;
|
||||
|
||||
namespace BITKit.Mod
|
||||
{
|
||||
public class DotNetSdkRoslynService
|
||||
{
|
||||
private const string _InstalledFilesKey = "DotNetSdkRoslyn_InstalledFiles";
|
||||
public static string DownloadUrl =>
|
||||
"http://server.bitfall.icu:3000/root/BITKit.DotNetSdkRoslyn.Unity/archive/main.zip";
|
||||
public static bool Installed=>File.Exists(CSCPath);
|
||||
public static string CSCPath =>
|
||||
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), nameof(BITKit),"DotnetSdkRoslyn","csc.dll");
|
||||
public static string FolderPath =>
|
||||
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), nameof(BITKit),"DotnetSdkRoslyn");
|
||||
public static event Action OnInstall;
|
||||
public static event Action OnInstalled;
|
||||
public static event Action OnUnInstalled;
|
||||
public static event Action<Exception> OnInstallFailed;
|
||||
public static event Action<float> OnDownloadProgress;
|
||||
|
||||
private static string[] _InstalledFiles
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!PlayerPrefs.HasKey(_InstalledFilesKey)) return Array.Empty<string>();
|
||||
var json = PlayerPrefs.GetString(_InstalledFilesKey);
|
||||
try
|
||||
{
|
||||
return JsonConvert.DeserializeObject<string[]>(json);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
BIT4Log.LogException(e);
|
||||
}
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
set
|
||||
{
|
||||
PlayerPrefs.SetString(_InstalledFilesKey, JsonConvert.SerializeObject(value));
|
||||
PlayerPrefs.Save();
|
||||
}
|
||||
}
|
||||
|
||||
public static async UniTask Install()
|
||||
{
|
||||
await UniTask.SwitchToMainThread();
|
||||
OnInstall?.Invoke();
|
||||
var request = UnityWebRequest.Get(DownloadUrl);
|
||||
try
|
||||
{
|
||||
await request.SendWebRequest();
|
||||
while (request.isDone is false)
|
||||
{
|
||||
await UniTask.SwitchToMainThread();
|
||||
OnDownloadProgress?.Invoke(request.downloadProgress);
|
||||
await UniTask.NextFrame();
|
||||
}
|
||||
OnDownloadProgress?.Invoke(1);
|
||||
|
||||
using var ms = new MemoryStream(request.downloadHandler.data);
|
||||
|
||||
request.Dispose();
|
||||
|
||||
await UniTask.SwitchToTaskPool();
|
||||
var zipArchive = new ZipArchive(ms);
|
||||
|
||||
PathHelper.EnsureDirectoryCreated(FolderPath);
|
||||
|
||||
List<string> installedFiles = new();
|
||||
|
||||
var reportBuilder =new StringBuilder();
|
||||
reportBuilder.AppendLine($"正在安装文件到:{FolderPath}");
|
||||
|
||||
var entryFolder = zipArchive.Entries[0].FullName;
|
||||
foreach (var zipArchiveEntry in zipArchive.Entries)
|
||||
{
|
||||
var entryPath = Path.Combine(FolderPath, zipArchiveEntry.FullName.Replace(entryFolder,string.Empty));
|
||||
if (zipArchiveEntry.Name is "")
|
||||
{
|
||||
reportBuilder.AppendLine($"正在创建目录:{entryPath}");
|
||||
PathHelper.EnsureDirectoryCreated(entryPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
reportBuilder.AppendLine($"正在解压文件:{entryPath}");
|
||||
await using var entryStream = zipArchiveEntry.Open();
|
||||
await using var fs = System.IO.File.Create(entryPath);
|
||||
await entryStream.CopyToAsync(fs);
|
||||
installedFiles.Add(entryPath);
|
||||
}
|
||||
}
|
||||
await UniTask.SwitchToMainThread();
|
||||
_InstalledFiles = installedFiles.ToArray();
|
||||
installedFiles.Clear();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
await UniTask.SwitchToMainThread();
|
||||
request.Dispose();
|
||||
OnInstallFailed?.Invoke(e);
|
||||
return;
|
||||
}
|
||||
OnInstalled?.Invoke();
|
||||
}
|
||||
public static async UniTask UnInstall()
|
||||
{
|
||||
if (Installed is false)
|
||||
{
|
||||
BIT4Log.Warning<DotNetSdkRoslynService>("未安装,无法卸载");
|
||||
return;
|
||||
}
|
||||
await UniTask.SwitchToMainThread();
|
||||
var files = _InstalledFiles;
|
||||
await UniTask.SwitchToTaskPool();
|
||||
foreach (var path in files)
|
||||
{
|
||||
File.Delete(path);
|
||||
}
|
||||
await UniTask.SwitchToMainThread();
|
||||
PlayerPrefs.DeleteKey(_InstalledFilesKey);
|
||||
PlayerPrefs.Save();
|
||||
OnUnInstalled?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 56389bdbeb2065541a8e62a4ae5bd746
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -1,98 +0,0 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using BITKit.Mod;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
using static BITKit.Mod.DotNetSdkRoslynService;
|
||||
namespace BITKit.UX
|
||||
{
|
||||
public class UXDotNetSdkRoslynService : MonoBehaviour
|
||||
{
|
||||
[UXBindPath("install-roslyn-button")]
|
||||
private Button _installButton;
|
||||
[UXBindPath("uninstall-roslyn-button")]
|
||||
private Button _uninstallButton;
|
||||
[UXBindPath("install-roslyn-fill")]
|
||||
private VisualElement _installFill;
|
||||
private void OnEnable()
|
||||
{
|
||||
OnInstall+=_OnInstall;
|
||||
OnInstalled+=_OnInstalled;
|
||||
OnUnInstalled+=_OnUnInstalled;
|
||||
OnDownloadProgress+=_OnDownloadProgress;
|
||||
OnInstallFailed += _OnInstallFailed;
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
OnInstall-=_OnInstall;
|
||||
OnInstalled-=_OnInstalled;
|
||||
OnUnInstalled-=_OnUnInstalled;
|
||||
OnDownloadProgress-=_OnDownloadProgress;
|
||||
OnInstallFailed -= _OnInstallFailed;
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
UXUtils.Inject(this);
|
||||
|
||||
if (Installed)
|
||||
{
|
||||
_OnInstalled();
|
||||
}
|
||||
else
|
||||
{
|
||||
_OnUnInstalled();
|
||||
}
|
||||
_installButton.clicked +=()=>Install().Forget();
|
||||
_uninstallButton.clicked +=()=>
|
||||
{
|
||||
_uninstallButton.SetEnabled(false);
|
||||
UnInstall().Forget();
|
||||
};
|
||||
|
||||
}
|
||||
private void _OnInstall()
|
||||
{
|
||||
_installButton.text = "准备中...";
|
||||
_installButton.SetEnabled(false);
|
||||
_uninstallButton.SetEnabled(false);
|
||||
}
|
||||
private void _OnUnInstalled()
|
||||
{
|
||||
_installButton.text = "安装";
|
||||
_uninstallButton.SetEnabled(false);
|
||||
_installButton.SetEnabled(true);
|
||||
_installFill.style.width = new StyleLength(Length.Percent(0));
|
||||
}
|
||||
private void _OnInstalled()
|
||||
{
|
||||
_installButton.text = "重新安装";
|
||||
_installButton.SetEnabled(true);
|
||||
_uninstallButton.SetEnabled(true);
|
||||
_installFill.style.width = new StyleLength(Length.Percent(0));
|
||||
}
|
||||
private void _OnDownloadProgress(float progress)
|
||||
{
|
||||
if (progress >= 1)
|
||||
{
|
||||
_installButton.text = "安装中...";
|
||||
}
|
||||
else
|
||||
{
|
||||
_installButton.text = $"下载中...{progress:P}";
|
||||
}
|
||||
|
||||
_installFill.style.width = new StyleLength(Length.Percent((int)(progress * 100)));
|
||||
}
|
||||
|
||||
private void _OnInstallFailed(Exception e)
|
||||
{
|
||||
_installButton.tooltip = e.Message;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 8c8c71e08d0a2544aae8e62cd2b86af2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
Loading…
Reference in New Issue