提交 6de25cef 编写于 作者: T tanghai

服务器代码通过文件引用的方式共用客户端代码

上级 748d7e71
using System;
using System.IO;
using System.Reflection;
using Base;
using Model;
using Object = Base.Object;
namespace App
{
class Program
{
static void Main(string[] args)
{
try
{
Log.Info("server start........................");
Object.ObjectManager.Register("Base", typeof(Game).Assembly);
Object.ObjectManager.Register("Model", typeof(Opcode).Assembly);
byte[] dllBytes = File.ReadAllBytes("./Controller.dll");
byte[] pdbBytes = File.ReadAllBytes("./Controller.pdb");
Assembly controller = Assembly.Load(dllBytes, pdbBytes);
Object.ObjectManager.Register("Controller", controller);
Game.Scene.AddComponent<EventComponent>();
TimeComponent timeComponent = Game.Scene.AddComponent<TimeComponent>();
Game.Scene.AddComponent<TimerComponent, TimeComponent>(timeComponent);
Game.Scene.AddComponent<NetworkComponent, NetworkProtocol>(NetworkProtocol.UDP);
Game.Scene.AddComponent<MessageHandlerComponent, MessageType>(MessageType.Realm);
while (true)
{
Object.ObjectManager.Update();
}
}
catch (Exception e)
{
Log.Error(e.ToString());
}
}
}
}
......@@ -7,8 +7,8 @@
<ProjectGuid>{3F8DC04C-9E05-403F-B6A5-36293EB99937}</ProjectGuid>
<OutputType>Exe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Server.App</RootNamespace>
<AssemblyName>Server.App</AssemblyName>
<RootNamespace>App</RootNamespace>
<AssemblyName>App</AssemblyName>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
......@@ -55,12 +55,12 @@
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Server.Base\Server.Base.csproj">
<Project>{afbad3d5-c827-4ba6-9940-7060b6c39306}</Project>
<ProjectReference Include="..\Base\Server.Base.csproj">
<Project>{e5078ec6-2b0e-4711-be8b-d99f69638316}</Project>
<Name>Server.Base</Name>
</ProjectReference>
<ProjectReference Include="..\Server.Model\Server.Model.csproj">
<Project>{e997a298-c1e1-4dc7-85a3-4b0e2a6e196d}</Project>
<ProjectReference Include="..\Model\Server.Model.csproj">
<Project>{820d3488-76b9-4ee8-872a-be06c2350b20}</Project>
<Name>Server.Model</Name>
</ProjectReference>
</ItemGroup>
......
......@@ -4,42 +4,24 @@
{
private static readonly ILog globalLog = new NLogAdapter(new StackInfoDecorater());
private static ILog GlobalLog
public static void Warning(string message)
{
get
{
return globalLog;
}
globalLog.Warning(message);
}
public static void Info(string message)
{
GlobalLog.Info(message);
}
public static void Info(string format, params object[] args)
{
GlobalLog.Info(string.Format(format, args));
globalLog.Info(message);
}
public static void Debug(string format)
{
GlobalLog.Debug(format);
}
public static void Debug(string format, params object[] args)
{
GlobalLog.Debug(string.Format(format, args));
globalLog.Debug(format);
}
public static void Error(string format)
{
GlobalLog.Error(format);
}
public static void Error(string format, params object[] args)
{
GlobalLog.Error(string.Format(format, args));
globalLog.Error(format);
}
}
}
\ No newline at end of file
}
......@@ -2,6 +2,7 @@
{
public interface ILog
{
void Warning(string message);
void Info(string message);
void Debug(string message);
void Error(string message);
......
......@@ -4,12 +4,17 @@ namespace Base
{
public class NLogAdapter: ALogDecorater, ILog
{
private readonly NLog.Logger logger = LogManager.GetCurrentClassLogger();
private readonly Logger logger = LogManager.GetCurrentClassLogger();
public NLogAdapter(ALogDecorater decorater = null): base(decorater)
{
}
public void Warning(string message)
{
this.logger.Warn(this.Decorate(SEP + message));
}
public void Info(string message)
{
this.logger.Info(this.Decorate(SEP + message));
......
......@@ -20,7 +20,7 @@ using System.Runtime.InteropServices;
[assembly: ComVisible(false)]
// 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID
[assembly: Guid("afbad3d5-c827-4ba6-9940-7060b6c39306")]
[assembly: Guid("e5078ec6-2b0e-4711-be8b-d99f69638316")]
// 程序集的版本信息由下列四个值组成:
//
......
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{E5078EC6-2B0E-4711-BE8B-D99F69638316}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Base</RootNamespace>
<AssemblyName>Base</AssemblyName>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>..\Bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="ICSharpCode.SharpZipLib">
<HintPath>..\Lib\ICSharpCode.SharpZipLib.dll</HintPath>
</Reference>
<Reference Include="MongoDB.Bson">
<HintPath>..\Lib\MongoDB.Bson.dll</HintPath>
</Reference>
<Reference Include="NLog">
<HintPath>..\Lib\NLog.dll</HintPath>
</Reference>
<Reference Include="protobuf-net">
<HintPath>..\Lib\protobuf-net.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\..\Unity\Assets\Plugins\Base\Component\EventComponent.cs">
<Link>Component\EventComponent.cs</Link>
</Compile>
<Compile Include="..\..\Unity\Assets\Plugins\Base\Component\KVComponent.cs">
<Link>Component\KVComponent.cs</Link>
</Compile>
<Compile Include="..\..\Unity\Assets\Plugins\Base\Component\MessageComponent.cs">
<Link>Component\MessageComponent.cs</Link>
</Compile>
<Compile Include="..\..\Unity\Assets\Plugins\Base\Component\MessageHandlerComponent.cs">
<Link>Component\MessageHandlerComponent.cs</Link>
</Compile>
<Compile Include="..\..\Unity\Assets\Plugins\Base\Component\NetworkComponent.cs">
<Link>Component\NetworkComponent.cs</Link>
</Compile>
<Compile Include="..\..\Unity\Assets\Plugins\Base\Component\TimeComponent.cs">
<Link>Component\TimeComponent.cs</Link>
</Compile>
<Compile Include="..\..\Unity\Assets\Plugins\Base\Component\TimerComponent.cs">
<Link>Component\TimerComponent.cs</Link>
</Compile>
<Compile Include="..\..\Unity\Assets\Plugins\Base\Config\ACategory.cs">
<Link>Config\ACategory.cs</Link>
</Compile>
<Compile Include="..\..\Unity\Assets\Plugins\Base\Config\AConfig.cs">
<Link>Config\AConfig.cs</Link>
</Compile>
<Compile Include="..\..\Unity\Assets\Plugins\Base\Config\ConfigAttribute.cs">
<Link>Config\ConfigAttribute.cs</Link>
</Compile>
<Compile Include="..\..\Unity\Assets\Plugins\Base\Config\ICategory.cs">
<Link>Config\ICategory.cs</Link>
</Compile>
<Compile Include="..\..\Unity\Assets\Plugins\Base\DoubleMap.cs">
<Link>DoubleMap.cs</Link>
</Compile>
<Compile Include="..\..\Unity\Assets\Plugins\Base\Event\AEventAttribute.cs">
<Link>Event\AEventAttribute.cs</Link>
</Compile>
<Compile Include="..\..\Unity\Assets\Plugins\Base\Event\Env.cs">
<Link>Event\Env.cs</Link>
</Compile>
<Compile Include="..\..\Unity\Assets\Plugins\Base\Event\EnvBaseKey.cs">
<Link>Event\EnvBaseKey.cs</Link>
</Compile>
<Compile Include="..\..\Unity\Assets\Plugins\Base\Event\EventAttribute.cs">
<Link>Event\EventAttribute.cs</Link>
</Compile>
<Compile Include="..\..\Unity\Assets\Plugins\Base\Event\EventBaseType.cs">
<Link>Event\EventBaseType.cs</Link>
</Compile>
<Compile Include="..\..\Unity\Assets\Plugins\Base\Event\IEvent.cs">
<Link>Event\IEvent.cs</Link>
</Compile>
<Compile Include="..\..\Unity\Assets\Plugins\Base\Game.cs">
<Link>Game.cs</Link>
</Compile>
<Compile Include="..\..\Unity\Assets\Plugins\Base\Helper\ArrayHelper.cs">
<Link>Helper\ArrayHelper.cs</Link>
</Compile>
<Compile Include="..\..\Unity\Assets\Plugins\Base\Helper\ByteHelper.cs">
<Link>Helper\ByteHelper.cs</Link>
</Compile>
<Compile Include="..\..\Unity\Assets\Plugins\Base\Helper\EnumHelper.cs">
<Link>Helper\EnumHelper.cs</Link>
</Compile>
<Compile Include="..\..\Unity\Assets\Plugins\Base\Helper\FileHelper.cs">
<Link>Helper\FileHelper.cs</Link>
</Compile>
<Compile Include="..\..\Unity\Assets\Plugins\Base\Helper\IdGenerater.cs">
<Link>Helper\IdGenerater.cs</Link>
</Compile>
<Compile Include="..\..\Unity\Assets\Plugins\Base\Helper\MD5Helper.cs">
<Link>Helper\MD5Helper.cs</Link>
</Compile>
<Compile Include="..\..\Unity\Assets\Plugins\Base\Helper\MongoHelper.cs">
<Link>Helper\MongoHelper.cs</Link>
</Compile>
<Compile Include="..\..\Unity\Assets\Plugins\Base\Helper\ProtobufHelper.cs">
<Link>Helper\ProtobufHelper.cs</Link>
</Compile>
<Compile Include="..\..\Unity\Assets\Plugins\Base\Helper\RandomHelper.cs">
<Link>Helper\RandomHelper.cs</Link>
</Compile>
<Compile Include="..\..\Unity\Assets\Plugins\Base\Helper\StringHelper.cs">
<Link>Helper\StringHelper.cs</Link>
</Compile>
<Compile Include="..\..\Unity\Assets\Plugins\Base\Helper\TimeHelper.cs">
<Link>Helper\TimeHelper.cs</Link>
</Compile>
<Compile Include="..\..\Unity\Assets\Plugins\Base\Helper\ZipHelper.cs">
<Link>Helper\ZipHelper.cs</Link>
</Compile>
<Compile Include="..\..\Unity\Assets\Plugins\Base\Message\AMEvent.cs">
<Link>Message\AMEvent.cs</Link>
</Compile>
<Compile Include="..\..\Unity\Assets\Plugins\Base\Message\IErrorMessage.cs">
<Link>Message\IErrorMessage.cs</Link>
</Compile>
<Compile Include="..\..\Unity\Assets\Plugins\Base\Message\IMRegister.cs">
<Link>Message\IMRegister.cs</Link>
</Compile>
<Compile Include="..\..\Unity\Assets\Plugins\Base\Message\MessageAttribute.cs">
<Link>Message\MessageAttribute.cs</Link>
</Compile>
<Compile Include="..\..\Unity\Assets\Plugins\Base\Message\RpcException.cs">
<Link>Message\RpcException.cs</Link>
</Compile>
<Compile Include="..\..\Unity\Assets\Plugins\Base\MultiMap.cs">
<Link>MultiMap.cs</Link>
</Compile>
<Compile Include="..\..\Unity\Assets\Plugins\Base\Network\AChannel.cs">
<Link>Network\AChannel.cs</Link>
</Compile>
<Compile Include="..\..\Unity\Assets\Plugins\Base\Network\AService.cs">
<Link>Network\AService.cs</Link>
</Compile>
<Compile Include="..\..\Unity\Assets\Plugins\Base\Network\TNet\PacketParser.cs">
<Link>Network\TNet\PacketParser.cs</Link>
</Compile>
<Compile Include="..\..\Unity\Assets\Plugins\Base\Network\TNet\TBuffer.cs">
<Link>Network\TNet\TBuffer.cs</Link>
</Compile>
<Compile Include="..\..\Unity\Assets\Plugins\Base\Network\TNet\TChannel.cs">
<Link>Network\TNet\TChannel.cs</Link>
</Compile>
<Compile Include="..\..\Unity\Assets\Plugins\Base\Network\TNet\TPoller.cs">
<Link>Network\TNet\TPoller.cs</Link>
</Compile>
<Compile Include="..\..\Unity\Assets\Plugins\Base\Network\TNet\TService.cs">
<Link>Network\TNet\TService.cs</Link>
</Compile>
<Compile Include="..\..\Unity\Assets\Plugins\Base\Network\TNet\TSocket.cs">
<Link>Network\TNet\TSocket.cs</Link>
</Compile>
<Compile Include="..\..\Unity\Assets\Plugins\Base\Network\UNet\Library.cs">
<Link>Network\UNet\Library.cs</Link>
</Compile>
<Compile Include="..\..\Unity\Assets\Plugins\Base\Network\UNet\NativeMethods.cs">
<Link>Network\UNet\NativeMethods.cs</Link>
</Compile>
<Compile Include="..\..\Unity\Assets\Plugins\Base\Network\UNet\NativeStructs.cs">
<Link>Network\UNet\NativeStructs.cs</Link>
</Compile>
<Compile Include="..\..\Unity\Assets\Plugins\Base\Network\UNet\UAddress.cs">
<Link>Network\UNet\UAddress.cs</Link>
</Compile>
<Compile Include="..\..\Unity\Assets\Plugins\Base\Network\UNet\UChannel.cs">
<Link>Network\UNet\UChannel.cs</Link>
</Compile>
<Compile Include="..\..\Unity\Assets\Plugins\Base\Network\UNet\UPacket.cs">
<Link>Network\UNet\UPacket.cs</Link>
</Compile>
<Compile Include="..\..\Unity\Assets\Plugins\Base\Network\UNet\UPoller.cs">
<Link>Network\UNet\UPoller.cs</Link>
</Compile>
<Compile Include="..\..\Unity\Assets\Plugins\Base\Network\UNet\UService.cs">
<Link>Network\UNet\UService.cs</Link>
</Compile>
<Compile Include="..\..\Unity\Assets\Plugins\Base\Network\UNet\USocket.cs">
<Link>Network\UNet\USocket.cs</Link>
</Compile>
<Compile Include="..\..\Unity\Assets\Plugins\Base\Network\UNet\USocketManager.cs">
<Link>Network\UNet\USocketManager.cs</Link>
</Compile>
<Compile Include="..\..\Unity\Assets\Plugins\Base\Object\Component.cs">
<Link>Object\Component.cs</Link>
</Compile>
<Compile Include="..\..\Unity\Assets\Plugins\Base\Object\Entity.cs">
<Link>Object\Entity.cs</Link>
</Compile>
<Compile Include="..\..\Unity\Assets\Plugins\Base\Object\IAwake.cs">
<Link>Object\IAwake.cs</Link>
</Compile>
<Compile Include="..\..\Unity\Assets\Plugins\Base\Object\ILoader.cs">
<Link>Object\ILoader.cs</Link>
</Compile>
<Compile Include="..\..\Unity\Assets\Plugins\Base\Object\IStart.cs">
<Link>Object\IStart.cs</Link>
</Compile>
<Compile Include="..\..\Unity\Assets\Plugins\Base\Object\IUpdate.cs">
<Link>Object\IUpdate.cs</Link>
</Compile>
<Compile Include="..\..\Unity\Assets\Plugins\Base\Object\Object.cs">
<Link>Object\Object.cs</Link>
</Compile>
<Compile Include="..\..\Unity\Assets\Plugins\Base\Object\ObjectEventAttribute.cs">
<Link>Object\ObjectEventAttribute.cs</Link>
</Compile>
<Compile Include="..\..\Unity\Assets\Plugins\Base\Object\ObjectManager.cs">
<Link>Object\ObjectManager.cs</Link>
</Compile>
<Compile Include="..\..\Unity\Assets\Plugins\Base\QueueDictionary.cs">
<Link>QueueDictionary.cs</Link>
</Compile>
<Compile Include="..\..\Unity\Assets\Plugins\Base\TryLocker.cs">
<Link>TryLocker.cs</Link>
</Compile>
<Compile Include="Log.cs" />
<Compile Include="Logger\ALogDecorater.cs" />
<Compile Include="Logger\ILog.cs" />
<Compile Include="Logger\NLogAdapter.cs" />
<Compile Include="Logger\StackInfoDecorater.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>
\ No newline at end of file
......@@ -20,7 +20,7 @@ using System.Runtime.InteropServices;
[assembly: ComVisible(false)]
// 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID
[assembly: Guid("ed2a5401-3829-4a8b-915d-a2a18fb233f8")]
[assembly: Guid("3878bd71-2f75-4edf-882f-bc708154b1b0")]
// 程序集的版本信息由下列四个值组成:
//
......
......@@ -4,7 +4,7 @@
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{ED2A5401-3829-4A8B-915D-A2A18FB233F8}</ProjectGuid>
<ProjectGuid>{3878BD71-2F75-4EDF-882F-BC708154B1B0}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Controller</RootNamespace>
......@@ -43,7 +43,14 @@
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<Folder Include="Message\" />
<ProjectReference Include="..\Base\Server.Base.csproj">
<Project>{e5078ec6-2b0e-4711-be8b-d99f69638316}</Project>
<Name>Server.Base</Name>
</ProjectReference>
<ProjectReference Include="..\Model\Server.Model.csproj">
<Project>{820d3488-76b9-4ee8-872a-be06c2350b20}</Project>
<Name>Server.Model</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
......
......@@ -20,7 +20,7 @@ using System.Runtime.InteropServices;
[assembly: ComVisible(false)]
// 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID
[assembly: Guid("e997a298-c1e1-4dc7-85a3-4b0e2a6e196d")]
[assembly: Guid("820d3488-76b9-4ee8-872a-be06c2350b20")]
// 程序集的版本信息由下列四个值组成:
//
......
......@@ -4,7 +4,7 @@
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{E997A298-C1E1-4DC7-85A3-4B0E2A6E196D}</ProjectGuid>
<ProjectGuid>{820D3488-76B9-4EE8-872A-BE06C2350B20}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Model</RootNamespace>
......@@ -33,9 +33,6 @@
<Reference Include="MongoDB.Bson">
<HintPath>..\Lib\MongoDB.Bson.dll</HintPath>
</Reference>
<Reference Include="protobuf-net">
<HintPath>..\Lib\protobuf-net.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
......@@ -46,35 +43,23 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Event\AEventAttribute.cs" />
<Compile Include="Message\AMEvent.cs" />
<Compile Include="Component\EventComponent.cs" />
<Compile Include="Component\MessageHandlerComponent.cs" />
<Compile Include="Component\MessageComponent.cs" />
<Compile Include="Component\NetworkComponent.cs" />
<Compile Include="Component\Scene.cs" />
<Compile Include="Component\TimeComponent.cs" />
<Compile Include="Component\TimerComponent.cs" />
<Compile Include="Event\EntityType.cs" />
<Compile Include="Event\Env.cs" />
<Compile Include="Event\EnvKey.cs" />
<Compile Include="Message\ErrorCode.cs" />
<Compile Include="Event\EventAttribute.cs" />
<Compile Include="Event\EventIdType.cs" />
<Compile Include="GameException.cs" />
<Compile Include="Message\IErrorMessage.cs" />
<Compile Include="Event\IEvent.cs" />
<Compile Include="Message\IMRegister.cs" />
<Compile Include="Message\Message.cs" />
<Compile Include="Message\MessageAttribute.cs" />
<Compile Include="Message\Opcode.cs" />
<Compile Include="Message\OpcodeHelper.cs" />
<Compile Include="..\..\Unity\Assets\Scripts\Message\ErrorCode.cs">
<Link>Message\ErrorCode.cs</Link>
</Compile>
<Compile Include="..\..\Unity\Assets\Scripts\Message\Message.cs">
<Link>Message\Message.cs</Link>
</Compile>
<Compile Include="..\..\Unity\Assets\Scripts\Message\Opcode.cs">
<Link>Message\Opcode.cs</Link>
</Compile>
<Compile Include="..\..\Unity\Assets\Scripts\Message\OpcodeHelper.cs">
<Link>Message\OpcodeHelper.cs</Link>
</Compile>
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Scene\Server.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Server.Base\Server.Base.csproj">
<Project>{afbad3d5-c827-4ba6-9940-7060b6c39306}</Project>
<ProjectReference Include="..\Base\Server.Base.csproj">
<Project>{e5078ec6-2b0e-4711-be8b-d99f69638316}</Project>
<Name>Server.Base</Name>
</ProjectReference>
</ItemGroup>
......
using System;
using Base;
using Object = Base.Object;
namespace App
{
class Program
{
static void Main(string[] args)
{
try
{
Object.ObjectManager.Register("Model", typeof(Scene).Assembly);
Server.Scene.AddComponent<EventComponent>();
Server.Scene.AddComponent<TimerComponent>();
Server.Scene.AddComponent<NetworkComponent, NetworkProtocol>(NetworkProtocol.UDP);
Server.Scene.AddComponent<Scene, SceneType, string>(SceneType.Realm, "realm");
Server.Scene.AddComponent<MessageHandlerComponent, SceneType>(Server.Scene.GetComponent<Scene>().SceneType);
while (true)
{
Object.ObjectManager.Update();
}
}
catch (Exception e)
{
Log.Error(e.ToString());
}
}
}
}
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace Base
{
/// <summary>
/// 管理该所有的配置
/// </summary>
/// <typeparam name="T"></typeparam>
public abstract class ACategory<T>: ICategory where T : AConfig
{
protected Dictionary<long, T> dict;
public virtual void BeginInit()
{
this.dict = new Dictionary<long, T>();
string path = $@"Config/{typeof (T).Name}";
string configStr;
try
{
configStr = File.ReadAllText(path);
}
catch (Exception)
{
throw new Exception($"load config file fail, path: {path}");
}
foreach (string str in configStr.Split(new[] { "\r\n" }, StringSplitOptions.None))
{
try
{
string str2 = str.Trim();
if (str2 == "")
{
continue;
}
T t = MongoHelper.FromJson<T>(str2);
this.dict.Add(t.Id, t);
}
catch (Exception e)
{
throw new Exception($"parser json fail: {str}", e);
}
}
}
public Type ConfigType
{
get
{
return typeof (T);
}
}
public virtual void EndInit()
{
}
public T this[long type]
{
get
{
T t;
if (!this.dict.TryGetValue(type, out t))
{
throw new Exception($"{typeof (T)} 没有找到配置, key: {type}");
}
return t;
}
}
public T TryGet(int type)
{
T t;
if (!this.dict.TryGetValue(type, out t))
{
return null;
}
return t;
}
public T[] GetAll()
{
return this.dict.Values.ToArray();
}
public T GetOne()
{
return this.dict.Values.First();
}
}
}
\ No newline at end of file
using System.ComponentModel;
using MongoDB.Bson.Serialization.Attributes;
namespace Base
{
/// <summary>
/// 每个Config的基类
/// </summary>
public abstract class AConfig: ISupportInitialize
{
[BsonId]
public long Id { get; set; }
public virtual void BeginInit()
{
}
public virtual void EndInit()
{
}
}
}
\ No newline at end of file
using System;
namespace Base
{
[AttributeUsage(AttributeTargets.Class)]
public class ConfigAttribute: Attribute
{
}
}
\ No newline at end of file
using System;
using System.ComponentModel;
namespace Base
{
public interface ICategory: ISupportInitialize
{
Type ConfigType { get; }
}
}
\ No newline at end of file
using System;
using System.Collections.Generic;
namespace Base
{
public class DoubleMap<K, V>
{
private readonly Dictionary<K, V> kv = new Dictionary<K, V>();
private readonly Dictionary<V, K> vk = new Dictionary<V, K>();
public DoubleMap()
{
}
public DoubleMap(int capacity)
{
kv = new Dictionary<K, V>(capacity);
vk = new Dictionary<V, K>(capacity);
}
public void ForEach(Action<K, V> action)
{
if (action == null)
{
return;
}
Dictionary<K, V>.KeyCollection keys = kv.Keys;
foreach (K key in keys)
{
action(key, kv[key]);
}
}
public List<K> Keys
{
get
{
return new List<K>(kv.Keys);
}
}
public List<V> Values
{
get
{
return new List<V>(vk.Keys);
}
}
public void Add(K key, V value)
{
if (key == null || value == null || kv.ContainsKey(key) || vk.ContainsKey(value))
{
return;
}
kv.Add(key, value);
vk.Add(value, key);
}
public V GetValueByKey(K key)
{
if (key != null && kv.ContainsKey(key))
{
return kv[key];
}
return default(V);
}
public K GetKeyByValue(V value)
{
if (value != null && vk.ContainsKey(value))
{
return vk[value];
}
return default(K);
}
public void RemoveByKey(K key)
{
if (key == null)
{
return;
}
V value;
if (!kv.TryGetValue(key, out value))
{
return;
}
kv.Remove(key);
vk.Remove(value);
}
public void RemoveByValue(V value)
{
if (value == null)
{
return;
}
K key;
if (!vk.TryGetValue(value, out key))
{
return;
}
kv.Remove(key);
vk.Remove(value);
}
public void Clear()
{
kv.Clear();
vk.Clear();
}
public bool ContainsKey(K key)
{
if (key == null)
{
return false;
}
return kv.ContainsKey(key);
}
public bool ContainsValue(V value)
{
if (value == null)
{
return false;
}
return vk.ContainsKey(value);
}
public bool Contains(K key, V value)
{
if (key == null || value == null)
{
return false;
}
return kv.ContainsKey(key) && vk.ContainsKey(value);
}
}
}
\ No newline at end of file
namespace Base
{
public static class ObjectHelper
{
public static void Swap<T>(ref T t1, ref T t2)
{
T t3 = t1;
t1 = t2;
t2 = t3;
}
}
}
\ No newline at end of file
using System;
using System.Text;
namespace Base
{
public static class ByteHelper
{
public static string ToHex(this byte b)
{
return b.ToString("X2");
}
public static string ToHex(this byte[] bytes)
{
StringBuilder stringBuilder = new StringBuilder();
foreach (byte b in bytes)
{
stringBuilder.Append(b.ToString("X2"));
}
return stringBuilder.ToString();
}
public static string ToHex(this byte[] bytes, string format)
{
StringBuilder stringBuilder = new StringBuilder();
foreach (byte b in bytes)
{
stringBuilder.Append(b.ToString(format));
}
return stringBuilder.ToString();
}
public static string ToHex(this byte[] bytes, int offset, int count)
{
StringBuilder stringBuilder = new StringBuilder();
for (int i = offset; i < offset + count; ++i)
{
stringBuilder.Append(bytes[i].ToString("X2"));
}
return stringBuilder.ToString();
}
public static string ToStr(this byte[] bytes)
{
return Encoding.Default.GetString(bytes);
}
public static string Utf8ToStr(this byte[] bytes)
{
return Encoding.UTF8.GetString(bytes);
}
public static byte[] Reverse(this byte[] bytes)
{
Array.Reverse(bytes);
return bytes;
}
}
}
\ No newline at end of file
using System;
namespace Base
{
public static class EnumHelper
{
public static int EnumIndex<T>(int value)
{
int i = 0;
foreach (object v in Enum.GetValues(typeof (T)))
{
if ((int) v == value)
{
return i;
}
++i;
}
return -1;
}
public static T FromString<T>(string str)
{
if (!Enum.IsDefined(typeof(T), str))
{
return default(T);
}
return (T)Enum.Parse(typeof(T), str);
}
}
}
\ No newline at end of file
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
namespace Base
{
public static class FileHelper
{
public static void CleanDirectory(string dir)
{
foreach (string subdir in Directory.GetDirectories(dir))
{
Directory.Delete(subdir, true);
}
foreach (string subFile in Directory.GetFiles(dir))
{
File.Delete(subFile);
}
}
public static void CopyDirectory(string srcDir, string tgtDir)
{
DirectoryInfo source = new DirectoryInfo(srcDir);
DirectoryInfo target = new DirectoryInfo(tgtDir);
if (target.FullName.StartsWith(source.FullName, StringComparison.CurrentCultureIgnoreCase))
{
throw new Exception("父目录不能拷贝到子目录!");
}
if (!source.Exists)
{
return;
}
if (!target.Exists)
{
target.Create();
}
FileInfo[] files = source.GetFiles();
for (int i = 0; i < files.Length; i++)
{
File.Copy(files[i].FullName, target.FullName + @"\" + files[i].Name, true);
}
DirectoryInfo[] dirs = source.GetDirectories();
for (int j = 0; j < dirs.Length; j++)
{
CopyDirectory(dirs[j].FullName, target.FullName + @"\" + dirs[j].Name);
}
}
}
}
namespace Base
{
public static class IdGenerater
{
private static long value = long.MaxValue;
public static long GenerateId()
{
return --value;
}
}
}
\ No newline at end of file
using System.IO;
using System.Security.Cryptography;
namespace Base
{
public static class MD5Helper
{
public static string FileMD5(string filePath)
{
byte[] retVal;
using (FileStream file = new FileStream(filePath, FileMode.Open))
{
MD5 md5 = new MD5CryptoServiceProvider();
retVal = md5.ComputeHash(file);
}
return retVal.ToHex("x2");
}
}
}
using System;
using System.IO;
using MongoDB.Bson;
using MongoDB.Bson.IO;
using MongoDB.Bson.Serialization;
namespace Base
{
public static class MongoHelper
{
public static string ToJson(object obj)
{
return obj.ToJson();
}
public static string ToJson(object obj, JsonWriterSettings settings)
{
return obj.ToJson(settings);
}
public static T FromJson<T>(string str)
{
return BsonSerializer.Deserialize<T>(str);
}
public static object FromJson(Type type, string str)
{
return BsonSerializer.Deserialize(str, type);
}
public static byte[] ToBson(object obj)
{
return obj.ToBson();
}
public static object FromBson(Type type, byte[] bytes)
{
return BsonSerializer.Deserialize(bytes, type);
}
public static object FromBson(Type type, byte[] bytes, int index, int count)
{
using (MemoryStream memoryStream = new MemoryStream(bytes, index, count))
{
return BsonSerializer.Deserialize(memoryStream, type);
}
}
public static T FromBson<T>(byte[] bytes)
{
using (MemoryStream memoryStream = new MemoryStream(bytes))
{
return (T) BsonSerializer.Deserialize(memoryStream, typeof (T));
}
}
public static T FromBson<T>(byte[] bytes, int index, int count)
{
return (T) FromBson(typeof (T), bytes, index, count);
}
public static T Clone<T>(T t)
{
return FromBson<T>(ToBson(t));
}
}
}
\ No newline at end of file
using System;
using System.ComponentModel;
using System.IO;
using ProtoBuf;
namespace Base
{
public static class ProtobufHelper
{
public static byte[] ToBytes(object message)
{
using (MemoryStream ms = new MemoryStream())
{
Serializer.Serialize(ms, message);
return ms.ToArray();
}
}
public static T FromBytes<T>(byte[] bytes)
{
T t;
using (MemoryStream ms = new MemoryStream(bytes, 0, bytes.Length))
{
t = Serializer.Deserialize<T>(ms);
}
ISupportInitialize iSupportInitialize = t as ISupportInitialize;
if (iSupportInitialize == null)
{
return t;
}
iSupportInitialize.EndInit();
return t;
}
public static T FromBytes<T>(byte[] bytes, int index, int length)
{
T t;
using (MemoryStream ms = new MemoryStream(bytes, index, length))
{
t = Serializer.Deserialize<T>(ms);
}
ISupportInitialize iSupportInitialize = t as ISupportInitialize;
if (iSupportInitialize == null)
{
return t;
}
iSupportInitialize.EndInit();
return t;
}
public static object FromBytes(Type type, byte[] bytes)
{
object t;
using (MemoryStream ms = new MemoryStream(bytes, 0, bytes.Length))
{
t = Serializer.NonGeneric.Deserialize(type, ms);
}
ISupportInitialize iSupportInitialize = t as ISupportInitialize;
if (iSupportInitialize == null)
{
return t;
}
iSupportInitialize.EndInit();
return t;
}
public static object FromBytes(Type type, byte[] bytes, int index, int length)
{
object t;
using (MemoryStream ms = new MemoryStream(bytes, index, length))
{
t = Serializer.NonGeneric.Deserialize(type, ms);
}
ISupportInitialize iSupportInitialize = t as ISupportInitialize;
if (iSupportInitialize == null)
{
return t;
}
iSupportInitialize.EndInit();
return t;
}
}
}
\ No newline at end of file
using System;
namespace Base
{
public static class RandomHelper
{
public static UInt64 RandUInt64()
{
var bytes = new byte[8];
Random random = new Random();
random.NextBytes(bytes);
return BitConverter.ToUInt64(bytes, 0);
}
/// <summary>
/// 获取lower与Upper之间的随机数
/// </summary>
/// <param name="lower"></param>
/// <param name="upper"></param>
/// <returns></returns>
public static int RandomNumber(int lower, int upper)
{
Random ra = new Random(Guid.NewGuid().GetHashCode());
int value = ra.Next(lower, upper);
return value;
}
}
}
\ No newline at end of file
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
namespace Base
{
public static class StringHelper
{
public static IEnumerable<byte> ToBytes(this string str)
{
byte[] byteArray = Encoding.Default.GetBytes(str);
return byteArray;
}
public static byte[] ToByteArray(this string str)
{
byte[] byteArray = Encoding.Default.GetBytes(str);
return byteArray;
}
public static byte[] ToUtf8(this string str)
{
byte[] byteArray = Encoding.UTF8.GetBytes(str);
return byteArray;
}
public static byte[] HexToBytes(this string hexString)
{
if (hexString.Length % 2 != 0)
{
throw new ArgumentException(String.Format(CultureInfo.InvariantCulture, "The binary key cannot have an odd number of digits: {0}", hexString));
}
var hexAsBytes = new byte[hexString.Length / 2];
for (int index = 0; index < hexAsBytes.Length; index++)
{
string byteValue = "";
byteValue += hexString[index * 2];
byteValue += hexString[index * 2 + 1];
hexAsBytes[index] = byte.Parse(byteValue, NumberStyles.HexNumber, CultureInfo.InvariantCulture);
}
return hexAsBytes;
}
public static string Fmt(this string text, params object[] args)
{
return string.Format(text, args);
}
public static string ListToString<T>(this List<T> list)
{
StringBuilder sb = new StringBuilder();
foreach (T t in list)
{
sb.Append(t);
sb.Append(",");
}
return sb.ToString();
}
}
}
\ No newline at end of file
using System;
namespace Base
{
public static class TimeHelper
{
private static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
/// <summary>
/// 客户端时间
/// </summary>
/// <returns></returns>
public static long ClientNow()
{
return Convert.ToInt64((DateTime.UtcNow - Epoch).TotalMilliseconds);
}
public static long ClientNowTicks()
{
return Convert.ToInt64((DateTime.UtcNow - Epoch).Ticks);
}
}
}
\ No newline at end of file
using System.IO;
using ICSharpCode.SharpZipLib.Zip.Compression;
namespace Base
{
public static class ZipHelper
{
public static byte[] Compress(byte[] content)
{
//return content;
Deflater compressor = new Deflater();
compressor.SetLevel(Deflater.BEST_COMPRESSION);
compressor.SetInput(content);
compressor.Finish();
using (MemoryStream bos = new MemoryStream(content.Length))
{
var buf = new byte[1024];
while (!compressor.IsFinished)
{
int n = compressor.Deflate(buf);
bos.Write(buf, 0, n);
}
return bos.ToArray();
}
}
public static byte[] Decompress(byte[] content)
{
return Decompress(content, 0, content.Length);
}
public static byte[] Decompress(byte[] content, int offset, int count)
{
//return content;
Inflater decompressor = new Inflater();
decompressor.SetInput(content, offset, count);
using (MemoryStream bos = new MemoryStream(content.Length))
{
var buf = new byte[1024];
while (!decompressor.IsFinished)
{
int n = decompressor.Inflate(buf);
bos.Write(buf, 0, n);
}
return bos.ToArray();
}
}
}
}
/*
using System.IO;
using System.IO.Compression;
namespace Model
{
public static class ZipHelper
{
public static byte[] Compress(byte[] content)
{
using (MemoryStream ms = new MemoryStream())
using (DeflateStream stream = new DeflateStream(ms, CompressionMode.Compress, true))
{
stream.Write(content, 0, content.Length);
return ms.ToArray();
}
}
public static byte[] Decompress(byte[] content)
{
return Decompress(content, 0, content.Length);
}
public static byte[] Decompress(byte[] content, int offset, int count)
{
using (MemoryStream ms = new MemoryStream())
using (DeflateStream stream = new DeflateStream(new MemoryStream(content, offset, count), CompressionMode.Decompress, true))
{
byte[] buffer = new byte[1024];
while (true)
{
int bytesRead = stream.Read(buffer, 0, 1024);
if (bytesRead == 0)
{
break;
}
ms.Write(buffer, 0, bytesRead);
}
return ms.ToArray();
}
}
}
}
*/
\ No newline at end of file
using System.Collections.Generic;
namespace Base
{
public class MultiMap<T, K>
{
private readonly SortedDictionary<T, List<K>> dictionary = new SortedDictionary<T, List<K>>();
public SortedDictionary<T, List<K>>.KeyCollection Keys
{
get
{
return this.dictionary.Keys;
}
}
public void Add(T t, K k)
{
List<K> list;
this.dictionary.TryGetValue(t, out list);
if (list == null)
{
list = new List<K>();
}
list.Add(k);
this.dictionary[t] = list;
}
public bool Remove(T t, K k)
{
List<K> list;
this.dictionary.TryGetValue(t, out list);
if (list == null)
{
return false;
}
if (!list.Remove(k))
{
return false;
}
if (list.Count == 0)
{
this.dictionary.Remove(t);
}
return true;
}
public bool Remove(T t)
{
return this.dictionary.Remove(t);
}
/// <summary>
/// 不返回内部的list,copy一份出来
/// </summary>
/// <param name="t"></param>
/// <returns></returns>
public K[] GetAll(T t)
{
List<K> list;
this.dictionary.TryGetValue(t, out list);
if (list == null)
{
return new K[0];
}
var newList = new List<K>();
foreach (K k in list)
{
newList.Add(k);
}
return newList.ToArray();
}
/// <summary>
/// 返回内部的list
/// </summary>
/// <param name="t"></param>
/// <returns></returns>
public List<K> this[T t]
{
get
{
List<K> list;
this.dictionary.TryGetValue(t, out list);
return list;
}
}
public K GetOne(T t)
{
List<K> list;
this.dictionary.TryGetValue(t, out list);
if ((list != null) && (list.Count > 0))
{
return list[0];
}
return default(K);
}
public bool Contains(T t, K k)
{
List<K> list;
this.dictionary.TryGetValue(t, out list);
if (list == null)
{
return false;
}
return list.Contains(k);
}
}
}
\ No newline at end of file
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Base
{
[Flags]
public enum PacketFlags
{
None = 0,
Reliable = 1 << 0,
Unsequenced = 1 << 1,
NoAllocate = 1 << 2
}
public abstract class AChannel: IDisposable
{
public long Id { get; private set; }
protected AService service;
protected AChannel(AService service)
{
this.Id = IdGenerater.GenerateId();
this.service = service;
}
/// <summary>
/// 发送消息
/// </summary>
public abstract void Send(byte[] buffer, byte channelID = 0, PacketFlags flags = PacketFlags.Reliable);
public abstract void Send(List<byte[]> buffers, byte channelID = 0, PacketFlags flags = PacketFlags.Reliable);
/// <summary>
/// 接收消息
/// </summary>
public abstract Task<byte[]> Recv();
public virtual void Dispose()
{
if (this.Id == 0)
{
return;
}
this.service.Remove(this.Id);
this.Id = 0;
}
}
}
\ No newline at end of file
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Threading.Tasks;
namespace Base
{
public enum NetworkProtocol
{
TCP,
UDP
}
public abstract class AService: IDisposable
{
/// <summary>
/// 将函数调用加入IService线程
/// </summary>
/// <param name="action"></param>
public abstract void Add(Action action);
public abstract AChannel GetChannel(long id);
public abstract AChannel GetChannel(string host, int port);
public abstract AChannel GetChannel(string address);
public abstract Task<AChannel> AcceptChannel();
public abstract void Remove(long channelId);
public abstract void Update();
public Action<long, SocketError> OnError;
protected void OnChannelError(long channelId, SocketError error)
{
this.OnError?.Invoke(channelId, error);
this.Remove(channelId);
}
public abstract void Dispose();
}
}
\ No newline at end of file
using System;
namespace Base
{
internal enum ParserState
{
PacketSize,
PacketBody
}
internal class PacketParser
{
private readonly TBuffer buffer;
private uint packetSize;
private readonly byte[] packetSizeBuffer = new byte[4];
private ParserState state;
private byte[] packet;
private bool isOK;
public PacketParser(TBuffer buffer)
{
this.buffer = buffer;
}
private bool Parse()
{
if (this.isOK)
{
return true;
}
bool finish = false;
while (!finish)
{
switch (this.state)
{
case ParserState.PacketSize:
if (this.buffer.Count < 4)
{
finish = true;
}
else
{
this.buffer.RecvFrom(this.packetSizeBuffer);
this.packetSize = BitConverter.ToUInt32(this.packetSizeBuffer, 0);
if (packetSize > 1024 * 1024)
{
throw new Exception($"packet too large, size: {this.packetSize}");
}
this.state = ParserState.PacketBody;
}
break;
case ParserState.PacketBody:
if (this.buffer.Count < this.packetSize)
{
finish = true;
}
else
{
this.packet = new byte[this.packetSize];
this.buffer.RecvFrom(this.packet);
this.isOK = true;
this.state = ParserState.PacketSize;
finish = true;
}
break;
}
}
return this.isOK;
}
public byte[] GetPacket()
{
this.Parse();
if (!this.isOK)
{
return null;
}
byte[] result = this.packet;
this.isOK = false;
return result;
}
}
}
\ No newline at end of file
using System;
using System.Collections.Generic;
namespace Base
{
public class TBuffer
{
public const int ChunkSize = 8192;
private readonly LinkedList<byte[]> bufferList = new LinkedList<byte[]>();
public int LastIndex { get; set; }
public int FirstIndex { get; set; }
public TBuffer()
{
this.bufferList.AddLast(new byte[ChunkSize]);
}
public int Count
{
get
{
int c = 0;
if (this.bufferList.Count == 0)
{
c = 0;
}
else
{
c = (this.bufferList.Count - 1) * ChunkSize + this.LastIndex - this.FirstIndex;
}
if (c < 0)
{
Log.Error("TBuffer count < 0: {0}, {1}, {2}".Fmt(bufferList.Count, this.LastIndex, this.FirstIndex));
}
return c;
}
}
public void AddLast()
{
this.bufferList.AddLast(new byte[ChunkSize]);
}
public void RemoveFirst()
{
this.bufferList.RemoveFirst();
}
public byte[] First
{
get
{
if (this.bufferList.First == null)
{
this.AddLast();
}
return this.bufferList.First.Value;
}
}
public byte[] Last
{
get
{
if (this.bufferList.Last == null)
{
this.AddLast();
}
return this.bufferList.Last.Value;
}
}
public void RecvFrom(byte[] buffer)
{
if (this.Count < buffer.Length || buffer.Length == 0)
{
throw new Exception($"bufferList size < n, bufferList: {this.Count} buffer length: {buffer.Length}");
}
int alreadyCopyCount = 0;
while (alreadyCopyCount < buffer.Length)
{
int n = buffer.Length - alreadyCopyCount;
if (ChunkSize - this.FirstIndex > n)
{
Array.Copy(this.bufferList.First.Value, this.FirstIndex, buffer, alreadyCopyCount, n);
this.FirstIndex += n;
alreadyCopyCount += n;
}
else
{
Array.Copy(this.bufferList.First.Value, this.FirstIndex, buffer, alreadyCopyCount, ChunkSize - this.FirstIndex);
alreadyCopyCount += ChunkSize - this.FirstIndex;
this.FirstIndex = 0;
this.bufferList.RemoveFirst();
}
}
}
public void SendTo(byte[] buffer)
{
int alreadyCopyCount = 0;
while (alreadyCopyCount < buffer.Length)
{
if (this.LastIndex == ChunkSize)
{
this.bufferList.AddLast(new byte[ChunkSize]);
this.LastIndex = 0;
}
int n = buffer.Length - alreadyCopyCount;
if (ChunkSize - this.LastIndex > n)
{
Array.Copy(buffer, alreadyCopyCount, this.bufferList.Last.Value, this.LastIndex, n);
this.LastIndex += buffer.Length - alreadyCopyCount;
alreadyCopyCount += n;
}
else
{
Array.Copy(buffer, alreadyCopyCount, this.bufferList.Last.Value, this.LastIndex, ChunkSize - this.LastIndex);
alreadyCopyCount += ChunkSize - this.LastIndex;
this.LastIndex = ChunkSize;
}
}
}
}
}
\ No newline at end of file
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Threading.Tasks;
namespace Base
{
public class TChannel : AChannel
{
private readonly TSocket socket;
private readonly TBuffer recvBuffer = new TBuffer();
private readonly TBuffer sendBuffer = new TBuffer();
private bool isSending;
private readonly PacketParser parser;
private bool isConnected;
public Action<long, SocketError> OnError;
public string RemoteAddress { get; }
private TaskCompletionSource<byte[]> recvTcs;
/// <summary>
/// connect
/// </summary>
public TChannel(TSocket socket, string host, int port, TService service) : base(service)
{
this.socket = socket;
this.parser = new PacketParser(this.recvBuffer);
this.RemoteAddress = host + ":" + port;
bool result = this.socket.ConnectAsync(host, port);
if (!result)
{
this.OnConnected(this.Id, SocketError.Success);
return;
}
this.socket.OnConn += e => OnConnected(this.Id, e);
}
/// <summary>
/// accept
/// </summary>
public TChannel(TSocket socket, TService service) : base(service)
{
this.socket = socket;
this.parser = new PacketParser(this.recvBuffer);
this.RemoteAddress = socket.RemoteAddress;
}
public override void Dispose()
{
if (this.Id == 0)
{
return;
}
long id = this.Id;
base.Dispose();
this.socket.Dispose();
this.service.Remove(id);
}
private void OnConnected(long channelId, SocketError error)
{
if (this.service.GetChannel(channelId) == null)
{
return;
}
if (error != SocketError.Success)
{
Log.Error($"connect error: {error}");
return;
}
this.isConnected = true;
this.StartSend();
this.StartRecv();
}
public override void Send(byte[] buffer, byte channelID = 0, PacketFlags flags = PacketFlags.Reliable)
{
byte[] size = BitConverter.GetBytes(buffer.Length);
this.sendBuffer.SendTo(size);
this.sendBuffer.SendTo(buffer);
if (!this.isSending && this.isConnected)
{
this.StartSend();
}
}
public override void Send(List<byte[]> buffers, byte channelID = 0, PacketFlags flags = PacketFlags.Reliable)
{
int size = buffers.Select(b => b.Length).Sum();
byte[] sizeBuffer = BitConverter.GetBytes(size);
this.sendBuffer.SendTo(sizeBuffer);
foreach (byte[] buffer in buffers)
{
this.sendBuffer.SendTo(buffer);
}
if (!this.isSending && this.isConnected)
{
this.StartSend();
}
}
private void StartSend()
{
// 没有数据需要发送
if (this.sendBuffer.Count == 0)
{
this.isSending = false;
return;
}
this.isSending = true;
int sendSize = TBuffer.ChunkSize - this.sendBuffer.FirstIndex;
if (sendSize > this.sendBuffer.Count)
{
sendSize = this.sendBuffer.Count;
}
if (!this.socket.SendAsync(this.sendBuffer.First, this.sendBuffer.FirstIndex, sendSize))
{
this.OnSend(sendSize, SocketError.Success);
return;
}
this.socket.OnSend = this.OnSend;
}
private void OnSend(int n, SocketError error)
{
if (this.Id == 0)
{
return;
}
this.socket.OnSend = null;
if (error != SocketError.Success)
{
Log.Info($"socket send fail, error: {error}, n: {n}");
this.OnError(this.Id, error);
return;
}
this.sendBuffer.FirstIndex += n;
if (this.sendBuffer.FirstIndex == TBuffer.ChunkSize)
{
this.sendBuffer.FirstIndex = 0;
this.sendBuffer.RemoveFirst();
}
this.StartSend();
}
private void StartRecv()
{
int size = TBuffer.ChunkSize - this.recvBuffer.LastIndex;
if (!this.socket.RecvAsync(this.recvBuffer.Last, this.recvBuffer.LastIndex, size))
{
this.OnRecv(size, SocketError.Success);
}
this.socket.OnRecv = this.OnRecv;
}
private void OnRecv(int n, SocketError error)
{
if (this.Id == 0)
{
return;
}
this.socket.OnRecv = null;
if (error != SocketError.Success)
{
Log.Info($"socket recv fail, error: {error}, {n}");
this.OnError(this.Id, error);
return;
}
this.recvBuffer.LastIndex += n;
if (this.recvBuffer.LastIndex == TBuffer.ChunkSize)
{
this.recvBuffer.AddLast();
this.recvBuffer.LastIndex = 0;
}
if (this.recvTcs != null)
{
byte[] packet = this.parser.GetPacket();
if (packet != null)
{
this.recvTcs.SetResult(packet);
this.recvTcs = null;
}
}
StartRecv();
}
public override Task<byte[]> Recv()
{
TaskCompletionSource<byte[]> tcs = new TaskCompletionSource<byte[]>();
byte[] packet = this.parser.GetPacket();
if (packet != null)
{
tcs.SetResult(packet);
}
else
{
recvTcs = tcs;
}
return tcs.Task;
}
}
}
\ No newline at end of file
using System;
using System.Collections.Generic;
namespace Base
{
public class TPoller
{
// 线程同步队列,发送接收socket回调都放到该队列,由poll线程统一执行
private Queue<Action> queue = new Queue<Action>();
private Queue<Action> localQueue = new Queue<Action>();
private readonly object lockObject = new object();
public void Add(Action action)
{
lock (lockObject)
{
this.queue.Enqueue(action);
}
}
public void Update()
{
lock (lockObject)
{
localQueue = queue;
queue = new Queue<Action>();
}
while (this.localQueue.Count > 0)
{
Action a = this.localQueue.Dequeue();
a();
}
}
}
}
\ No newline at end of file
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Base
{
public sealed class TService: AService
{
private TPoller poller = new TPoller();
private readonly TSocket acceptor;
private readonly Dictionary<long, TChannel> idChannels = new Dictionary<long, TChannel>();
private readonly Dictionary<string, TChannel> addressChannels = new Dictionary<string, TChannel>();
/// <summary>
/// 即可做client也可做server
/// </summary>
/// <param name="host"></param>
/// <param name="port"></param>
public TService(string host, int port)
{
this.acceptor = new TSocket(this.poller, host, port);
}
public TService()
{
}
public override void Dispose()
{
if (this.poller == null)
{
return;
}
foreach (long id in this.idChannels.Keys.ToArray())
{
TChannel channel = this.idChannels[id];
channel.Dispose();
}
this.poller = null;
}
public override void Add(Action action)
{
this.poller.Add(action);
}
public override AChannel GetChannel(long id)
{
TChannel channel = null;
this.idChannels.TryGetValue(id, out channel);
return channel;
}
public override async Task<AChannel> AcceptChannel()
{
if (this.acceptor == null)
{
throw new Exception("service construct must use host and port param");
}
TSocket socket = new TSocket(this.poller);
await this.acceptor.AcceptAsync(socket);
TChannel channel = new TChannel(socket, this);
this.addressChannels[channel.RemoteAddress] = channel;
this.idChannels[channel.Id] = channel;
return channel;
}
public override void Remove(long id)
{
TChannel channel;
if (!this.idChannels.TryGetValue(id, out channel))
{
return;
}
if (channel == null)
{
return;
}
this.idChannels.Remove(id);
channel.Dispose();
}
public override AChannel GetChannel(string host, int port)
{
string address = $"{host}:{port}";
return this.GetChannel(address);
}
public override AChannel GetChannel(string address)
{
TChannel channel = null;
if (this.addressChannels.TryGetValue(address, out channel))
{
return channel;
}
string[] ss = address.Split(':');
string host = ss[0];
int port = int.Parse(ss[1]);
TSocket newSocket = new TSocket(this.poller);
channel = new TChannel(newSocket, host, port, this);
channel.OnError += this.OnChannelError;
this.idChannels[channel.Id] = channel;
return channel;
}
public override void Update()
{
this.poller.Update();
}
}
}
\ No newline at end of file
using System;
using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks;
namespace Base
{
/// <summary>
/// 封装Socket,将回调push到主线程处理
/// </summary>
public class TSocket: IDisposable
{
private readonly TPoller poller;
private Socket socket;
private readonly SocketAsyncEventArgs innArgs = new SocketAsyncEventArgs();
private readonly SocketAsyncEventArgs outArgs = new SocketAsyncEventArgs();
public Action<SocketError> OnConn;
public Action<int, SocketError> OnRecv;
public Action<int, SocketError> OnSend;
public Action<SocketError> OnDisconnect;
public TSocket(TPoller poller)
{
this.poller = poller;
this.socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
this.innArgs.Completed += this.OnComplete;
this.outArgs.Completed += this.OnComplete;
}
public TSocket(TPoller poller, string host, int port): this(poller)
{
this.Bind(host, port);
this.Listen(100);
}
public Socket Socket
{
get
{
return this.socket;
}
}
public string RemoteAddress
{
get
{
IPEndPoint ipEndPoint = (IPEndPoint)this.socket.RemoteEndPoint;
return ipEndPoint.Address + ":" + ipEndPoint.Port;
}
}
public void Dispose()
{
if (this.socket == null)
{
return;
}
this.socket.Close();
this.socket = null;
}
private void Bind(string host, int port)
{
this.socket.Bind(new IPEndPoint(IPAddress.Parse(host), port));
}
private void Listen(int backlog)
{
this.socket.Listen(backlog);
}
public Task<bool> AcceptAsync(TSocket accpetSocket)
{
var tcs = new TaskCompletionSource<bool>();
this.innArgs.UserToken = tcs;
this.innArgs.AcceptSocket = accpetSocket.socket;
if (!this.socket.AcceptAsync(this.innArgs))
{
OnAcceptComplete(this.innArgs);
}
return tcs.Task;
}
private static void OnAcceptComplete(SocketAsyncEventArgs e)
{
var tcs = (TaskCompletionSource<bool>)e.UserToken;
e.UserToken = null;
if (e.SocketError != SocketError.Success)
{
tcs.SetException(new Exception($"socket error: {e.SocketError}"));
return;
}
tcs.SetResult(true);
}
private void OnComplete(object sender, SocketAsyncEventArgs e)
{
Action action;
switch (e.LastOperation)
{
case SocketAsyncOperation.Connect:
action = () => OnConnectComplete(e);
break;
case SocketAsyncOperation.Receive:
action = () => OnRecvComplete(e);
break;
case SocketAsyncOperation.Send:
action = () => OnSendComplete(e);
break;
case SocketAsyncOperation.Disconnect:
action = () => OnDisconnectComplete(e);
break;
case SocketAsyncOperation.Accept:
action = () => OnAcceptComplete(e);
break;
default:
throw new Exception($"socket error: {e.LastOperation}");
}
// 回调到主线程处理
this.poller.Add(action);
}
public bool ConnectAsync(string host, int port)
{
this.outArgs.RemoteEndPoint = new IPEndPoint(IPAddress.Parse(host), port);
if (this.socket.ConnectAsync(this.outArgs))
{
return true;
}
OnConnectComplete(this.outArgs);
return false;
}
private void OnConnectComplete(SocketAsyncEventArgs e)
{
if (this.OnConn == null)
{
return;
}
this.OnConn(e.SocketError);
}
public bool RecvAsync(byte[] buffer, int offset, int count)
{
try
{
this.innArgs.SetBuffer(buffer, offset, count);
}
catch (Exception e)
{
throw new Exception($"socket set buffer error: {buffer.Length}, {offset}, {count}", e);
}
if (this.socket.ReceiveAsync(this.innArgs))
{
return true;
}
OnRecvComplete(this.innArgs);
return false;
}
private void OnRecvComplete(SocketAsyncEventArgs e)
{
if (this.OnRecv == null)
{
return;
}
this.OnRecv(e.BytesTransferred, e.SocketError);
}
public bool SendAsync(byte[] buffer, int offset, int count)
{
try
{
this.outArgs.SetBuffer(buffer, offset, count);
}
catch (Exception e)
{
throw new Exception($"socket set buffer error: {buffer.Length}, {offset}, {count}", e);
}
if (this.socket.SendAsync(this.outArgs))
{
return true;
}
OnSendComplete(this.outArgs);
return false;
}
private void OnSendComplete(SocketAsyncEventArgs e)
{
if (this.OnSend == null)
{
return;
}
this.OnSend(e.BytesTransferred, e.SocketError);
}
private void OnDisconnectComplete(SocketAsyncEventArgs e)
{
if (this.OnDisconnect == null)
{
return;
}
this.OnDisconnect(e.SocketError);
}
}
}
\ No newline at end of file
using System;
namespace Base
{
internal static class Library
{
public static void Initialize()
{
int ret = NativeMethods.ENetInitialize();
if (ret < 0)
{
throw new Exception($"Initialization failed, ret: {ret}");
}
}
public static void Deinitialize()
{
NativeMethods.ENetDeinitialize();
}
public static uint Time
{
get
{
return NativeMethods.ENetTimeGet();
}
set
{
NativeMethods.ENetTimeSet(value);
}
}
}
}
\ No newline at end of file
using System;
using System.Runtime.InteropServices;
using System.Text;
namespace Base
{
public static class NativeMethods
{
private const string LIB = "ENet";
public const int ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT = 255;
public const int ENET_PROTOCOL_MAXIMUM_PEER_ID = 0xfff;
[DllImport(LIB, EntryPoint = "enet_address_set_host", CallingConvention = CallingConvention.Cdecl)]
internal static extern int ENetAddressSetHost(ref ENetAddress address, string hostName);
[DllImport(LIB, EntryPoint = "enet_address_get_host", CallingConvention = CallingConvention.Cdecl)]
internal static extern int ENetAddressGetHost(ref ENetAddress address, StringBuilder hostName, uint nameLength);
[DllImport(LIB, EntryPoint = "enet_address_get_host_ip", CallingConvention = CallingConvention.Cdecl)]
internal static extern int ENetAddressGetHostIp(ref ENetAddress address, StringBuilder hostIp, uint ipLength);
[DllImport(LIB, EntryPoint = "enet_deinitialize", CallingConvention = CallingConvention.Cdecl)]
internal static extern void ENetDeinitialize();
[DllImport(LIB, EntryPoint = "enet_initialize", CallingConvention = CallingConvention.Cdecl)]
internal static extern int ENetInitialize();
[DllImport(LIB, EntryPoint = "enet_host_create", CallingConvention = CallingConvention.Cdecl)]
internal static extern IntPtr ENetHostCreate(
ref ENetAddress address, uint peerLimit, uint channelLimit, uint incomingBandwidth, uint outgoingBandwidth);
[DllImport(LIB, EntryPoint = "enet_host_create", CallingConvention = CallingConvention.Cdecl)]
internal static extern IntPtr ENetHostCreate(IntPtr address, uint peerLimit, uint channelLimit, uint incomingBandwidth, uint outgoingBandwidth);
[DllImport(LIB, EntryPoint = "enet_host_destroy", CallingConvention = CallingConvention.Cdecl)]
internal static extern void ENetHostDestroy(IntPtr host);
[DllImport(LIB, EntryPoint = "enet_host_connect", CallingConvention = CallingConvention.Cdecl)]
internal static extern IntPtr ENetHostConnect(IntPtr host, ref ENetAddress address, uint channelCount, uint data);
[DllImport(LIB, EntryPoint = "enet_host_broadcast", CallingConvention = CallingConvention.Cdecl)]
internal static extern void ENetHostBroadcast(IntPtr host, byte channelID, IntPtr packet);
[DllImport(LIB, EntryPoint = "enet_host_compress", CallingConvention = CallingConvention.Cdecl)]
internal static extern void ENetHostCompress(IntPtr host, IntPtr compressor);
[DllImport(LIB, EntryPoint = "enet_host_compress_with_range_coder", CallingConvention = CallingConvention.Cdecl)]
internal static extern int ENetHostCompressWithRangeCoder(IntPtr host);
[DllImport(LIB, EntryPoint = "enet_host_channel_limit", CallingConvention = CallingConvention.Cdecl)]
internal static extern void ENetHostChannelLimit(IntPtr host, uint channelLimit);
[DllImport(LIB, EntryPoint = "enet_host_bandwidth_limit", CallingConvention = CallingConvention.Cdecl)]
internal static extern void ENetHostBandwidthLimit(IntPtr host, uint incomingBandwidth, uint outgoingBandwidth);
[DllImport(LIB, EntryPoint = "enet_host_flush", CallingConvention = CallingConvention.Cdecl)]
internal static extern void ENetHostFlush(IntPtr host);
[DllImport(LIB, EntryPoint = "enet_host_check_events", CallingConvention = CallingConvention.Cdecl)]
internal static extern int ENetHostCheckEvents(IntPtr host, ENetEvent ev);
[DllImport(LIB, EntryPoint = "enet_host_service", CallingConvention = CallingConvention.Cdecl)]
internal static extern int ENetHostService(IntPtr host, ENetEvent ev, uint timeout);
[DllImport(LIB, EntryPoint = "enet_time_get", CallingConvention = CallingConvention.Cdecl)]
internal static extern uint ENetTimeGet();
[DllImport(LIB, EntryPoint = "enet_time_set", CallingConvention = CallingConvention.Cdecl)]
internal static extern void ENetTimeSet(uint newTimeBase);
[DllImport(LIB, EntryPoint = "enet_packet_create", CallingConvention = CallingConvention.Cdecl)]
internal static extern IntPtr ENetPacketCreate(byte[] data, uint dataLength, PacketFlags flags);
[DllImport(LIB, EntryPoint = "enet_packet_destroy", CallingConvention = CallingConvention.Cdecl)]
internal static extern void ENetPacketDestroy(IntPtr packet);
[DllImport(LIB, EntryPoint = "enet_packet_resize", CallingConvention = CallingConvention.Cdecl)]
internal static extern int ENetPacketResize(IntPtr packet, uint dataLength);
[DllImport(LIB, EntryPoint = "enet_peer_throttle_configure", CallingConvention = CallingConvention.Cdecl)]
internal static extern void ENetPeerThrottleConfigure(IntPtr peer, uint interval, uint acceleration, uint deceleration);
[DllImport(LIB, EntryPoint = "enet_peer_send")]
internal static extern int ENetPeerSend(IntPtr peer, byte channelID, IntPtr packet);
[DllImport(LIB, EntryPoint = "enet_peer_receive")]
internal static extern IntPtr ENetPeerReceive(IntPtr peer, out byte channelID);
[DllImport(LIB, EntryPoint = "enet_peer_reset")]
internal static extern void ENetPeerReset(IntPtr peer);
[DllImport(LIB, EntryPoint = "enet_peer_ping")]
internal static extern void ENetPeerPing(IntPtr peer);
[DllImport(LIB, EntryPoint = "enet_peer_disconnect_now")]
internal static extern void ENetPeerDisconnectNow(IntPtr peer, uint data);
[DllImport(LIB, EntryPoint = "enet_peer_disconnect")]
internal static extern void ENetPeerDisconnect(IntPtr peer, uint data);
[DllImport(LIB, EntryPoint = "enet_peer_disconnect_later")]
internal static extern void ENetPeerDisconnectLater(IntPtr peer, uint data);
}
}
\ No newline at end of file
using System;
using System.Runtime.InteropServices;
namespace Base
{
internal enum EventType
{
None = 0,
Connect = 1,
Disconnect = 2,
Receive = 3
}
internal enum PeerState
{
Uninitialized = -1,
Disconnected = 0,
Connecting = 1,
AcknowledgingConnect = 2,
ConnectionPending = 3,
ConnectionSucceeded = 4,
Connected = 5,
DisconnectLater = 6,
Disconnecting = 7,
AcknowledgingDisconnect = 8,
Zombie = 9
}
[StructLayout(LayoutKind.Sequential)]
internal struct ENetAddress
{
public uint Host;
public ushort Port;
}
// ENetEvent
[StructLayout(LayoutKind.Sequential)]
internal class ENetEvent
{
public EventType Type;
public IntPtr Peer;
public byte ChannelID;
public uint Data;
public IntPtr Packet;
}
[StructLayout(LayoutKind.Sequential)]
internal class ENetListNode
{
public IntPtr Next;
public IntPtr Previous;
}
[StructLayout(LayoutKind.Sequential)]
internal struct ENetPacket
{
public IntPtr ReferenceCount; // size_t
public uint Flags;
public IntPtr Data;
public IntPtr DataLength; // size_t
}
[StructLayout(LayoutKind.Sequential)]
internal struct ENetPeer
{
public ENetListNode DispatchList;
public IntPtr Host;
public ushort OutgoingPeerID;
public ushort IncomingPeerID;
public uint ConnectID;
public byte OutgoingSessionID;
public byte IncomingSessionID;
public ENetAddress Address;
public IntPtr Data;
}
}
\ No newline at end of file
using System;
using System.Net;
namespace Base
{
internal struct UAddress
{
private readonly uint ip;
private readonly ushort port;
public UAddress(string host, int port)
{
IPAddress address = IPAddress.Parse(host);
this.ip = BitConverter.ToUInt32(address.GetAddressBytes(), 0);
this.port = (ushort) port;
}
public ENetAddress Struct
{
get
{
ENetAddress address = new ENetAddress { Host = this.ip, Port = this.port };
return address;
}
}
}
}
\ No newline at end of file
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Base
{
internal class UChannel: AChannel
{
private readonly USocket socket;
public string RemoteAddress { get; private set; }
private TaskCompletionSource<byte[]> recvTcs;
/// <summary>
/// connect
/// </summary>
public UChannel(USocket socket, string host, int port, UService service): base(service)
{
this.socket = socket;
this.service = service;
this.RemoteAddress = host + ":" + port;
this.socket.ConnectAsync(host, (ushort)port);
this.socket.Received += this.OnRecv;
}
/// <summary>
/// accept
/// </summary>
public UChannel(USocket socket, UService service) : base(service)
{
this.socket = socket;
this.service = service;
this.RemoteAddress = socket.RemoteAddress;
this.socket.Received += this.OnRecv;
}
public override void Dispose()
{
if (this.Id == 0)
{
return;
}
base.Dispose();
this.socket.Dispose();
}
public override void Send(byte[] buffer, byte channelID = 0, PacketFlags flags = PacketFlags.Reliable)
{
this.socket.SendAsync(buffer, channelID, flags);
}
public override void Send(List<byte[]> buffers, byte channelID = 0, PacketFlags flags = PacketFlags.Reliable)
{
int size = buffers.Select(b => b.Length).Sum();
var buffer = new byte[size];
int index = 0;
foreach (byte[] bytes in buffers)
{
Array.Copy(bytes, 0, buffer, index, bytes.Length);
index += bytes.Length;
}
this.socket.SendAsync(buffer, channelID, flags);
}
public override Task<byte[]> Recv()
{
TaskCompletionSource<byte[]> tcs = new TaskCompletionSource<byte[]>();
var recvQueue = this.socket.RecvQueue;
if (recvQueue.Count > 0)
{
tcs.SetResult(recvQueue.Dequeue());
}
else
{
recvTcs = tcs;
}
return tcs.Task;
}
private void OnRecv()
{
this.recvTcs?.SetResult(this.socket.RecvQueue.Dequeue());
this.recvTcs = null;
}
}
}
\ No newline at end of file
using System;
using System.Runtime.InteropServices;
namespace Base
{
internal sealed class UPacket: IDisposable
{
public UPacket(IntPtr packet)
{
this.PacketPtr = packet;
}
public UPacket(byte[] data, PacketFlags flags = PacketFlags.None)
{
if (data == null)
{
throw new ArgumentNullException(nameof(data));
}
this.PacketPtr = NativeMethods.ENetPacketCreate(data, (uint) data.Length, flags);
if (this.PacketPtr == IntPtr.Zero)
{
throw new Exception("Packet creation call failed");
}
}
~UPacket()
{
this.Dispose(false);
}
public void Dispose()
{
this.Dispose(true);
}
private void Dispose(bool disposing)
{
if (this.PacketPtr == IntPtr.Zero)
{
return;
}
NativeMethods.ENetPacketDestroy(this.PacketPtr);
this.PacketPtr = IntPtr.Zero;
}
private ENetPacket Struct
{
get
{
return (ENetPacket) Marshal.PtrToStructure(this.PacketPtr, typeof (ENetPacket));
}
set
{
Marshal.StructureToPtr(value, this.PacketPtr, false);
}
}
public IntPtr PacketPtr { get; set; }
public byte[] Bytes
{
get
{
ENetPacket enetPacket = this.Struct;
var bytes = new byte[(long) enetPacket.DataLength];
Marshal.Copy(enetPacket.Data, bytes, 0, (int) enetPacket.DataLength);
return bytes;
}
}
}
}
\ No newline at end of file
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Base
{
internal sealed class UPoller : IDisposable
{
static UPoller()
{
Library.Initialize();
}
public USocketManager USocketManager { get; }
private readonly QueueDictionary<IntPtr, ENetEvent> connQueue = new QueueDictionary<IntPtr, ENetEvent>();
private IntPtr host;
// 线程同步队列,发送接收socket回调都放到该队列,由poll线程统一执行
private Queue<Action> concurrentQueue = new Queue<Action>();
private Queue<Action> localQueue;
private readonly object lockObject = new object();
private ENetEvent eNetEventCache;
private TaskCompletionSource<USocket> AcceptTcs { get; set; }
public UPoller(string hostName, ushort port)
{
this.USocketManager = new USocketManager();
UAddress address = new UAddress(hostName, port);
ENetAddress nativeAddress = address.Struct;
this.host = NativeMethods.ENetHostCreate(ref nativeAddress,
NativeMethods.ENET_PROTOCOL_MAXIMUM_PEER_ID, 0, 0, 0);
if (this.host == IntPtr.Zero)
{
throw new Exception("Host creation call failed.");
}
NativeMethods.ENetHostCompressWithRangeCoder(this.host);
}
public UPoller()
{
this.USocketManager = new USocketManager();
this.host = NativeMethods.ENetHostCreate(IntPtr.Zero, NativeMethods.ENET_PROTOCOL_MAXIMUM_PEER_ID, 0, 0, 0);
if (this.host == IntPtr.Zero)
{
throw new Exception("Host creation call failed.");
}
}
public void Dispose()
{
if (this.host == IntPtr.Zero)
{
return;
}
NativeMethods.ENetHostDestroy(this.host);
this.host = IntPtr.Zero;
}
public IntPtr Host
{
get
{
return this.host;
}
}
private ENetEvent TryGetEvent()
{
if (this.eNetEventCache == null)
{
this.eNetEventCache = new ENetEvent();
}
if (NativeMethods.ENetHostCheckEvents(this.host, this.eNetEventCache) <= 0)
{
return null;
}
ENetEvent eNetEvent = this.eNetEventCache;
this.eNetEventCache = null;
return eNetEvent;
}
public void Flush()
{
NativeMethods.ENetHostFlush(this.host);
}
public void Add(Action action)
{
lock (lockObject)
{
this.concurrentQueue.Enqueue(action);
}
}
public Task<USocket> AcceptAsync()
{
if (this.AcceptTcs != null)
{
throw new Exception("do not accept twice!");
}
var tcs = new TaskCompletionSource<USocket>();
// 如果有请求连接缓存的包,从缓存中取
if (this.connQueue.Count > 0)
{
IntPtr ptr = this.connQueue.FirstKey;
this.connQueue.Remove(ptr);
USocket socket = new USocket(ptr, this);
this.USocketManager.Add(ptr, socket);
tcs.SetResult(socket);
}
else
{
this.AcceptTcs = tcs;
}
return tcs.Task;
}
private void OnAccepted(ENetEvent eEvent)
{
if (eEvent.Type == EventType.Disconnect)
{
this.AcceptTcs.TrySetException(new Exception("socket disconnected in accpet"));
}
USocket socket = new USocket(eEvent.Peer, this);
this.USocketManager.Add(socket.PeerPtr, socket);
socket.OnAccepted();
var tcs = this.AcceptTcs;
this.AcceptTcs = null;
tcs.SetResult(socket);
}
private void OnEvents()
{
lock (lockObject)
{
localQueue = concurrentQueue;
concurrentQueue = new Queue<Action>();
}
while (this.localQueue.Count > 0)
{
Action a = this.localQueue.Dequeue();
a();
}
}
private int Service()
{
int ret = NativeMethods.ENetHostService(this.host, null, 0);
return ret;
}
public void Update()
{
this.OnEvents();
if (this.Service() < 0)
{
return;
}
while (true)
{
ENetEvent eNetEvent = this.TryGetEvent();
if (eNetEvent == null)
{
return;
}
switch (eNetEvent.Type)
{
case EventType.Connect:
{
// 这是一个connect peer
if (this.USocketManager.ContainsKey(eNetEvent.Peer))
{
USocket uSocket = this.USocketManager[eNetEvent.Peer];
uSocket.OnConnected();
break;
}
// 这是accept peer
if (this.AcceptTcs != null)
{
this.OnAccepted(eNetEvent);
break;
}
// 如果server端没有acceptasync,则请求放入队列
this.connQueue.Add(eNetEvent.Peer, eNetEvent);
break;
}
case EventType.Receive:
{
USocket uSocket = this.USocketManager[eNetEvent.Peer];
uSocket.OnReceived(eNetEvent);
break;
}
case EventType.Disconnect:
{
USocket uSocket = this.USocketManager[eNetEvent.Peer];
this.USocketManager.Remove(uSocket.PeerPtr);
uSocket.PeerPtr = IntPtr.Zero;
uSocket.OnDisconnect(eNetEvent);
break;
}
}
}
}
}
}
\ No newline at end of file
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Threading.Tasks;
namespace Base
{
public sealed class UService: AService
{
private UPoller poller;
private readonly Dictionary<long, UChannel> idChannels = new Dictionary<long, UChannel>();
private readonly Dictionary<string, UChannel> addressChannels = new Dictionary<string, UChannel>();
/// <summary>
/// 即可做client也可做server
/// </summary>
public UService(string host, int port)
{
this.poller = new UPoller(host, (ushort)port);
}
/// <summary>
/// 只能做client
/// </summary>
public UService()
{
this.poller = new UPoller();
}
public override void Dispose()
{
if (this.poller == null)
{
return;
}
foreach (long id in this.idChannels.Keys.ToArray())
{
UChannel channel = this.idChannels[id];
channel.Dispose();
}
this.poller = null;
}
public override void Add(Action action)
{
this.poller.Add(action);
}
public override AChannel GetChannel(string host, int port)
{
return this.GetChannel($"{host}:{port}");
}
public override AChannel GetChannel(string address)
{
UChannel channel = null;
if (this.addressChannels.TryGetValue(address, out channel))
{
return channel;
}
USocket newSocket = new USocket(this.poller);
string[] ss = address.Split(':');
int port = int.Parse(ss[1]);
string host = ss[0];
channel = new UChannel(newSocket, host, port, this);
newSocket.Disconnect += () => this.OnChannelError(channel.Id, SocketError.SocketError);
this.idChannels[channel.Id] = channel;
return channel;
}
public override async Task<AChannel> AcceptChannel()
{
USocket socket = await this.poller.AcceptAsync();
UChannel channel = new UChannel(socket, this);
this.addressChannels[channel.RemoteAddress] = channel;
this.idChannels[channel.Id] = channel;
return channel;
}
public override AChannel GetChannel(long id)
{
UChannel channel = null;
this.idChannels.TryGetValue(id, out channel);
return channel;
}
public override void Remove(long id)
{
UChannel channel;
if (!this.idChannels.TryGetValue(id, out channel))
{
return;
}
if (channel == null)
{
return;
}
this.idChannels.Remove(id);
channel.Dispose();
}
public override void Update()
{
this.poller.Update();
}
}
}
\ No newline at end of file
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
namespace Base
{
internal class BufferInfo
{
public byte[] Buffer { get; set; }
public byte ChannelID { get; set; }
public PacketFlags Flags { get; set; }
}
internal sealed class USocket: IDisposable
{
private readonly UPoller poller;
public IntPtr PeerPtr { get; set; }
private readonly Queue<byte[]> recvQueue = new Queue<byte[]>();
private readonly Queue<BufferInfo> sendQueue = new Queue<BufferInfo>();
private bool isConnected;
public Action Disconnect;
public Action Received;
public USocket(IntPtr peerPtr, UPoller poller)
{
this.poller = poller;
this.PeerPtr = peerPtr;
}
public USocket(UPoller poller)
{
this.poller = poller;
}
public void Dispose()
{
if (this.PeerPtr == IntPtr.Zero)
{
return;
}
poller.USocketManager.Remove(this.PeerPtr);
NativeMethods.ENetPeerDisconnectNow(this.PeerPtr, 0);
this.PeerPtr = IntPtr.Zero;
}
public string RemoteAddress
{
get
{
ENetPeer peer = this.Struct;
return peer.Address.Host + ":" + peer.Address.Port;
}
}
private ENetPeer Struct
{
get
{
if (this.PeerPtr == IntPtr.Zero)
{
return new ENetPeer();
}
ENetPeer peer = (ENetPeer)Marshal.PtrToStructure(this.PeerPtr, typeof(ENetPeer));
return peer;
}
set
{
Marshal.StructureToPtr(value, this.PeerPtr, false);
}
}
public Queue<byte[]> RecvQueue
{
get
{
return recvQueue;
}
}
public void ConnectAsync(string host, ushort port)
{
UAddress address = new UAddress(host, port);
ENetAddress nativeAddress = address.Struct;
this.PeerPtr = NativeMethods.ENetHostConnect(this.poller.Host, ref nativeAddress, 2, 0);
if (this.PeerPtr == IntPtr.Zero)
{
throw new Exception($"host connect call failed, {host}:{port}");
}
this.poller.USocketManager.Add(this.PeerPtr, this);
}
public void SendAsync(byte[] data, byte channelID = 0, PacketFlags flags = PacketFlags.Reliable)
{
if (!isConnected)
{
sendQueue.Enqueue(new BufferInfo { Buffer = data, ChannelID = channelID, Flags = flags });
return;
}
UPacket packet = new UPacket(data, flags);
NativeMethods.ENetPeerSend(this.PeerPtr, channelID, packet.PacketPtr);
// enet_peer_send函数会自动删除packet,设置为0,防止Dispose或者析构函数再次删除
packet.PacketPtr = IntPtr.Zero;
}
internal void OnConnected()
{
isConnected = true;
while (this.sendQueue.Count > 0)
{
BufferInfo info = this.sendQueue.Dequeue();
this.SendAsync(info.Buffer, info.ChannelID, info.Flags);
}
}
internal void OnAccepted()
{
isConnected = true;
}
internal void OnReceived(ENetEvent eNetEvent)
{
// 将包放到缓存队列
using (UPacket packet = new UPacket(eNetEvent.Packet))
{
byte[] bytes = packet.Bytes;
this.RecvQueue.Enqueue(bytes);
}
}
internal void OnDisconnect(ENetEvent eNetEvent)
{
Disconnect();
}
}
}
\ No newline at end of file
using System;
using System.Collections.Generic;
namespace Base
{
internal class USocketManager
{
private readonly Dictionary<IntPtr, USocket> sockets = new Dictionary<IntPtr, USocket>();
public void Add(IntPtr peerPtr, USocket uSocket)
{
this.sockets.Add(peerPtr, uSocket);
}
public void Remove(IntPtr peerPtr)
{
this.sockets.Remove(peerPtr);
}
public bool ContainsKey(IntPtr peerPtr)
{
if (this.sockets.ContainsKey(peerPtr))
{
return true;
}
return false;
}
public USocket this[IntPtr peerPtr]
{
get
{
if (!this.sockets.ContainsKey(peerPtr))
{
throw new KeyNotFoundException("No Peer Key");
}
return this.sockets[peerPtr];
}
}
}
}
\ No newline at end of file
namespace Base
{
/// <summary>
/// Component的Id与Owner Entity Id一样
/// </summary>
public abstract class Component: Object
{
public Entity Owner { get; set; }
protected Component()
{
ObjectManager.Add(this);
}
protected Component(long id): base(id)
{
ObjectManager.Add(this);
}
public T GetComponent<T>() where T: Component
{
return this.Owner.GetComponent<T>();
}
public override void Dispose()
{
base.Dispose();
ObjectManager.Remove(this.Id);
}
}
}
\ No newline at end of file
using System;
using System.Collections.Generic;
using System.Linq;
using Base;
using MongoDB.Bson.Serialization.Attributes;
namespace Base
{
public sealed class Entity: Object
{
[BsonElement, BsonIgnoreIfNull]
private HashSet<Component> components = new HashSet<Component>();
private Dictionary<Type, Component> componentDict = new Dictionary<Type, Component>();
public Entity()
{
ObjectManager.Add(this);
}
public Entity(long id): base(id)
{
ObjectManager.Add(this);
}
public override void Dispose()
{
if (this.Id == 0)
{
return;
}
base.Dispose();
foreach (Component component in this.GetComponents())
{
try
{
component.Dispose();
}
catch (Exception e)
{
Log.Error(e.ToString());
}
}
ObjectManager.Remove(this.Id);
}
public K AddComponent<K>() where K : Component, new()
{
K component = (K) Activator.CreateInstance(typeof (K));
component.Owner = this;
if (this.componentDict.ContainsKey(component.GetType()))
{
throw new Exception($"AddComponent, component already exist, id: {this.Id}, component: {typeof (K).Name}");
}
if (this.components == null)
{
this.components = new HashSet<Component>();
}
this.components.Add(component);
this.componentDict.Add(component.GetType(), component);
ObjectManager.Awake(component.Id);
return component;
}
public K AddComponent<K, P1>(P1 p1) where K : Component, new()
{
K component = (K)Activator.CreateInstance(typeof(K));
component.Owner = this;
if (this.componentDict.ContainsKey(component.GetType()))
{
throw new Exception($"AddComponent, component already exist, id: {this.Id}, component: {typeof(K).Name}");
}
if (this.components == null)
{
this.components = new HashSet<Component>();
}
this.components.Add(component);
this.componentDict.Add(component.GetType(), component);
ObjectManager.Awake(component.Id, p1);
return component;
}
public K AddComponent<K, P1, P2>(P1 p1, P2 p2) where K : Component, new()
{
K component = (K)Activator.CreateInstance(typeof(K));
component.Owner = this;
if (this.componentDict.ContainsKey(component.GetType()))
{
throw new Exception($"AddComponent, component already exist, id: {this.Id}, component: {typeof(K).Name}");
}
if (this.components == null)
{
this.components = new HashSet<Component>();
}
this.components.Add(component);
this.componentDict.Add(component.GetType(), component);
ObjectManager.Awake(component.Id, p1, p2);
return component;
}
public K AddComponent<K, P1, P2, P3>(P1 p1, P2 p2, P3 p3) where K : Component, new()
{
K component = (K)Activator.CreateInstance(typeof(K));
component.Owner = this;
if (this.componentDict.ContainsKey(component.GetType()))
{
throw new Exception($"AddComponent, component already exist, id: {this.Id}, component: {typeof(K).Name}");
}
if (this.components == null)
{
this.components = new HashSet<Component>();
}
this.components.Add(component);
this.componentDict.Add(component.GetType(), component);
ObjectManager.Awake(component.Id, p1, p2, p3);
return component;
}
public void AddComponent(Component component)
{
if (this.componentDict.ContainsKey(component.GetType()))
{
throw new Exception($"AddComponent, component already exist, id: {this.Id}, component: {component.GetType().Name}");
}
if (this.components == null)
{
this.components = new HashSet<Component>();
}
this.components.Add(component);
this.componentDict.Add(component.GetType(), component);
ObjectManager.Awake(component.Id);
}
public void RemoveComponent<K>() where K : Component
{
Component component;
if (!this.componentDict.TryGetValue(typeof (K), out component))
{
return;
}
this.components.Remove(component);
this.componentDict.Remove(typeof (K));
if (this.components.Count == 0)
{
this.components = null;
}
component.Dispose();
}
public K GetComponent<K>() where K : Component
{
Component component;
if (!this.componentDict.TryGetValue(typeof (K), out component))
{
return default(K);
}
return (K) component;
}
public Component[] GetComponents()
{
return components.ToArray();
}
public override void BeginInit()
{
base.BeginInit();
this.components = new HashSet<Component>();
this.componentDict = new Dictionary<Type, Component>();
}
public override void EndInit()
{
base.EndInit();
if (this.components.Count == 0)
{
this.components = null;
return;
}
foreach (Component component in this.components)
{
component.Owner = this;
this.componentDict.Add(component.GetType(), component);
}
}
}
}
\ No newline at end of file
namespace Base
{
/// <summary>
/// World的Componet实现该接口后,会在World.Start时调用该Start方法
/// </summary>
public interface IAwake
{
void Awake();
}
public interface IAwake<in P1>
{
void Awake(P1 p1);
}
public interface IAwake<in P1, in P2>
{
void Awake(P1 p1, P2 p2);
}
public interface IAwake<in P1, in P2, in P3>
{
void Awake(P1 p1, P2 p2, P3 p3);
}
}
\ No newline at end of file
namespace Base
{
/// <summary>
/// World的Componet实现该接口,World.Load会调用Load方法
/// </summary>
public interface ILoader
{
void Load();
}
}
\ No newline at end of file
namespace Base
{
/// <summary>
/// World的Componet实现该接口后,会在World.Start时调用该Start方法
/// </summary>
public interface IStart
{
void Start();
}
}
\ No newline at end of file
namespace Base
{
/// <summary>
/// 实现了该接口的World Componet会每帧刷新
/// </summary>
public interface IUpdate
{
void Update();
}
}
\ No newline at end of file
using System;
using System.ComponentModel;
using MongoDB.Bson.Serialization.Attributes;
namespace Base
{
public abstract class Object: IDisposable, ISupportInitialize
{
public static ObjectManager ObjectManager = new ObjectManager();
[BsonId]
public long Id { get; private set; }
protected Object()
{
Id = IdGenerater.GenerateId();
}
protected Object(long id)
{
this.Id = id;
}
public bool IsDisposed()
{
return this.Id == 0;
}
public virtual void Dispose()
{
if (this.Id == 0)
{
return;
}
this.Id = 0;
}
public virtual void BeginInit()
{
}
public virtual void EndInit()
{
}
}
}
\ No newline at end of file
using System;
namespace Base
{
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class ObjectEventAttribute: Attribute
{
}
}
\ No newline at end of file
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
namespace Base
{
public interface IObjectEvent
{
Type ValueType();
void SetValue(object value);
}
public abstract class ObjectEvent<T> : IObjectEvent
{
private T value;
protected T GetValue()
{
return value;
}
public void SetValue(object v)
{
this.value = (T)v;
}
public Type ValueType()
{
return typeof(T);
}
}
public class ObjectManager : IDisposable
{
private readonly Dictionary<string, Assembly> assemblies = new Dictionary<string, Assembly>();
private Dictionary<Type, IObjectEvent> objectEvents;
private readonly Dictionary<long, Object> objects = new Dictionary<long, Object>();
private List<long> starts = new List<long>();
private List<long> newStarts = new List<long>();
private List<long> updates = new List<long>(3000);
private List<long> newUpdates = new List<long>(3000);
private readonly List<long> loaders = new List<long>();
public void Dispose()
{
foreach (Object o in this.objects.Values.ToArray())
{
o.Dispose();
}
}
public void Register(string name, Assembly assembly)
{
this.assemblies[name] = assembly;
objectEvents = new Dictionary<Type, IObjectEvent>();
foreach (Assembly ass in this.assemblies.Values)
{
Type[] types = ass.GetTypes();
foreach (Type type in types)
{
object[] attrs = type.GetCustomAttributes(typeof(ObjectEventAttribute), false);
if (attrs.Length == 0)
{
continue;
}
object obj = Activator.CreateInstance(type);
IObjectEvent objectEvent = obj as IObjectEvent;
if (objectEvent == null)
{
Log.Error($"组件事件没有继承IComponentEvent: {type.Name}");
}
objectEvents[objectEvent.ValueType()] = objectEvent;
}
}
this.Load();
}
public Assembly GetAssembly(string name)
{
return this.assemblies[name];
}
public Assembly[] GetAssemblies()
{
return this.assemblies.Values.ToArray();
}
private void Load()
{
foreach (long id in this.loaders)
{
Object obj;
if (!this.objects.TryGetValue(id, out obj))
{
continue;
}
IObjectEvent objectEvent;
if (!objectEvents.TryGetValue(obj.GetType(), out objectEvent))
{
continue;
}
ILoader iLoader = objectEvent as ILoader;
if (iLoader == null)
{
continue;
}
objectEvent.SetValue(obj);
iLoader.Load();
}
}
public void Add(Object obj)
{
if (objectEvents == null)
{
return;
}
this.objects.Add(obj.Id, obj);
IObjectEvent objectEvent;
if (!objectEvents.TryGetValue(obj.GetType(), out objectEvent))
{
return;
}
IStart iStart = objectEvent as IStart;
if (iStart != null)
{
this.newStarts.Add(obj.Id);
}
IUpdate iUpdate = objectEvent as IUpdate;
if (iUpdate != null)
{
this.newUpdates.Add(obj.Id);
}
ILoader iLoader = objectEvent as ILoader;
if (iLoader != null)
{
this.loaders.Add(obj.Id);
}
}
public void Remove(long id)
{
this.objects.Remove(id);
}
public void Awake(long id)
{
Object obj;
if (!objects.TryGetValue(id, out obj))
{
return;
}
IObjectEvent objectEvent;
if (!objectEvents.TryGetValue(obj.GetType(), out objectEvent))
{
return;
}
IAwake iAwake = objectEvent as IAwake;
if (iAwake == null)
{
return;
}
objectEvent.SetValue(obj);
iAwake.Awake();
}
public void Awake<P1>(long id, P1 p1)
{
Object obj;
if (!objects.TryGetValue(id, out obj))
{
return;
}
IObjectEvent objectEvent;
if (!objectEvents.TryGetValue(obj.GetType(), out objectEvent))
{
return;
}
IAwake<P1> iAwake = objectEvent as IAwake<P1>;
if (iAwake == null)
{
return;
}
objectEvent.SetValue(obj);
iAwake.Awake(p1);
}
public void Awake<P1, P2>(long id, P1 p1, P2 p2)
{
Object obj;
if (!objects.TryGetValue(id, out obj))
{
return;
}
IObjectEvent objectEvent;
if (!objectEvents.TryGetValue(obj.GetType(), out objectEvent))
{
return;
}
IAwake<P1, P2> iAwake = objectEvent as IAwake<P1, P2>;
if (iAwake == null)
{
return;
}
objectEvent.SetValue(obj);
iAwake.Awake(p1, p2);
}
public void Awake<P1, P2, P3>(long id, P1 p1, P2 p2, P3 p3)
{
Object obj;
if (!objects.TryGetValue(id, out obj))
{
return;
}
IObjectEvent objectEvent;
if (!objectEvents.TryGetValue(obj.GetType(), out objectEvent))
{
return;
}
IAwake<P1, P2, P3> iAwake = objectEvent as IAwake<P1, P2, P3>;
if (iAwake == null)
{
return;
}
objectEvent.SetValue(obj);
iAwake.Awake(p1, p2, p3);
}
private void Start()
{
starts = newStarts;
newStarts = new List<long>();
foreach (long id in starts)
{
Object obj;
if (!this.objects.TryGetValue(id, out obj))
{
continue;
}
IObjectEvent objectEvent;
if (!objectEvents.TryGetValue(obj.GetType(), out objectEvent))
{
continue;
}
IStart iStart = objectEvent as IStart;
if (iStart == null)
{
continue;
}
objectEvent.SetValue(obj);
iStart.Start();
}
}
public void Update()
{
this.Start();
// 交换update
List<long> tmpUpdate = updates;
updates = newUpdates;
newUpdates = tmpUpdate;
newUpdates.Clear();
foreach (long id in updates)
{
Object obj;
if (!objects.TryGetValue(id, out obj))
{
continue;
}
IObjectEvent objectEvent;
if (!objectEvents.TryGetValue(obj.GetType(), out objectEvent))
{
continue;
}
IUpdate iUpdate = objectEvent as IUpdate;
if (iUpdate == null)
{
continue;
}
newUpdates.Add(id);
objectEvent.SetValue(obj);
try
{
iUpdate.Update();
}
catch (Exception e)
{
Log.Error(e.ToString());
}
}
}
public override string ToString()
{
var info = new Dictionary<string, int>();
foreach (Object obj in objects.Values)
{
if (info.ContainsKey(obj.GetType().Name))
{
info[obj.GetType().Name] += 1;
}
else
{
info[obj.GetType().Name] = 1;
}
}
info = info.OrderByDescending(s => s.Value).ToDictionary(p => p.Key, p => p.Value);
StringBuilder sb = new StringBuilder();
sb.Append("\r\n");
foreach (string key in info.Keys)
{
sb.Append($"{info[key],10} {key}\r\n");
}
sb.Append($"\r\n start: {newStarts.Count}, update: {newUpdates.Count} total: {this.objects.Count}");
return sb.ToString();
}
}
}
\ No newline at end of file
using System.Collections.Generic;
namespace Base
{
public class QueueDictionary<T, K>
{
private readonly List<T> list = new List<T>();
private readonly Dictionary<T, K> dictionary = new Dictionary<T, K>();
public void Add(T t, K k)
{
this.list.Add(t);
this.dictionary.Add(t, k);
}
public bool Remove(T t)
{
this.list.Remove(t);
this.dictionary.Remove(t);
return true;
}
public int Count
{
get
{
return this.list.Count;
}
}
public T FirstKey
{
get
{
return this.list[0];
}
}
public K this[T t]
{
get
{
return this.dictionary[t];
}
}
}
}
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{AFBAD3D5-C827-4BA6-9940-7060B6C39306}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Base</RootNamespace>
<AssemblyName>Base</AssemblyName>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>..\Bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="ICSharpCode.SharpZipLib">
<HintPath>..\Lib\ICSharpCode.SharpZipLib.dll</HintPath>
</Reference>
<Reference Include="MongoDB.Bson">
<HintPath>..\Lib\MongoDB.Bson.dll</HintPath>
</Reference>
<Reference Include="NLog">
<HintPath>..\Lib\NLog.dll</HintPath>
</Reference>
<Reference Include="protobuf-net">
<HintPath>..\Lib\protobuf-net.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Config\ACategory.cs" />
<Compile Include="Config\AConfig.cs" />
<Compile Include="Config\ConfigAttribute.cs" />
<Compile Include="Config\ICategory.cs" />
<Compile Include="DoubleMap.cs" />
<Compile Include="Helper\ArrayHelper.cs" />
<Compile Include="Helper\ByteHelper.cs" />
<Compile Include="Helper\EnumHelper.cs" />
<Compile Include="Helper\FileHelper.cs" />
<Compile Include="Helper\IdGenerater.cs" />
<Compile Include="Helper\MD5Helper.cs" />
<Compile Include="Helper\MongoHelper.cs" />
<Compile Include="Helper\ProtobufHelper.cs" />
<Compile Include="Helper\RandomHelper.cs" />
<Compile Include="Helper\StringHelper.cs" />
<Compile Include="Helper\TimeHelper.cs" />
<Compile Include="Helper\ZipHelper.cs" />
<Compile Include="Log\Log.cs" />
<Compile Include="Log\ALogDecorater.cs" />
<Compile Include="Log\ILog.cs" />
<Compile Include="Log\NLogAdapter.cs" />
<Compile Include="Log\StackInfoDecorater.cs" />
<Compile Include="MultiMap.cs" />
<Compile Include="Network\AChannel.cs" />
<Compile Include="Network\AService.cs" />
<Compile Include="Network\TNet\PacketParser.cs" />
<Compile Include="Network\TNet\TBuffer.cs" />
<Compile Include="Network\TNet\TChannel.cs" />
<Compile Include="Network\TNet\TPoller.cs" />
<Compile Include="Network\TNet\TService.cs" />
<Compile Include="Network\TNet\TSocket.cs" />
<Compile Include="Network\UNet\Library.cs" />
<Compile Include="Network\UNet\NativeMethods.cs" />
<Compile Include="Network\UNet\NativeStructs.cs" />
<Compile Include="Network\UNet\UAddress.cs" />
<Compile Include="Network\UNet\UChannel.cs" />
<Compile Include="Network\UNet\UPacket.cs" />
<Compile Include="Network\UNet\UPoller.cs" />
<Compile Include="Network\UNet\UService.cs" />
<Compile Include="Network\UNet\USocket.cs" />
<Compile Include="Network\UNet\USocketManager.cs" />
<Compile Include="Object\Component.cs" />
<Compile Include="Object\Entity.cs" />
<Compile Include="Object\IAwake.cs" />
<Compile Include="Object\ILoader.cs" />
<Compile Include="Object\IStart.cs" />
<Compile Include="Object\IUpdate.cs" />
<Compile Include="Object\Object.cs" />
<Compile Include="Object\ObjectEventAttribute.cs" />
<Compile Include="Object\ObjectManager.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="QueueDictionary.cs" />
<Compile Include="TryLocker.cs" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>
\ No newline at end of file
using System;
using System.Threading;
namespace Base
{
public class TryLock : IDisposable
{
private object locked;
public bool HasLock { get; private set; }
public TryLock(object obj)
{
if (!Monitor.TryEnter(obj))
{
return;
}
this.HasLock = true;
this.locked = obj;
}
public void Dispose()
{
if (!this.HasLock)
{
return;
}
Monitor.Exit(this.locked);
this.locked = null;
this.HasLock = false;
}
}
}
using System;
using System.Collections.Generic;
using System.Reflection;
namespace Base
{
[ObjectEvent]
public class EventComponentEvent : ObjectEvent<EventComponent>, ILoader, IAwake
{
public void Load()
{
this.GetValue().Load();
}
public void Awake()
{
this.GetValue().Load();
}
}
/// <summary>
/// 事件分发
/// </summary>
public class EventComponent: Component
{
private Dictionary<EventIdType, List<object>> allEvents;
public void Load()
{
this.allEvents = new Dictionary<EventIdType, List<object>>();
Assembly[] assemblies = Object.ObjectManager.GetAssemblies();
foreach (Assembly assembly in assemblies)
{
Type[] types = assembly.GetTypes();
foreach (Type type in types)
{
object[] attrs = type.GetCustomAttributes(typeof(EventAttribute), false);
foreach (object attr in attrs)
{
EventAttribute aEventAttribute = (EventAttribute)attr;
object obj = Activator.CreateInstance(type);
if (!this.allEvents.ContainsKey(aEventAttribute.Type))
{
this.allEvents.Add(aEventAttribute.Type, new List<object>());
}
this.allEvents[aEventAttribute.Type].Add(obj);
}
}
}
}
public void Run(EventIdType type)
{
List<object> iEvents = null;
if (!this.allEvents.TryGetValue(type, out iEvents))
{
return;
}
foreach (object obj in iEvents)
{
try
{
IEvent iEvent = obj as IEvent;
if (iEvent == null)
{
throw new GameException($"event type: {type} is not IEvent");
}
iEvent.Run();
}
catch (Exception err)
{
Log.Error(err.ToString());
}
}
}
public void Run<A>(EventIdType type, A a)
{
List<object> iEvents = null;
if (!this.allEvents.TryGetValue(type, out iEvents))
{
return;
}
foreach (object obj in iEvents)
{
try
{
var iEvent = obj as IEvent<A>;
if (iEvent == null)
{
throw new GameException($"event type: {type} is not IEvent<{typeof (A).Name}>");
}
iEvent.Run(a);
}
catch (Exception err)
{
Log.Error(err.ToString());
}
}
}
public void Run<A, B>(EventIdType type, A a, B b)
{
List<object> iEvents = null;
if (!this.allEvents.TryGetValue(type, out iEvents))
{
return;
}
foreach (object obj in iEvents)
{
try
{
var iEvent = obj as IEvent<A, B>;
if (iEvent == null)
{
throw new GameException($"event type: {type} is not IEvent<{typeof (A).Name}, {typeof (B).Name}>");
}
iEvent.Run(a, b);
}
catch (Exception err)
{
Log.Error(err.ToString());
}
}
}
public void Run<A, B, C>(EventIdType type, A a, B b, C c)
{
List<object> iEvents = null;
if (!this.allEvents.TryGetValue(type, out iEvents))
{
return;
}
foreach (object obj in iEvents)
{
try
{
var iEvent = obj as IEvent<A, B, C>;
if (iEvent == null)
{
throw new GameException($"event type: {type} is not IEvent<{typeof (A).Name}, {typeof (B).Name}, {typeof (C).Name}>");
}
iEvent.Run(a, b, c);
}
catch (Exception err)
{
Log.Error(err.ToString());
}
}
}
public void Run<A, B, C, D>(EventIdType type, A a, B b, C c, D d)
{
List<object> iEvents = null;
if (!this.allEvents.TryGetValue(type, out iEvents))
{
return;
}
foreach (object obj in iEvents)
{
try
{
var iEvent = obj as IEvent<A, B, C, D>;
if (iEvent == null)
{
throw new GameException($"event type: {type} is not IEvent<{typeof (A).Name}, {typeof (B).Name}, {typeof (C).Name}, {typeof (D).Name}>");
}
iEvent.Run(a, b, c, d);
}
catch (Exception err)
{
Log.Error(err.ToString());
}
}
}
public void Run<A, B, C, D, E>(EventIdType type, A a, B b, C c, D d, E e)
{
List<object> iEvents = null;
if (!this.allEvents.TryGetValue(type, out iEvents))
{
return;
}
foreach (object obj in iEvents)
{
try
{
var iEvent = obj as IEvent<A, B, C, D, E>;
if (iEvent == null)
{
throw new GameException(
$"event type: {type} is not IEvent<{typeof (A).Name}, {typeof (B).Name}, {typeof (C).Name}, {typeof (D).Name}, {typeof (E).Name}>");
}
iEvent.Run(a, b, c, d, e);
}
catch (Exception err)
{
Log.Error(err.ToString());
}
}
}
public void Run<A, B, C, D, E,F>(EventIdType type, A a, B b, C c, D d, E e,F f)
{
List<object> iEvents = null;
if (!this.allEvents.TryGetValue(type, out iEvents))
{
return;
}
foreach (object obj in iEvents)
{
try
{
var iEvent = obj as IEvent<A, B, C, D, E,F>;
if (iEvent == null)
{
throw new GameException(
$"event type: {type} is not IEvent<{typeof(A).Name}, {typeof(B).Name}, {typeof(C).Name}, {typeof(D).Name}, {typeof(E).Name}>");
}
iEvent.Run(a, b, c, d, e,f);
}
catch (Exception err)
{
Log.Error(err.ToString());
}
}
}
}
}
\ No newline at end of file
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace Base
{
public enum NetChannelType
{
Login,
Gate,
Battle,
}
[ObjectEvent]
public class MessageComponentEvent : ObjectEvent<MessageComponent>, IAwake<AChannel>
{
public void Awake(AChannel aChannel)
{
this.GetValue().Awake(aChannel);
}
}
/// <summary>
/// 消息收发
/// </summary>
public class MessageComponent: Component
{
private uint RpcId { get; set; }
private readonly Dictionary<uint, Action<byte[], int, int>> requestCallback = new Dictionary<uint, Action<byte[], int, int>>();
private readonly Dictionary<Opcode, Action<byte[], int, int>> waitCallback = new Dictionary<Opcode, Action<byte[], int, int>>();
private AChannel channel;
public void Awake(AChannel aChannel)
{
this.channel = aChannel;
this.UpdateChannel();
}
private async void UpdateChannel()
{
while (true)
{
byte[] messageBytes;
try
{
messageBytes = await channel.Recv();
}
catch (Exception e)
{
Log.Error(e.ToString());
continue;
}
if (messageBytes.Length < 6)
{
continue;
}
Opcode opcode = (Opcode)BitConverter.ToUInt16(messageBytes, 0);
try
{
this.Run(opcode, messageBytes);
}
catch (Exception e)
{
Log.Error(e.ToString());
}
}
}
private void Run(Opcode opcode, byte[] messageBytes)
{
int offset = 0;
uint flagUInt = BitConverter.ToUInt32(messageBytes, 2);
bool isCompressed = (byte)(flagUInt >> 24) == 1;
if (isCompressed) // 表示有压缩,需要解压缩
{
messageBytes = ZipHelper.Decompress(messageBytes, 6, messageBytes.Length - 6);
offset = 0;
}
else
{
offset = 6;
}
uint rpcId = flagUInt & 0x0fff;
this.RunDecompressedBytes(opcode, rpcId, messageBytes, offset);
}
private void RunDecompressedBytes(Opcode opcode, uint rpcId, byte[] messageBytes, int offset)
{
Action<byte[], int, int> action;
if (this.requestCallback.TryGetValue(rpcId, out action))
{
this.requestCallback.Remove(rpcId);
action(messageBytes, offset, messageBytes.Length - offset);
return;
}
if (this.waitCallback.TryGetValue(opcode, out action))
{
this.waitCallback.Remove(opcode);
action(messageBytes, offset, messageBytes.Length - offset);
return;
}
Server.Scene.GetComponent<MessageHandlerComponent>().Handle(this.Owner, opcode, messageBytes, offset);
}
public Task<Response> CallAsync<Response>(object request, CancellationToken cancellationToken) where Response : IErrorMessage
{
this.Send(request, ++this.RpcId);
var tcs = new TaskCompletionSource<Response>();
this.requestCallback[this.RpcId] = (bytes, offset, count) =>
{
try
{
Response response = MongoHelper.FromBson<Response>(bytes, offset, count);
Opcode opcode = EnumHelper.FromString<Opcode>(response.GetType().Name);
if (OpcodeHelper.IsNeedDebugLogMessage(opcode))
{
Log.Debug(MongoHelper.ToJson(response));
}
if (response.ErrorMessage.errno != (int)ErrorCode.ERR_Success)
{
tcs.SetException(new RpcException((ErrorCode)response.ErrorMessage.errno, response.ErrorMessage.msg.Utf8ToStr()));
return;
}
tcs.SetResult(response);
}
catch (Exception e)
{
tcs.SetException(new GameException($"Rpc Error: {typeof(Response).FullName}", e));
}
};
cancellationToken.Register(() => { this.requestCallback.Remove(this.RpcId); });
return tcs.Task;
}
/// <summary>
/// Rpc调用,发送一个消息,等待返回一个消息
/// </summary>
/// <typeparam name="Response"></typeparam>
/// <param name="request"></param>
/// <returns></returns>
public Task<Response> CallAsync<Response>(object request) where Response : IErrorMessage
{
this.Send(request, ++this.RpcId);
var tcs = new TaskCompletionSource<Response>();
this.requestCallback[this.RpcId] = (bytes, offset, count) =>
{
try
{
Response response = MongoHelper.FromBson<Response>(bytes, offset, count);
Opcode opcode = EnumHelper.FromString<Opcode>(response.GetType().Name);
if (OpcodeHelper.IsNeedDebugLogMessage(opcode))
{
Log.Debug(MongoHelper.ToJson(response));
}
if (response.ErrorMessage.errno != (int) ErrorCode.ERR_Success)
{
tcs.SetException(new RpcException((ErrorCode)response.ErrorMessage.errno, response.ErrorMessage.msg.Utf8ToStr()));
return;
}
tcs.SetResult(response);
}
catch (Exception e)
{
tcs.SetException(new GameException($"Rpc Error: {typeof(Response).FullName}", e));
}
};
return tcs.Task;
}
/// <summary>
/// 不发送消息,直接等待返回一个消息
/// </summary>
/// <typeparam name="Response"></typeparam>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public Task<Response> WaitAsync<Response>(CancellationToken cancellationToken) where Response : class
{
var tcs = new TaskCompletionSource<Response>();
Opcode opcode = EnumHelper.FromString<Opcode>(typeof(Response).Name);
this.waitCallback[opcode] = (bytes, offset, count) =>
{
try
{
Response response = MongoHelper.FromBson<Response>(bytes, offset, count);
Opcode op = EnumHelper.FromString<Opcode>(response.GetType().Name);
if (OpcodeHelper.IsNeedDebugLogMessage(op))
{
Log.Debug(MongoHelper.ToJson(response));
}
tcs.SetResult(response);
}
catch (Exception e)
{
tcs.SetException(new GameException($"Wait Error: {typeof(Response).FullName}", e));
}
};
cancellationToken.Register(() => { this.waitCallback.Remove(opcode); });
return tcs.Task;
}
/// <summary>
/// 不发送消息,直接等待返回一个消息
/// </summary>
/// <typeparam name="Response"></typeparam>
/// <returns></returns>
public Task<Response> WaitAsync<Response>() where Response : class
{
var tcs = new TaskCompletionSource<Response>();
Opcode opcode = EnumHelper.FromString<Opcode>(typeof(Response).Name);
this.waitCallback[opcode] = (bytes, offset, count) =>
{
try
{
Response response = MongoHelper.FromBson<Response>(bytes, offset, count);
Opcode op = EnumHelper.FromString<Opcode>(response.GetType().Name);
if (OpcodeHelper.IsNeedDebugLogMessage(op))
{
Log.Debug(MongoHelper.ToJson(response));
}
tcs.SetResult(response);
}
catch (Exception e)
{
tcs.SetException(new GameException($"Wait Error: {typeof(Response).FullName}", e));
}
};
return tcs.Task;
}
public void Send(object message)
{
this.Send(message, 0);
}
private void Send(object message, uint rpcId)
{
Opcode opcode = EnumHelper.FromString<Opcode>(message.GetType().Name);
byte[] opcodeBytes = BitConverter.GetBytes((ushort)opcode);
byte[] seqBytes = BitConverter.GetBytes(rpcId);
byte[] messageBytes = MongoHelper.ToBson(message);
if (channel == null)
{
throw new GameException("game channel not found!");
}
channel.Send(new List<byte[]> { opcodeBytes, seqBytes, messageBytes });
if (OpcodeHelper.IsNeedDebugLogMessage(opcode))
{
Log.Debug(MongoHelper.ToJson(message));
}
}
public override void Dispose()
{
if (this.Id == 0)
{
return;
}
base.Dispose();
channel.Dispose();
}
}
}
\ No newline at end of file
using System;
using System.Collections.Generic;
using System.Reflection;
namespace Base
{
[ObjectEvent]
public class MessageHandlerComponentEvent : ObjectEvent<MessageHandlerComponent>, ILoader, IAwake<SceneType>
{
public void Load()
{
this.GetValue().Load();
}
public void Awake(SceneType sceneType)
{
this.GetValue().Awake(sceneType);
}
}
/// <summary>
/// 消息分发组件
/// </summary>
public class MessageHandlerComponent: Component
{
private SceneType SceneType;
private Dictionary<Opcode, List<Action<Entity, byte[], int, int>>> events;
public void Awake(SceneType sceneType)
{
this.SceneType = sceneType;
this.Load();
}
public void Load()
{
this.events = new Dictionary<Opcode, List<Action<Entity, byte[], int, int>>>();
Assembly[] assemblies = Object.ObjectManager.GetAssemblies();
foreach (Assembly assembly in assemblies)
{
Type[] types = assembly.GetTypes();
foreach (Type type in types)
{
object[] attrs = type.GetCustomAttributes(typeof(MessageAttribute), false);
if (attrs.Length == 0)
{
continue;
}
MessageAttribute messageAttribute = (MessageAttribute)attrs[0];
if (messageAttribute.SceneType != this.SceneType)
{
continue;
}
object obj = Activator.CreateInstance(type);
IMRegister<MessageHandlerComponent> iMRegister = obj as IMRegister<MessageHandlerComponent>;
if (iMRegister == null)
{
throw new GameException($"message handler not inherit IEventSync or IEventAsync interface: {obj.GetType().FullName}");
}
iMRegister.Register(this);
}
}
}
public void Register<T>(Action<Entity, T> action)
{
Opcode opcode = EnumHelper.FromString<Opcode>(typeof (T).Name);
if (!this.events.ContainsKey(opcode))
{
this.events.Add(opcode, new List<Action<Entity, byte[], int, int>>());
}
List<Action<Entity, byte[], int, int>> actions = this.events[opcode];
actions.Add((entity, messageBytes, offset, count) =>
{
T t;
try
{
t = MongoHelper.FromBson<T>(messageBytes, offset, count);
}
catch (Exception ex)
{
throw new GameException("解释消息失败:" + opcode, ex);
}
if (OpcodeHelper.IsNeedDebugLogMessage(opcode))
{
Log.Debug(MongoHelper.ToJson(t));
}
action(entity, t);
});
}
public void Handle(Entity entity, Opcode opcode, byte[] messageBytes, int offset)
{
List<Action<Entity, byte[], int, int>> actions;
if (!this.events.TryGetValue(opcode, out actions))
{
if (this.SceneType == SceneType.Game)
{
Log.Error($"消息{opcode}没有处理");
}
return;
}
foreach (var ev in actions)
{
try
{
ev(entity, messageBytes, offset, messageBytes.Length - offset);
}
catch (Exception e)
{
Log.Error(e.ToString());
}
}
}
public override void Dispose()
{
if (this.Id == 0)
{
return;
}
base.Dispose();
}
}
}
\ No newline at end of file
namespace Base
{
public enum SceneType
{
Share,
Game,
Login,
Lobby,
Map,
Launcher,
Robot,
BehaviorTreeScene,
RobotClient,
Realm,
Gate,
}
[ObjectEvent]
public class SceneEvent : ObjectEvent<Scene>, IAwake<SceneType, string>
{
public void Awake(SceneType sceneType, string name)
{
this.GetValue().Awake(sceneType, name);
}
}
public sealed class Scene: Component
{
public SceneType SceneType { get; set; }
public string Name { get; set; }
public void Awake(SceneType sceneType, string name)
{
this.SceneType = sceneType;
this.Name = name;
}
public override void Dispose()
{
if (this.Id == 0)
{
return;
}
base.Dispose();
}
}
}
\ No newline at end of file
namespace Base
{
/// <summary>
/// 用于同步服务端和客户端时间
/// </summary>
public class TimeComponent : Component
{
private long syncTime;
private long syncClientTime;
public long SyncTime
{
get
{
return this.syncTime;
}
set
{
this.syncTime = value;
this.syncClientTime = TimeHelper.ClientNow();
}
}
public long Now()
{
return TimeHelper.ClientNow() - this.syncClientTime + syncTime;
}
}
}
\ No newline at end of file
using System;
namespace Base
{
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public abstract class AEventAttribute: Attribute
{
public EventIdType Type { get; private set; }
protected AEventAttribute(EventIdType type)
{
this.Type = type;
}
}
}
\ No newline at end of file
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
namespace Base
{
public enum EventIdType
{
NetworkChannelError,
}
}
\ No newline at end of file
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册