diff --git a/Config/NetworkProtocol/Inner/InnerMessage.proto b/Config/NetworkProtocol/Inner/InnerMessage.proto index feb5e4f..fad5279 100644 --- a/Config/NetworkProtocol/Inner/InnerMessage.proto +++ b/Config/NetworkProtocol/Inner/InnerMessage.proto @@ -59,7 +59,7 @@ message G2Map_EnterMapRequest // IRouteRequest,Map2G_EnterMapResponse { string RoomCode = 1; //房间代码 int64 AccountId = 2; //账号id - int32 MapId = 3; //地图id + int32 MapId =3; //地图id } /// 请求进入房间响应 @@ -67,7 +67,7 @@ message Map2G_EnterMapResponse // IRouteResponse { string RoomCode = 1; //房间代码 int32 MapId = 2; //地图id - repeated MapUnitInfo Units = 3; //房间玩家列表 + repeated MapUnitInfo Units = 2; //房间玩家列表 } diff --git a/Config/NetworkProtocol/Outer/data/CommonProtoData.proto b/Config/NetworkProtocol/Outer/data/CommonProtoData.proto index 83614ef..5a4ce7f 100644 --- a/Config/NetworkProtocol/Outer/data/CommonProtoData.proto +++ b/Config/NetworkProtocol/Outer/data/CommonProtoData.proto @@ -86,7 +86,7 @@ message FishInfo { uint32 ConfigId = 1; //配置id int64 Id = 2; //物品id - int32 Weight = 3; //重量 + int32 Weight =3; //重量 int64 GetTime = 4; //获得时间 int64 ExpirationTime = 5; //失效时间 } diff --git a/Entity/AssemblyHelper.cs b/Entity/AssemblyHelper.cs index d9696d4..ae347d3 100644 --- a/Entity/AssemblyHelper.cs +++ b/Entity/AssemblyHelper.cs @@ -1,27 +1,30 @@ -using System.Runtime.Loader; -using Fantasy.Generated; +using System.Reflection; +using System.Runtime.Loader; -namespace Fantasy +namespace NB { public static class AssemblyHelper { private const string HotfixDll = "Hotfix"; private static AssemblyLoadContext? _assemblyLoadContext = null; - - public static void Initialize() + + public static System.Reflection.Assembly[] Assemblies { - LoadEntityAssembly(); - LoadHotfixAssembly(); + get + { + var assemblies = new System.Reflection.Assembly[2]; + assemblies[0] = LoadEntityAssembly(); + assemblies[1] = LoadHotfixAssembly(); + return assemblies; + } } - private static void LoadEntityAssembly() + private static System.Reflection.Assembly LoadEntityAssembly() { - // .NET 运行时采用延迟加载机制,如果代码中不使用程序集的类型,程序集不会被加载 - // 执行一下,触发运行时强制加载从而自动注册到框架中 - Entity_AssemblyMarker.EnsureLoaded(); + return typeof(AssemblyHelper).Assembly; } - public static System.Reflection.Assembly LoadHotfixAssembly() + private static System.Reflection.Assembly LoadHotfixAssembly() { if (_assemblyLoadContext != null) { @@ -30,20 +33,9 @@ namespace Fantasy } _assemblyLoadContext = new AssemblyLoadContext(HotfixDll, true); - var dllBytes = File.ReadAllBytes(Path.Combine(AppContext.BaseDirectory, $"{HotfixDll}.dll")); - var pdbBytes = File.ReadAllBytes(Path.Combine(AppContext.BaseDirectory, $"{HotfixDll}.pdb")); - var assembly = _assemblyLoadContext.LoadFromStream(new MemoryStream(dllBytes), new MemoryStream(pdbBytes)); - // 强制触发 ModuleInitializer 执行 - // AssemblyLoadContext.LoadFromStream 只加载程序集到内存,不会自动触发 ModuleInitializer - // 必须访问程序集中的类型才能触发初始化,这里通过反射调用生成的 AssemblyMarker - // 注意:此方法仅用于热重载场景(JIT),Native AOT 不支持动态加载 - var markerType = assembly.GetType("Fantasy.Generated.Hotfix_AssemblyMarker"); - if (markerType != null) - { - var method = markerType.GetMethod("EnsureLoaded"); - method?.Invoke(null, null); - } - return assembly; + var dllBytes = File.ReadAllBytes(Path.Combine(Environment.CurrentDirectory, $"{HotfixDll}.dll")); + var pdbBytes = File.ReadAllBytes(Path.Combine(Environment.CurrentDirectory, $"{HotfixDll}.pdb")); + return _assemblyLoadContext.LoadFromStream(new MemoryStream(dllBytes), new MemoryStream(pdbBytes)); } } } \ No newline at end of file diff --git a/Entity/Entity.csproj b/Entity/Entity.csproj index e4a5a40..5a06389 100644 --- a/Entity/Entity.csproj +++ b/Entity/Entity.csproj @@ -9,33 +9,31 @@ - TRACE;FANTASY_NET + TRACE;FANTASY_NET - TRACE;FANTASY_NET + TRACE;FANTASY_NET - - - - + + + - - - + + + - - - - - + + + + + diff --git a/Entity/Generate/ConfigTable/Entity/BaitConfig.cs b/Entity/Generate/ConfigTable/Entity/BaitConfig.cs index 945d28d..a38b53c 100644 --- a/Entity/Generate/ConfigTable/Entity/BaitConfig.cs +++ b/Entity/Generate/ConfigTable/Entity/BaitConfig.cs @@ -11,7 +11,7 @@ using Fantasy.ConfigTable; namespace NBF { [ProtoContract] - public sealed partial class BaitConfig : ASerialize, IConfigTable + public sealed partial class BaitConfig : ASerialize, IProto, IConfigTable { [ProtoMember(1)] diff --git a/Entity/Generate/ConfigTable/Entity/BasicConfig.cs b/Entity/Generate/ConfigTable/Entity/BasicConfig.cs index ba6d2e2..a9a9cbb 100644 --- a/Entity/Generate/ConfigTable/Entity/BasicConfig.cs +++ b/Entity/Generate/ConfigTable/Entity/BasicConfig.cs @@ -11,7 +11,7 @@ using Fantasy.ConfigTable; namespace NBF { [ProtoContract] - public sealed partial class BasicConfig : ASerialize, IConfigTable + public sealed partial class BasicConfig : ASerialize, IProto, IConfigTable { [ProtoMember(1)] diff --git a/Entity/Generate/ConfigTable/Entity/BobberConfig.cs b/Entity/Generate/ConfigTable/Entity/BobberConfig.cs index 782f5bb..d6ca6db 100644 --- a/Entity/Generate/ConfigTable/Entity/BobberConfig.cs +++ b/Entity/Generate/ConfigTable/Entity/BobberConfig.cs @@ -11,7 +11,7 @@ using Fantasy.ConfigTable; namespace NBF { [ProtoContract] - public sealed partial class BobberConfig : ASerialize, IConfigTable + public sealed partial class BobberConfig : ASerialize, IProto, IConfigTable { [ProtoMember(1)] diff --git a/Entity/Generate/ConfigTable/Entity/FeederConfig.cs b/Entity/Generate/ConfigTable/Entity/FeederConfig.cs index 4ffc1f0..efab711 100644 --- a/Entity/Generate/ConfigTable/Entity/FeederConfig.cs +++ b/Entity/Generate/ConfigTable/Entity/FeederConfig.cs @@ -11,7 +11,7 @@ using Fantasy.ConfigTable; namespace NBF { [ProtoContract] - public sealed partial class FeederConfig : ASerialize, IConfigTable + public sealed partial class FeederConfig : ASerialize, IProto, IConfigTable { [ProtoMember(1)] diff --git a/Entity/Generate/ConfigTable/Entity/FishConfig.cs b/Entity/Generate/ConfigTable/Entity/FishConfig.cs index 9a8da1e..ed434cf 100644 --- a/Entity/Generate/ConfigTable/Entity/FishConfig.cs +++ b/Entity/Generate/ConfigTable/Entity/FishConfig.cs @@ -11,7 +11,7 @@ using Fantasy.ConfigTable; namespace NBF { [ProtoContract] - public sealed partial class FishConfig : ASerialize, IConfigTable + public sealed partial class FishConfig : ASerialize, IProto, IConfigTable { [ProtoMember(1)] diff --git a/Entity/Generate/ConfigTable/Entity/HookConfig.cs b/Entity/Generate/ConfigTable/Entity/HookConfig.cs index 747dde9..4fc28ce 100644 --- a/Entity/Generate/ConfigTable/Entity/HookConfig.cs +++ b/Entity/Generate/ConfigTable/Entity/HookConfig.cs @@ -11,7 +11,7 @@ using Fantasy.ConfigTable; namespace NBF { [ProtoContract] - public sealed partial class HookConfig : ASerialize, IConfigTable + public sealed partial class HookConfig : ASerialize, IProto, IConfigTable { [ProtoMember(1)] diff --git a/Entity/Generate/ConfigTable/Entity/InitConfig.cs b/Entity/Generate/ConfigTable/Entity/InitConfig.cs index 1e4806a..7abba17 100644 --- a/Entity/Generate/ConfigTable/Entity/InitConfig.cs +++ b/Entity/Generate/ConfigTable/Entity/InitConfig.cs @@ -11,7 +11,7 @@ using Fantasy.ConfigTable; namespace NBF { [ProtoContract] - public sealed partial class InitConfig : ASerialize, IConfigTable + public sealed partial class InitConfig : ASerialize, IProto, IConfigTable { [ProtoMember(1)] diff --git a/Entity/Generate/ConfigTable/Entity/ItemConfig.cs b/Entity/Generate/ConfigTable/Entity/ItemConfig.cs index f3bbe4d..89302f0 100644 --- a/Entity/Generate/ConfigTable/Entity/ItemConfig.cs +++ b/Entity/Generate/ConfigTable/Entity/ItemConfig.cs @@ -11,7 +11,7 @@ using Fantasy.ConfigTable; namespace NBF { [ProtoContract] - public sealed partial class ItemConfig : ASerialize, IConfigTable + public sealed partial class ItemConfig : ASerialize, IProto, IConfigTable { [ProtoMember(1)] diff --git a/Entity/Generate/ConfigTable/Entity/LineConfig.cs b/Entity/Generate/ConfigTable/Entity/LineConfig.cs index 8d0dab9..0ab44c7 100644 --- a/Entity/Generate/ConfigTable/Entity/LineConfig.cs +++ b/Entity/Generate/ConfigTable/Entity/LineConfig.cs @@ -11,7 +11,7 @@ using Fantasy.ConfigTable; namespace NBF { [ProtoContract] - public sealed partial class LineConfig : ASerialize, IConfigTable + public sealed partial class LineConfig : ASerialize, IProto, IConfigTable { [ProtoMember(1)] diff --git a/Entity/Generate/ConfigTable/Entity/LureConfig.cs b/Entity/Generate/ConfigTable/Entity/LureConfig.cs index 9029db9..0b733e2 100644 --- a/Entity/Generate/ConfigTable/Entity/LureConfig.cs +++ b/Entity/Generate/ConfigTable/Entity/LureConfig.cs @@ -11,7 +11,7 @@ using Fantasy.ConfigTable; namespace NBF { [ProtoContract] - public sealed partial class LureConfig : ASerialize, IConfigTable + public sealed partial class LureConfig : ASerialize, IProto, IConfigTable { [ProtoMember(1)] diff --git a/Entity/Generate/ConfigTable/Entity/ReelConfig.cs b/Entity/Generate/ConfigTable/Entity/ReelConfig.cs index 36d031a..b824471 100644 --- a/Entity/Generate/ConfigTable/Entity/ReelConfig.cs +++ b/Entity/Generate/ConfigTable/Entity/ReelConfig.cs @@ -11,7 +11,7 @@ using Fantasy.ConfigTable; namespace NBF { [ProtoContract] - public sealed partial class ReelConfig : ASerialize, IConfigTable + public sealed partial class ReelConfig : ASerialize, IProto, IConfigTable { [ProtoMember(1)] diff --git a/Entity/Generate/ConfigTable/Entity/RingConfig.cs b/Entity/Generate/ConfigTable/Entity/RingConfig.cs new file mode 100644 index 0000000..42b537f --- /dev/null +++ b/Entity/Generate/ConfigTable/Entity/RingConfig.cs @@ -0,0 +1,85 @@ +using System; +using ProtoBuf; +using Fantasy; +using System.Linq; +using System.Reflection; +using System.Collections.Generic; +using System.Collections.Concurrent; +using Fantasy.Serialize; +using Fantasy.ConfigTable; + +namespace NBF +{ + [ProtoContract] + public sealed partial class RingConfig : ASerialize, IProto, IConfigTable + { + + [ProtoMember(1)] + public uint Id { get; set; } // Id + [ProtoMember(2)] + public string Model { get; set; } // 模型 + [ProtoMember(3)] + public string Icon { get; set; } // 图标 + [ProtoIgnore] + public uint Key => Id; + + #region Static + + private static ConfigContext Context => ConfigTableHelper.Table(); + + public static RingConfig Get(uint key) + { + return Context.Get(key); + } + + public static RingConfig Get(Predicate match) + { + return Context.Get(match); + } + + public static RingConfig Fist() + { + return Context.Fist(); + } + + public static RingConfig Last() + { + return Context.Last(); + } + + public static RingConfig Fist(Predicate match) + { + return Context.Fist(match); + } + + public static RingConfig Last(Predicate match) + { + return Context.Last(match); + } + + public static int Count() + { + return Context.Count(); + } + + public static int Count(Func predicate) + { + return Context.Count(predicate); + } + + public static List GetList() + { + return Context.GetList(); + } + + public static List GetList(Predicate match) + { + return Context.GetList(match); + } + public static void ParseJson(Newtonsoft.Json.Linq.JArray arr) + { + ConfigTableHelper.ParseLine(arr); + } + #endregion + } +} \ No newline at end of file diff --git a/Entity/Generate/ConfigTable/Entity/RodConfig.cs b/Entity/Generate/ConfigTable/Entity/RodConfig.cs index ea0445c..4266ee6 100644 --- a/Entity/Generate/ConfigTable/Entity/RodConfig.cs +++ b/Entity/Generate/ConfigTable/Entity/RodConfig.cs @@ -11,7 +11,7 @@ using Fantasy.ConfigTable; namespace NBF { [ProtoContract] - public sealed partial class RodConfig : ASerialize, IConfigTable + public sealed partial class RodConfig : ASerialize, IProto, IConfigTable { [ProtoMember(1)] diff --git a/Entity/Generate/ConfigTable/Entity/RodRingConfig.cs b/Entity/Generate/ConfigTable/Entity/RodRingConfig.cs index fe90c01..5502d6a 100644 --- a/Entity/Generate/ConfigTable/Entity/RodRingConfig.cs +++ b/Entity/Generate/ConfigTable/Entity/RodRingConfig.cs @@ -11,7 +11,7 @@ using Fantasy.ConfigTable; namespace NBF { [ProtoContract] - public sealed partial class RodRingConfig : ASerialize, IConfigTable + public sealed partial class RodRingConfig : ASerialize, IProto, IConfigTable { [ProtoMember(1)] diff --git a/Entity/Generate/ConfigTable/Entity/WeightConfig.cs b/Entity/Generate/ConfigTable/Entity/WeightConfig.cs new file mode 100644 index 0000000..95d355c --- /dev/null +++ b/Entity/Generate/ConfigTable/Entity/WeightConfig.cs @@ -0,0 +1,89 @@ +using System; +using ProtoBuf; +using Fantasy; +using System.Linq; +using System.Reflection; +using System.Collections.Generic; +using System.Collections.Concurrent; +using Fantasy.Serialize; +using Fantasy.ConfigTable; + +namespace NBF +{ + [ProtoContract] + public sealed partial class WeightConfig : ASerialize, IProto, IConfigTable + { + + [ProtoMember(1)] + public uint Id { get; set; } // Id + [ProtoMember(2)] + public string Model { get; set; } // 模型 + [ProtoMember(3)] + public string Icon { get; set; } // 图标 + [ProtoMember(4)] + public uint Type { get; set; } // 类型 + [ProtoMember(5)] + public uint Weight { get; set; } // 重量(克) + [ProtoIgnore] + public uint Key => Id; + + #region Static + + private static ConfigContext Context => ConfigTableHelper.Table(); + + public static WeightConfig Get(uint key) + { + return Context.Get(key); + } + + public static WeightConfig Get(Predicate match) + { + return Context.Get(match); + } + + public static WeightConfig Fist() + { + return Context.Fist(); + } + + public static WeightConfig Last() + { + return Context.Last(); + } + + public static WeightConfig Fist(Predicate match) + { + return Context.Fist(match); + } + + public static WeightConfig Last(Predicate match) + { + return Context.Last(match); + } + + public static int Count() + { + return Context.Count(); + } + + public static int Count(Func predicate) + { + return Context.Count(predicate); + } + + public static List GetList() + { + return Context.GetList(); + } + + public static List GetList(Predicate match) + { + return Context.GetList(match); + } + public static void ParseJson(Newtonsoft.Json.Linq.JArray arr) + { + ConfigTableHelper.ParseLine(arr); + } + #endregion + } +} \ No newline at end of file diff --git a/Entity/Generate/NetworkProtocol/CommonProtoData.cs b/Entity/Generate/NetworkProtocol/CommonProtoData.cs index 86b2633..c90bbd5 100644 --- a/Entity/Generate/NetworkProtocol/CommonProtoData.cs +++ b/Entity/Generate/NetworkProtocol/CommonProtoData.cs @@ -1,6 +1,5 @@ using ProtoBuf; -using System; using System.Collections.Generic; using MongoDB.Bson.Serialization.Attributes; using Fantasy; @@ -17,12 +16,12 @@ using Fantasy.Serialize; #pragma warning disable CS8618 namespace Fantasy -{ +{ /// /// 角色基础信息 /// [ProtoContract] - public partial class RoleBaseInfo : AMessage + public partial class RoleBaseInfo : AMessage, IProto { public static RoleBaseInfo Create(Scene scene) { @@ -54,7 +53,7 @@ namespace Fantasy public VipInfo VipInfo { get; set; } } [ProtoContract] - public partial class KeyValueInt64 : AMessage + public partial class KeyValueInt64 : AMessage, IProto { public static KeyValueInt64 Create(Scene scene) { @@ -77,7 +76,7 @@ namespace Fantasy /// 角色信息 /// [ProtoContract] - public partial class RoleInfo : AMessage + public partial class RoleInfo : AMessage, IProto { public static RoleInfo Create(Scene scene) { @@ -124,7 +123,7 @@ namespace Fantasy /// 角色信息 /// [ProtoContract] - public partial class RoleSimpleInfo : AMessage + public partial class RoleSimpleInfo : AMessage, IProto { public static RoleSimpleInfo Create(Scene scene) { @@ -162,7 +161,7 @@ namespace Fantasy /// VIP信息 /// [ProtoContract] - public partial class VipInfo : AMessage + public partial class VipInfo : AMessage, IProto { public static VipInfo Create(Scene scene) { @@ -188,7 +187,7 @@ namespace Fantasy /// 奖励信息 /// [ProtoContract] - public partial class AwardInfo : AMessage + public partial class AwardInfo : AMessage, IProto { public static AwardInfo Create(Scene scene) { @@ -211,7 +210,7 @@ namespace Fantasy /// 玩家当前使用钓组信息 /// [ProtoContract] - public partial class ItemBindInfo : AMessage + public partial class ItemBindInfo : AMessage, IProto { public static ItemBindInfo Create(Scene scene) { @@ -234,7 +233,7 @@ namespace Fantasy /// 物品信息 /// [ProtoContract] - public partial class ItemInfo : AMessage + public partial class ItemInfo : AMessage, IProto { public static ItemInfo Create(Scene scene) { @@ -269,7 +268,7 @@ namespace Fantasy /// fish信息 /// [ProtoContract] - public partial class FishInfo : AMessage + public partial class FishInfo : AMessage, IProto { public static FishInfo Create(Scene scene) { @@ -298,7 +297,7 @@ namespace Fantasy public long ExpirationTime { get; set; } } [ProtoContract] - public partial class ActivityInfo : AMessage + public partial class ActivityInfo : AMessage, IProto { public static ActivityInfo Create(Scene scene) { @@ -327,7 +326,7 @@ namespace Fantasy /// 技能情况 /// [ProtoContract] - public partial class SkillInfo : AMessage + public partial class SkillInfo : AMessage, IProto { public static SkillInfo Create(Scene scene) { @@ -350,4 +349,3 @@ namespace Fantasy public int Exp { get; set; } } } - diff --git a/Entity/Generate/NetworkProtocol/GameMessage.cs b/Entity/Generate/NetworkProtocol/GameMessage.cs index fee0e91..26c8f1c 100644 --- a/Entity/Generate/NetworkProtocol/GameMessage.cs +++ b/Entity/Generate/NetworkProtocol/GameMessage.cs @@ -1,6 +1,5 @@ using ProtoBuf; -using System; using System.Collections.Generic; using MongoDB.Bson.Serialization.Attributes; using Fantasy; @@ -17,7 +16,7 @@ using Fantasy.Serialize; #pragma warning disable CS8618 namespace Fantasy -{ +{ /// /// /////////// ******** 物品信息 *******///////////// /// @@ -25,7 +24,7 @@ namespace Fantasy /// 请求背包列表 /// [ProtoContract] - public partial class C2Game_GetItemsRequest : AMessage, ICustomRouteRequest + public partial class C2Game_GetItemsRequest : AMessage, ICustomRouteRequest, IProto { public static C2Game_GetItemsRequest Create(Scene scene) { @@ -47,7 +46,7 @@ namespace Fantasy /// 请求背包列表响应 /// [ProtoContract] - public partial class Game2C_GetItemsResponse : AMessage, ICustomRouteResponse + public partial class Game2C_GetItemsResponse : AMessage, ICustomRouteResponse, IProto { public static Game2C_GetItemsResponse Create(Scene scene) { @@ -74,7 +73,7 @@ namespace Fantasy /// 请求使用物品 /// [ProtoContract] - public partial class C2Game_UseItemRequest : AMessage, ICustomRouteRequest + public partial class C2Game_UseItemRequest : AMessage, ICustomRouteRequest, IProto { public static C2Game_UseItemRequest Create(Scene scene) { @@ -96,7 +95,7 @@ namespace Fantasy /// 请求使用物品响应 /// [ProtoContract] - public partial class Game2C_UseItemResponse : AMessage, ICustomRouteResponse + public partial class Game2C_UseItemResponse : AMessage, ICustomRouteResponse, IProto { public static Game2C_UseItemResponse Create(Scene scene) { @@ -117,7 +116,7 @@ namespace Fantasy /// 物品变化 /// [ProtoContract] - public partial class Game2C_ItemChange : AMessage, ICustomRouteMessage + public partial class Game2C_ItemChange : AMessage, ICustomRouteMessage, IProto { public static Game2C_ItemChange Create(Scene scene) { @@ -149,7 +148,7 @@ namespace Fantasy /// 请求安装或取下配件 /// [ProtoContract] - public partial class C2Game_RigChangeRequest : AMessage, ICustomRouteRequest + public partial class C2Game_RigChangeRequest : AMessage, ICustomRouteRequest, IProto { public static C2Game_RigChangeRequest Create(Scene scene) { @@ -180,7 +179,7 @@ namespace Fantasy /// 请求安装配件响应 /// [ProtoContract] - public partial class Game2C_RigChangeResponse : AMessage, ICustomRouteResponse + public partial class Game2C_RigChangeResponse : AMessage, ICustomRouteResponse, IProto { public static Game2C_RigChangeResponse Create(Scene scene) { @@ -207,7 +206,7 @@ namespace Fantasy /// 请求鱼护列表 /// [ProtoContract] - public partial class C2Game_GetFishsRequest : AMessage, ICustomRouteRequest + public partial class C2Game_GetFishsRequest : AMessage, ICustomRouteRequest, IProto { public static C2Game_GetFishsRequest Create(Scene scene) { @@ -229,7 +228,7 @@ namespace Fantasy /// 请求鱼护列表响应 /// [ProtoContract] - public partial class Game2C_GetFishsResponse : AMessage, ICustomRouteResponse + public partial class Game2C_GetFishsResponse : AMessage, ICustomRouteResponse, IProto { public static Game2C_GetFishsResponse Create(Scene scene) { @@ -253,7 +252,7 @@ namespace Fantasy /// 鱼护变化 /// [ProtoContract] - public partial class Game2C_FishChange : AMessage, ICustomRouteMessage + public partial class Game2C_FishChange : AMessage, ICustomRouteMessage, IProto { public static Game2C_FishChange Create(Scene scene) { @@ -282,7 +281,7 @@ namespace Fantasy /// 请求出售 /// [ProtoContract] - public partial class C2Game_SellFishRequest : AMessage, ICustomRouteRequest + public partial class C2Game_SellFishRequest : AMessage, ICustomRouteRequest, IProto { public static C2Game_SellFishRequest Create(Scene scene) { @@ -307,7 +306,7 @@ namespace Fantasy /// 请求出售响应 /// [ProtoContract] - public partial class Game2C_SellFishResponse : AMessage, ICustomRouteResponse + public partial class Game2C_SellFishResponse : AMessage, ICustomRouteResponse, IProto { public static Game2C_SellFishResponse Create(Scene scene) { @@ -334,7 +333,7 @@ namespace Fantasy /// 请求购买 /// [ProtoContract] - public partial class C2Game_BuyRequest : AMessage, ICustomRouteRequest + public partial class C2Game_BuyRequest : AMessage, ICustomRouteRequest, IProto { public static C2Game_BuyRequest Create(Scene scene) { @@ -356,7 +355,7 @@ namespace Fantasy /// 请求购买响应 /// [ProtoContract] - public partial class Game2C_BuyResponse : AMessage, ICustomRouteResponse + public partial class Game2C_BuyResponse : AMessage, ICustomRouteResponse, IProto { public static Game2C_BuyResponse Create(Scene scene) { @@ -377,4 +376,3 @@ namespace Fantasy public uint ErrorCode { get; set; } } } - diff --git a/Entity/Generate/NetworkProtocol/InnerMessage.cs b/Entity/Generate/NetworkProtocol/InnerMessage.cs index 60806ce..34819f3 100644 --- a/Entity/Generate/NetworkProtocol/InnerMessage.cs +++ b/Entity/Generate/NetworkProtocol/InnerMessage.cs @@ -1,6 +1,5 @@ using ProtoBuf; -using System; using System.Collections.Generic; using MongoDB.Bson.Serialization.Attributes; using Fantasy; @@ -17,12 +16,12 @@ using Fantasy.Serialize; #pragma warning disable CS8618 namespace Fantasy -{ +{ /// /// 通知游戏服角色进入该游戏服 /// [ProtoContract] - public partial class G2Common_EnterRequest : AMessage, IRouteRequest + public partial class G2Common_EnterRequest : AMessage, IRouteRequest, IProto { public static G2Common_EnterRequest Create(Scene scene) { @@ -48,7 +47,7 @@ namespace Fantasy public int RouteType { get; set; } } [ProtoContract] - public partial class G2Common_EnterResponse : AMessage, IRouteResponse + public partial class G2Common_EnterResponse : AMessage, IRouteResponse, IProto { public static G2Common_EnterResponse Create(Scene scene) { @@ -72,7 +71,7 @@ namespace Fantasy public uint ErrorCode { get; set; } } [ProtoContract] - public partial class G2Common_ExitRequest : AMessage, IRouteRequest + public partial class G2Common_ExitRequest : AMessage, IRouteRequest, IProto { public static G2Common_ExitRequest Create(Scene scene) { @@ -95,7 +94,7 @@ namespace Fantasy public long GateRouteId { get; set; } } [ProtoContract] - public partial class Common2G_ExitResponse : AMessage, IRouteResponse + public partial class Common2G_ExitResponse : AMessage, IRouteResponse, IProto { public static Common2G_ExitResponse Create(Scene scene) { @@ -116,7 +115,7 @@ namespace Fantasy /// 获取玩家基础信息 /// [ProtoContract] - public partial class S2G_GetPlayerBasicInfoRequest : AMessage, IRouteRequest + public partial class S2G_GetPlayerBasicInfoRequest : AMessage, IRouteRequest, IProto { public static S2G_GetPlayerBasicInfoRequest Create(Scene scene) { @@ -139,7 +138,7 @@ namespace Fantasy /// 获取玩家基础信息响应 /// [ProtoContract] - public partial class G2S_GetPlayerBasicInfoResponse : AMessage, IRouteResponse + public partial class G2S_GetPlayerBasicInfoResponse : AMessage, IRouteResponse, IProto { public static G2S_GetPlayerBasicInfoResponse Create(Scene scene) { @@ -160,7 +159,7 @@ namespace Fantasy public uint ErrorCode { get; set; } } [ProtoContract] - public partial class S2G_ChatMessage : AMessage, IRouteMessage + public partial class S2G_ChatMessage : AMessage, IRouteMessage, IProto { public static S2G_ChatMessage Create(Scene scene) { @@ -184,7 +183,7 @@ namespace Fantasy /// 创建聊天频道 /// [ProtoContract] - public partial class Club2Chat_CreateChannel : AMessage, IRouteMessage + public partial class Club2Chat_CreateChannel : AMessage, IRouteMessage, IProto { public static Club2Chat_CreateChannel Create(Scene scene) { @@ -205,7 +204,7 @@ namespace Fantasy /// 请求进入房间 /// [ProtoContract] - public partial class G2Map_EnterMapRequest : AMessage, IRouteRequest + public partial class G2Map_EnterMapRequest : AMessage, IRouteRequest, IProto { public static G2Map_EnterMapRequest Create(Scene scene) { @@ -234,7 +233,7 @@ namespace Fantasy /// 请求进入房间响应 /// [ProtoContract] - public partial class Map2G_EnterMapResponse : AMessage, IRouteResponse + public partial class Map2G_EnterMapResponse : AMessage, IRouteResponse, IProto { public static Map2G_EnterMapResponse Create(Scene scene) { @@ -264,7 +263,7 @@ namespace Fantasy /// 请求离开房间 /// [ProtoContract] - public partial class G2Map_ExitRoomRequest : AMessage, IRouteRequest + public partial class G2Map_ExitRoomRequest : AMessage, IRouteRequest, IProto { public static G2Map_ExitRoomRequest Create(Scene scene) { @@ -290,7 +289,7 @@ namespace Fantasy /// 请求离开房间响应 /// [ProtoContract] - public partial class Map2G_ExiRoomResponse : AMessage, IRouteResponse + public partial class Map2G_ExiRoomResponse : AMessage, IRouteResponse, IProto { public static Map2G_ExiRoomResponse Create(Scene scene) { @@ -308,4 +307,3 @@ namespace Fantasy public uint ErrorCode { get; set; } } } - diff --git a/Entity/Generate/NetworkProtocol/MapMessage.cs b/Entity/Generate/NetworkProtocol/MapMessage.cs index f36fb38..2a02cc7 100644 --- a/Entity/Generate/NetworkProtocol/MapMessage.cs +++ b/Entity/Generate/NetworkProtocol/MapMessage.cs @@ -1,6 +1,5 @@ using ProtoBuf; -using System; using System.Collections.Generic; using MongoDB.Bson.Serialization.Attributes; using Fantasy; @@ -17,12 +16,12 @@ using Fantasy.Serialize; #pragma warning disable CS8618 namespace Fantasy -{ +{ /// /// 请求创建房间 /// [ProtoContract] - public partial class C2Map_CreateRoomRequest : AMessage, ICustomRouteRequest + public partial class C2Map_CreateRoomRequest : AMessage, ICustomRouteRequest, IProto { public static C2Map_CreateRoomRequest Create(Scene scene) { @@ -47,7 +46,7 @@ namespace Fantasy /// 请求创建房间成功 /// [ProtoContract] - public partial class Map2C_CreateRoomResponse : AMessage, ICustomRouteResponse + public partial class Map2C_CreateRoomResponse : AMessage, ICustomRouteResponse, IProto { public static Map2C_CreateRoomResponse Create(Scene scene) { @@ -74,7 +73,7 @@ namespace Fantasy /// 请求网关离开房间(离开房间,但是不离开地图) /// [ProtoContract] - public partial class C2G_ExitRoomRequest : AMessage, IRequest + public partial class C2G_ExitRoomRequest : AMessage, IRequest, IProto { public static C2G_ExitRoomRequest Create(Scene scene) { @@ -97,7 +96,7 @@ namespace Fantasy /// 请求网关进入离开响应 /// [ProtoContract] - public partial class G2C_ExitRoomResponse : AMessage, IResponse + public partial class G2C_ExitRoomResponse : AMessage, IResponse, IProto { public static G2C_ExitRoomResponse Create(Scene scene) { @@ -121,7 +120,7 @@ namespace Fantasy /// 请求网关进入地图 /// [ProtoContract] - public partial class C2G_EnterMapRequest : AMessage, IRequest + public partial class C2G_EnterMapRequest : AMessage, IRequest, IProto { public static C2G_EnterMapRequest Create(Scene scene) { @@ -147,7 +146,7 @@ namespace Fantasy /// 请求网关进入房间响应 /// [ProtoContract] - public partial class G2C_EnterMapResponse : AMessage, IResponse + public partial class G2C_EnterMapResponse : AMessage, IResponse, IProto { public static G2C_EnterMapResponse Create(Scene scene) { @@ -177,7 +176,7 @@ namespace Fantasy /// 通知客户端切换地图 /// [ProtoContract] - public partial class Map2C_ChangeMap : AMessage, ICustomRouteMessage + public partial class Map2C_ChangeMap : AMessage, ICustomRouteMessage, IProto { public static Map2C_ChangeMap Create(Scene scene) { @@ -200,4 +199,3 @@ namespace Fantasy public int Node { get; set; } } } - diff --git a/Entity/Generate/NetworkProtocol/MapProtoData.cs b/Entity/Generate/NetworkProtocol/MapProtoData.cs index 2578965..23552c9 100644 --- a/Entity/Generate/NetworkProtocol/MapProtoData.cs +++ b/Entity/Generate/NetworkProtocol/MapProtoData.cs @@ -1,6 +1,5 @@ using ProtoBuf; -using System; using System.Collections.Generic; using MongoDB.Bson.Serialization.Attributes; using Fantasy; @@ -17,9 +16,9 @@ using Fantasy.Serialize; #pragma warning disable CS8618 namespace Fantasy -{ +{ [ProtoContract] - public partial class Vector3Info : AMessage + public partial class Vector3Info : AMessage, IProto { public static Vector3Info Create(Scene scene) { @@ -42,7 +41,7 @@ namespace Fantasy public float z { get; set; } } [ProtoContract] - public partial class Vector2Info : AMessage + public partial class Vector2Info : AMessage, IProto { public static Vector2Info Create(Scene scene) { @@ -62,7 +61,7 @@ namespace Fantasy public float y { get; set; } } [ProtoContract] - public partial class QuaternionInfo : AMessage + public partial class QuaternionInfo : AMessage, IProto { public static QuaternionInfo Create(Scene scene) { @@ -91,7 +90,7 @@ namespace Fantasy /// 玩家当前使用钓组信息 /// [ProtoContract] - public partial class GearInfo : AMessage + public partial class GearInfo : AMessage, IProto { public static GearInfo Create(Scene scene) { @@ -120,7 +119,7 @@ namespace Fantasy public List Propertys = new List(); } [ProtoContract] - public partial class UnitStateInfo : AMessage + public partial class UnitStateInfo : AMessage, IProto { public static UnitStateInfo Create(Scene scene) { @@ -140,7 +139,7 @@ namespace Fantasy public List Propertys = new List(); } [ProtoContract] - public partial class MapUnitInfo : AMessage + public partial class MapUnitInfo : AMessage, IProto { public static MapUnitInfo Create(Scene scene) { @@ -175,4 +174,3 @@ namespace Fantasy public List Propertys = new List(); } } - diff --git a/Entity/Generate/NetworkProtocol/OuterMessage.cs b/Entity/Generate/NetworkProtocol/OuterMessage.cs index a90f841..4af2453 100644 --- a/Entity/Generate/NetworkProtocol/OuterMessage.cs +++ b/Entity/Generate/NetworkProtocol/OuterMessage.cs @@ -1,6 +1,5 @@ using ProtoBuf; -using System; using System.Collections.Generic; using MongoDB.Bson.Serialization.Attributes; using Fantasy; @@ -17,9 +16,9 @@ using Fantasy.Serialize; #pragma warning disable CS8618 namespace Fantasy -{ +{ [ProtoContract] - public partial class C2A_LoginRequest : AMessage, IRequest + public partial class C2A_LoginRequest : AMessage, IRequest, IProto { public static C2A_LoginRequest Create(Scene scene) { @@ -48,7 +47,7 @@ namespace Fantasy public int Region { get; set; } } [ProtoContract] - public partial class A2C_LoginResponse : AMessage, IResponse + public partial class A2C_LoginResponse : AMessage, IResponse, IProto { public static A2C_LoginResponse Create(Scene scene) { @@ -72,7 +71,7 @@ namespace Fantasy /// 客户端登录到Gate服务器 /// [ProtoContract] - public partial class C2G_LoginRequest : AMessage, IRequest + public partial class C2G_LoginRequest : AMessage, IRequest, IProto { public static C2G_LoginRequest Create(Scene scene) { @@ -92,7 +91,7 @@ namespace Fantasy public string ToKen { get; set; } } [ProtoContract] - public partial class G2C_LoginResponse : AMessage, IResponse + public partial class G2C_LoginResponse : AMessage, IResponse, IProto { public static G2C_LoginResponse Create(Scene scene) { @@ -116,7 +115,7 @@ namespace Fantasy /// 通知客户端重复登录 /// [ProtoContract] - public partial class G2C_RepeatLogin : AMessage, IMessage + public partial class G2C_RepeatLogin : AMessage, IMessage, IProto { public static G2C_RepeatLogin Create(Scene scene) { @@ -131,7 +130,7 @@ namespace Fantasy public uint OpCode() { return OuterOpcode.G2C_RepeatLogin; } } [ProtoContract] - public partial class C2Game_GetRoleInfoRequest : AMessage, ICustomRouteRequest + public partial class C2Game_GetRoleInfoRequest : AMessage, ICustomRouteRequest, IProto { public static C2Game_GetRoleInfoRequest Create(Scene scene) { @@ -150,7 +149,7 @@ namespace Fantasy public int RouteType => Fantasy.RouteType.GameRoute; } [ProtoContract] - public partial class Game2C_GetRoleInfoResponse : AMessage, ICustomRouteResponse + public partial class Game2C_GetRoleInfoResponse : AMessage, ICustomRouteResponse, IProto { public static Game2C_GetRoleInfoResponse Create(Scene scene) { @@ -174,4 +173,3 @@ namespace Fantasy public uint ErrorCode { get; set; } } } - diff --git a/Entity/Generate/NetworkProtocol/RoomMessage.cs b/Entity/Generate/NetworkProtocol/RoomMessage.cs index 41c37e0..8bb37e2 100644 --- a/Entity/Generate/NetworkProtocol/RoomMessage.cs +++ b/Entity/Generate/NetworkProtocol/RoomMessage.cs @@ -1,6 +1,5 @@ using ProtoBuf; -using System; using System.Collections.Generic; using MongoDB.Bson.Serialization.Attributes; using Fantasy; @@ -17,12 +16,12 @@ using Fantasy.Serialize; #pragma warning disable CS8618 namespace Fantasy -{ +{ /// /// 用户进入地图 /// [ProtoContract] - public partial class Map2C_RoleEnterRoomNotify : AMessage, ICustomRouteMessage + public partial class Map2C_RoleEnterRoomNotify : AMessage, ICustomRouteMessage, IProto { public static Map2C_RoleEnterRoomNotify Create(Scene scene) { @@ -45,7 +44,7 @@ namespace Fantasy /// 用户离开地图 /// [ProtoContract] - public partial class Map2C_RoleExitRoomNotify : AMessage, ICustomRouteMessage + public partial class Map2C_RoleExitRoomNotify : AMessage, ICustomRouteMessage, IProto { public static Map2C_RoleExitRoomNotify Create(Scene scene) { @@ -65,7 +64,7 @@ namespace Fantasy public long Id { get; set; } } [ProtoContract] - public partial class C2Map_RolePropertyChange : AMessage, ICustomRouteMessage + public partial class C2Map_RolePropertyChange : AMessage, ICustomRouteMessage, IProto { public static C2Map_RolePropertyChange Create(Scene scene) { @@ -88,7 +87,7 @@ namespace Fantasy /// 玩家状态变化同步 /// [ProtoContract] - public partial class Map2C_RoleStateNotify : AMessage, ICustomRouteMessage + public partial class Map2C_RoleStateNotify : AMessage, ICustomRouteMessage, IProto { public static Map2C_RoleStateNotify Create(Scene scene) { @@ -114,7 +113,7 @@ namespace Fantasy /// 玩家钓组变化 /// [ProtoContract] - public partial class Map2C_RoleGearChangeNotify : AMessage, ICustomRouteMessage + public partial class Map2C_RoleGearChangeNotify : AMessage, ICustomRouteMessage, IProto { public static Map2C_RoleGearChangeNotify Create(Scene scene) { @@ -137,7 +136,7 @@ namespace Fantasy public List Gears = new List(); } [ProtoContract] - public partial class Map2C_RolePropertyChangeNotify : AMessage, ICustomRouteMessage + public partial class Map2C_RolePropertyChangeNotify : AMessage, ICustomRouteMessage, IProto { public static Map2C_RolePropertyChangeNotify Create(Scene scene) { @@ -160,7 +159,7 @@ namespace Fantasy public List Propertys = new List(); } [ProtoContract] - public partial class C2Map_Move : AMessage, ICustomRouteMessage + public partial class C2Map_Move : AMessage, ICustomRouteMessage, IProto { public static C2Map_Move Create(Scene scene) { @@ -195,7 +194,7 @@ namespace Fantasy public long Timestamp { get; set; } } [ProtoContract] - public partial class C2Map_Look : AMessage, ICustomRouteMessage + public partial class C2Map_Look : AMessage, ICustomRouteMessage, IProto { public static C2Map_Look Create(Scene scene) { @@ -221,7 +220,7 @@ namespace Fantasy /// 玩家移动推送 /// [ProtoContract] - public partial class Map2C_MoveNotify : AMessage, ICustomRouteMessage + public partial class Map2C_MoveNotify : AMessage, ICustomRouteMessage, IProto { public static Map2C_MoveNotify Create(Scene scene) { @@ -262,7 +261,7 @@ namespace Fantasy /// 玩家旋转推送 /// [ProtoContract] - public partial class Map2C_LookeNotify : AMessage, ICustomRouteMessage + public partial class Map2C_LookeNotify : AMessage, ICustomRouteMessage, IProto { public static Map2C_LookeNotify Create(Scene scene) { @@ -288,4 +287,3 @@ namespace Fantasy public long Timestamp { get; set; } } } - diff --git a/Entity/Generate/NetworkProtocol/SocialMessage.cs b/Entity/Generate/NetworkProtocol/SocialMessage.cs index cf1ab9b..5a802a5 100644 --- a/Entity/Generate/NetworkProtocol/SocialMessage.cs +++ b/Entity/Generate/NetworkProtocol/SocialMessage.cs @@ -1,6 +1,5 @@ using ProtoBuf; -using System; using System.Collections.Generic; using MongoDB.Bson.Serialization.Attributes; using Fantasy; @@ -17,7 +16,7 @@ using Fantasy.Serialize; #pragma warning disable CS8618 namespace Fantasy -{ +{ /// /// /////////// ******** 私聊/邮件 *******///////////// /// @@ -25,7 +24,7 @@ namespace Fantasy /// 会话信息 /// [ProtoContract] - public partial class ConversationInfo : AMessage + public partial class ConversationInfo : AMessage, IProto { public static ConversationInfo Create(Scene scene) { @@ -45,7 +44,7 @@ namespace Fantasy public List List = new List(); } [ProtoContract] - public partial class MailInfo : AMessage + public partial class MailInfo : AMessage, IProto { public static MailInfo Create(Scene scene) { @@ -83,7 +82,7 @@ namespace Fantasy /// 请求会话列表 /// [ProtoContract] - public partial class C2S_GetConversationsRequest : AMessage, ICustomRouteRequest + public partial class C2S_GetConversationsRequest : AMessage, ICustomRouteRequest, IProto { public static C2S_GetConversationsRequest Create(Scene scene) { @@ -105,7 +104,7 @@ namespace Fantasy /// 请求会话列表响应 /// [ProtoContract] - public partial class S2C_GetConversationsResponse : AMessage, ICustomRouteResponse + public partial class S2C_GetConversationsResponse : AMessage, ICustomRouteResponse, IProto { public static S2C_GetConversationsResponse Create(Scene scene) { @@ -129,7 +128,7 @@ namespace Fantasy /// 发送邮件消息 /// [ProtoContract] - public partial class C2S_SendMailRequest : AMessage, ICustomRouteRequest + public partial class C2S_SendMailRequest : AMessage, ICustomRouteRequest, IProto { public static C2S_SendMailRequest Create(Scene scene) { @@ -160,7 +159,7 @@ namespace Fantasy /// 发送邮件消息响应 /// [ProtoContract] - public partial class S2C_SendMailResponse : AMessage, ICustomRouteResponse + public partial class S2C_SendMailResponse : AMessage, ICustomRouteResponse, IProto { public static S2C_SendMailResponse Create(Scene scene) { @@ -181,7 +180,7 @@ namespace Fantasy /// 发送删除会话消息 /// [ProtoContract] - public partial class C2S_DeleteMailRequest : AMessage, ICustomRouteRequest + public partial class C2S_DeleteMailRequest : AMessage, ICustomRouteRequest, IProto { public static C2S_DeleteMailRequest Create(Scene scene) { @@ -206,7 +205,7 @@ namespace Fantasy /// 发送删除会话消息响应 /// [ProtoContract] - public partial class S2C_DeleteMailResponse : AMessage, ICustomRouteResponse + public partial class S2C_DeleteMailResponse : AMessage, ICustomRouteResponse, IProto { public static S2C_DeleteMailResponse Create(Scene scene) { @@ -230,7 +229,7 @@ namespace Fantasy /// 新邮件推送 /// [ProtoContract] - public partial class S2C_HaveMail : AMessage, ICustomRouteMessage + public partial class S2C_HaveMail : AMessage, ICustomRouteMessage, IProto { public static S2C_HaveMail Create(Scene scene) { @@ -253,7 +252,7 @@ namespace Fantasy public string Key { get; set; } } [ProtoContract] - public partial class S2C_MailState : AMessage, ICustomRouteMessage + public partial class S2C_MailState : AMessage, ICustomRouteMessage, IProto { public static S2C_MailState Create(Scene scene) { @@ -279,7 +278,7 @@ namespace Fantasy /// /////////// ******** 频道聊天 *******///////////// /// [ProtoContract] - public partial class ChatUserInfo : AMessage + public partial class ChatUserInfo : AMessage, IProto { public static ChatUserInfo Create(Scene scene) { @@ -299,7 +298,7 @@ namespace Fantasy public long Name { get; set; } } [ProtoContract] - public partial class ChatMessageInfo : AMessage + public partial class ChatMessageInfo : AMessage, IProto { public static ChatMessageInfo Create(Scene scene) { @@ -331,7 +330,7 @@ namespace Fantasy /// 创建频道 /// [ProtoContract] - public partial class C2S_CreateChannelRequest : AMessage, ICustomRouteRequest + public partial class C2S_CreateChannelRequest : AMessage, ICustomRouteRequest, IProto { public static C2S_CreateChannelRequest Create(Scene scene) { @@ -356,7 +355,7 @@ namespace Fantasy /// 创建频道响应 /// [ProtoContract] - public partial class S2C_CreateChannelResponse : AMessage, ICustomRouteResponse + public partial class S2C_CreateChannelResponse : AMessage, ICustomRouteResponse, IProto { public static S2C_CreateChannelResponse Create(Scene scene) { @@ -380,7 +379,7 @@ namespace Fantasy /// 请求进入频道 /// [ProtoContract] - public partial class C2S_JoinChannelRequest : AMessage, ICustomRouteRequest + public partial class C2S_JoinChannelRequest : AMessage, ICustomRouteRequest, IProto { public static C2S_JoinChannelRequest Create(Scene scene) { @@ -405,7 +404,7 @@ namespace Fantasy /// 进入频道响应 /// [ProtoContract] - public partial class S2C_JoinChannelResponse : AMessage, ICustomRouteResponse + public partial class S2C_JoinChannelResponse : AMessage, ICustomRouteResponse, IProto { public static S2C_JoinChannelResponse Create(Scene scene) { @@ -426,7 +425,7 @@ namespace Fantasy /// 发送消息 /// [ProtoContract] - public partial class C2S_SendMessageRequest : AMessage, ICustomRouteRequest + public partial class C2S_SendMessageRequest : AMessage, ICustomRouteRequest, IProto { public static C2S_SendMessageRequest Create(Scene scene) { @@ -454,7 +453,7 @@ namespace Fantasy /// 发送消息响应 /// [ProtoContract] - public partial class S2C_SendMessageResponse : AMessage, ICustomRouteResponse + public partial class S2C_SendMessageResponse : AMessage, ICustomRouteResponse, IProto { public static S2C_SendMessageResponse Create(Scene scene) { @@ -475,7 +474,7 @@ namespace Fantasy /// 推送消息 /// [ProtoContract] - public partial class S2C_Message : AMessage, ICustomRouteMessage + public partial class S2C_Message : AMessage, ICustomRouteMessage, IProto { public static S2C_Message Create(Scene scene) { @@ -498,7 +497,7 @@ namespace Fantasy /// /////////// ******** 工会 *******///////////// /// [ProtoContract] - public partial class ClubInfo : AMessage + public partial class ClubInfo : AMessage, IProto { public static ClubInfo Create(Scene scene) { @@ -530,7 +529,7 @@ namespace Fantasy /// 请求创建工会 /// [ProtoContract] - public partial class C2S_CreateClubRequest : AMessage, ICustomRouteRequest + public partial class C2S_CreateClubRequest : AMessage, ICustomRouteRequest, IProto { public static C2S_CreateClubRequest Create(Scene scene) { @@ -555,7 +554,7 @@ namespace Fantasy /// 创建工会响应 /// [ProtoContract] - public partial class S2C_CreateClubResponse : AMessage, ICustomRouteResponse + public partial class S2C_CreateClubResponse : AMessage, ICustomRouteResponse, IProto { public static S2C_CreateClubResponse Create(Scene scene) { @@ -579,7 +578,7 @@ namespace Fantasy /// 请求工会信息 /// [ProtoContract] - public partial class C2S_GetClubInfoRequest : AMessage, ICustomRouteRequest + public partial class C2S_GetClubInfoRequest : AMessage, ICustomRouteRequest, IProto { public static C2S_GetClubInfoRequest Create(Scene scene) { @@ -604,7 +603,7 @@ namespace Fantasy /// 响应工会信息 /// [ProtoContract] - public partial class S2C_GetClubInfoResponse : AMessage, ICustomRouteResponse + public partial class S2C_GetClubInfoResponse : AMessage, ICustomRouteResponse, IProto { public static S2C_GetClubInfoResponse Create(Scene scene) { @@ -628,7 +627,7 @@ namespace Fantasy /// 请求工会成员列表 /// [ProtoContract] - public partial class C2S_GetMemberListRequest : AMessage, ICustomRouteRequest + public partial class C2S_GetMemberListRequest : AMessage, ICustomRouteRequest, IProto { public static C2S_GetMemberListRequest Create(Scene scene) { @@ -653,7 +652,7 @@ namespace Fantasy /// 响应工会成员列表 /// [ProtoContract] - public partial class S2C_GetMemberListResponse : AMessage, ICustomRouteResponse + public partial class S2C_GetMemberListResponse : AMessage, ICustomRouteResponse, IProto { public static S2C_GetMemberListResponse Create(Scene scene) { @@ -677,7 +676,7 @@ namespace Fantasy /// 获取工会列表请求 /// [ProtoContract] - public partial class C2S_GetClubListRequest : AMessage, ICustomRouteRequest + public partial class C2S_GetClubListRequest : AMessage, ICustomRouteRequest, IProto { public static C2S_GetClubListRequest Create(Scene scene) { @@ -699,7 +698,7 @@ namespace Fantasy /// 获取工会列表响应 /// [ProtoContract] - public partial class S2C_GetClubListResponse : AMessage, ICustomRouteResponse + public partial class S2C_GetClubListResponse : AMessage, ICustomRouteResponse, IProto { public static S2C_GetClubListResponse Create(Scene scene) { @@ -723,7 +722,7 @@ namespace Fantasy /// 请求加入工会 /// [ProtoContract] - public partial class C2S_JoinClubRequest : AMessage, ICustomRouteRequest + public partial class C2S_JoinClubRequest : AMessage, ICustomRouteRequest, IProto { public static C2S_JoinClubRequest Create(Scene scene) { @@ -748,7 +747,7 @@ namespace Fantasy /// 响应加入工会 /// [ProtoContract] - public partial class S2C_JoinClubResponse : AMessage, ICustomRouteResponse + public partial class S2C_JoinClubResponse : AMessage, ICustomRouteResponse, IProto { public static S2C_JoinClubResponse Create(Scene scene) { @@ -772,7 +771,7 @@ namespace Fantasy /// 请求退出工会 /// [ProtoContract] - public partial class C2S_LeaveClubRequest : AMessage, ICustomRouteRequest + public partial class C2S_LeaveClubRequest : AMessage, ICustomRouteRequest, IProto { public static C2S_LeaveClubRequest Create(Scene scene) { @@ -797,7 +796,7 @@ namespace Fantasy /// 响应退出工会 /// [ProtoContract] - public partial class S2C_LeaveClubResponse : AMessage, ICustomRouteResponse + public partial class S2C_LeaveClubResponse : AMessage, ICustomRouteResponse, IProto { public static S2C_LeaveClubResponse Create(Scene scene) { @@ -821,7 +820,7 @@ namespace Fantasy /// 请求解散工会 /// [ProtoContract] - public partial class C2S_DissolveClubRequest : AMessage, ICustomRouteRequest + public partial class C2S_DissolveClubRequest : AMessage, ICustomRouteRequest, IProto { public static C2S_DissolveClubRequest Create(Scene scene) { @@ -846,7 +845,7 @@ namespace Fantasy /// 响应解散工会 /// [ProtoContract] - public partial class S2C_DissolveClubResponse : AMessage, ICustomRouteResponse + public partial class S2C_DissolveClubResponse : AMessage, ICustomRouteResponse, IProto { public static S2C_DissolveClubResponse Create(Scene scene) { @@ -870,7 +869,7 @@ namespace Fantasy /// 请求操作申请 /// [ProtoContract] - public partial class C2S_DisposeJoinRequest : AMessage, ICustomRouteRequest + public partial class C2S_DisposeJoinRequest : AMessage, ICustomRouteRequest, IProto { public static C2S_DisposeJoinRequest Create(Scene scene) { @@ -901,7 +900,7 @@ namespace Fantasy /// 响应操作申请 /// [ProtoContract] - public partial class S2C_DisposeJoinResponse : AMessage, ICustomRouteResponse + public partial class S2C_DisposeJoinResponse : AMessage, ICustomRouteResponse, IProto { public static S2C_DisposeJoinResponse Create(Scene scene) { @@ -931,7 +930,7 @@ namespace Fantasy /// 推送消息 /// [ProtoContract] - public partial class S2C_ClubChange : AMessage, ICustomRouteMessage + public partial class S2C_ClubChange : AMessage, ICustomRouteMessage, IProto { public static S2C_ClubChange Create(Scene scene) { @@ -954,4 +953,3 @@ namespace Fantasy public int ChangeType { get; set; } } } - diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Fantasy-Net.targets b/Fantasy/Fantasy.Net/Fantasy.Net/Fantasy-Net.targets index 2694462..5426ed6 100644 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Fantasy-Net.targets +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Fantasy-Net.targets @@ -1,7 +1,34 @@ - - - - $(DefineConstants);FANTASY_NET - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Fantasy.Net.csproj b/Fantasy/Fantasy.Net/Fantasy.Net/Fantasy.Net.csproj index c08e328..be19e19 100644 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Fantasy.Net.csproj +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Fantasy.Net.csproj @@ -30,9 +30,6 @@ true true bin\Debug\net8.0\Fantasy.Net.xml - - true - $(BaseIntermediateOutputPath)\GeneratedFiles @@ -60,21 +57,4 @@ - - - - - - - - - - - diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Assembly/AssemblyInfo.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Assembly/AssemblyInfo.cs new file mode 100644 index 0000000..3957539 --- /dev/null +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Assembly/AssemblyInfo.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Fantasy.DataStructure.Collection; + +// ReSharper disable CollectionNeverQueried.Global +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. + +namespace Fantasy.Assembly +{ + /// + /// AssemblyInfo提供有关程序集和类型的信息 + /// + public sealed class AssemblyInfo + { + /// + /// 唯一标识 + /// + public readonly long AssemblyIdentity; + /// + /// 获取或设置与此程序集相关联的 实例。 + /// + public System.Reflection.Assembly Assembly { get; private set; } + /// + /// 程序集类型集合,获取一个列表,包含从程序集加载的所有类型。 + /// + public readonly List AssemblyTypeList = new List(); + /// + /// 程序集类型分组集合,获取一个分组列表,将接口类型映射到实现这些接口的类型。 + /// + public readonly OneToManyList AssemblyTypeGroupList = new OneToManyList(); + + /// + /// 初始化 类的新实例。 + /// + /// + public AssemblyInfo(long assemblyIdentity) + { + AssemblyIdentity = assemblyIdentity; + } + + /// + /// 从指定的程序集加载类型信息并进行分类。 + /// + /// 要加载信息的程序集。 + public void Load(System.Reflection.Assembly assembly) + { + Assembly = assembly; + var assemblyTypes = assembly.GetTypes().ToList(); + + foreach (var type in assemblyTypes) + { + if (type.IsAbstract || type.IsInterface) + { + continue; + } + + var interfaces = type.GetInterfaces(); + + foreach (var interfaceType in interfaces) + { + AssemblyTypeGroupList.Add(interfaceType, type); + } + } + + AssemblyTypeList.AddRange(assemblyTypes); + } + + /// + /// 重新加载程序集的类型信息。 + /// + /// + public void ReLoad(System.Reflection.Assembly assembly) + { + Unload(); + Load(assembly); + } + + /// + /// 卸载程序集的类型信息。 + /// + public void Unload() + { + AssemblyTypeList.Clear(); + AssemblyTypeGroupList.Clear(); + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Assembly/AssemblyLifecycle.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Assembly/AssemblyLifecycle.cs deleted file mode 100644 index 6bd0ee3..0000000 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Assembly/AssemblyLifecycle.cs +++ /dev/null @@ -1,91 +0,0 @@ -using System.Collections.Concurrent; -using System.Collections.Generic; -using Fantasy.Async; - -namespace Fantasy.Assembly -{ - /// - /// 程序集生命周期管理类 - /// 管理所有注册的程序集生命周期回调,在程序集加载、卸载时触发相应的回调方法 - /// - public static class AssemblyLifecycle - { -#if FANTASY_WEBGL - /// - /// 程序集生命周期回调集合(WebGL 单线程版本) - /// - private static readonly Dictionary AssemblyLifecycles = new Dictionary(); -#else - /// - /// 程序集生命周期回调集合(线程安全版本) - /// 使用 ConcurrentDictionary 当作 Set 使用,Value 无实际意义 - /// - private static readonly ConcurrentDictionary AssemblyLifecycles = new ConcurrentDictionary(); -#endif - /// - /// 触发程序集加载事件 - /// 遍历所有已注册的生命周期回调,调用其 OnLoad 方法 - /// - /// 程序集清单对象 - /// 异步任务 - internal static async FTask OnLoad(AssemblyManifest assemblyManifest) - { - foreach (var (assemblyLifecycle, _) in AssemblyLifecycles) - { - await assemblyLifecycle.OnLoad(assemblyManifest); - } - } - - /// - /// 触发程序集卸载事件 - /// 遍历所有已注册的生命周期回调,调用其 OnUnload 方法,并清理程序集清单 - /// - /// 程序集清单对象 - /// 异步任务 - internal static async FTask OnUnLoad(AssemblyManifest assemblyManifest) - { - foreach (var (assemblyLifecycle, _) in AssemblyLifecycles) - { - await assemblyLifecycle.OnUnload(assemblyManifest); - } - assemblyManifest.Clear(); - } - - /// - /// 添加程序集生命周期回调 - /// 添加后会立即对所有已加载的程序集触发 Load 回调 - /// - /// 实现 IAssemblyLifecycle 接口的生命周期回调对象 - internal static async FTask Add(IAssemblyLifecycle assemblyLifecycle) - { -#if FANTASY_WEBGL - AssemblyLifecycles.Add(assemblyLifecycle, 0); -#else - AssemblyLifecycles.TryAdd(assemblyLifecycle, 0); -#endif - foreach (var (_, assemblyManifest) in AssemblyManifest.Manifests) - { - await assemblyLifecycle.OnLoad(assemblyManifest); - } - } - - /// - /// 移除程序集生命周期回调 - /// 移除后该回调将不再接收程序集的加载、卸载、重载事件 - /// - /// 要移除的生命周期回调对象 - internal static void Remove(IAssemblyLifecycle assemblyLifecycle) - { - AssemblyLifecycles.Remove(assemblyLifecycle, out _); - } - - /// - /// 释放所有程序集生命周期回调 - /// 清空所有已注册的生命周期回调集合 - /// - public static void Dispose() - { - AssemblyLifecycles.Clear(); - } - } -} \ No newline at end of file diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Assembly/AssemblyManifest.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Assembly/AssemblyManifest.cs deleted file mode 100644 index ef47124..0000000 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Assembly/AssemblyManifest.cs +++ /dev/null @@ -1,256 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using Fantasy.Async; -using Fantasy.DataStructure.Collection; -using Fantasy.Entitas; -// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract -#pragma warning disable CS8602 // Dereference of a possibly null reference. -#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. -#pragma warning disable CS8601 // Possible null reference assignment. -#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. -#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type. -#pragma warning disable CS8603 // Possible null reference return. - -namespace Fantasy.Assembly -{ - /// - /// 程序集清单类 - /// 封装程序集的元数据和各种系统注册器,用于统一管理程序集的生命周期和系统注册 - /// - public sealed class AssemblyManifest - { - /// - /// 程序集唯一标识符 - /// 通过程序集名称的哈希值生成 - /// - public long AssemblyManifestId { get; private set; } - - /// - /// 程序集实例 - /// - public System.Reflection.Assembly Assembly { get; private set; } - - /// - /// ProtoBuf 序列化类型注册器 - /// - internal INetworkProtocolRegistrar NetworkProtocolRegistrar { get; set; } - - /// - /// 事件系统注册器 - /// - internal IEventSystemRegistrar EventSystemRegistrar { get; set; } - - /// - /// 实体系统注册器 - /// - internal IEntitySystemRegistrar EntitySystemRegistrar { get; set; } - - /// - /// 消息分发器注册器 - /// - internal IMessageHandlerResolver MessageHandlerResolver { get; set; } - - /// - /// 实体类型集合注册器 - /// - internal IEntityTypeCollectionRegistrar EntityTypeCollectionRegistrar { get; set; } - - /// - /// 网络协议 OpCode 解析器接口 - /// - internal INetworkProtocolOpCodeResolver NetworkProtocolOpCodeResolver { get; set; } - - - /// - /// 网络协议 Response 解析器接口 - /// - internal INetworkProtocolResponseTypeResolver NetworkProtocolResponseTypeResolver { get; set; } - -#if FANTASY_NET - /// - /// 分表注册器 - /// - internal ISeparateTableRegistrar SeparateTableRegistrar { get; set; } -#endif -#if FANTASY_WEBGL - /// - /// 程序集清单集合(WebGL 单线程版本) - /// Key: 程序集唯一标识, Value: 程序集清单对象 - /// - private static readonly Dictionary Manifests = new Dictionary(); -#else - /// - /// 程序集清单集合(线程安全版本) - /// Key: 程序集唯一标识, Value: 程序集清单对象 - /// - internal static readonly ConcurrentDictionary Manifests = new ConcurrentDictionary(); -#endif - /// - /// 清理程序集清单内部资源 - /// 释放所有注册器并清空引用 - /// - internal void Clear() - { - EventSystemRegistrar?.Dispose(); - - Assembly = null; - NetworkProtocolRegistrar = null; - EventSystemRegistrar = null; - EntitySystemRegistrar = null; - MessageHandlerResolver = null; - EntityTypeCollectionRegistrar = null; -#if FANTASY_NET - SeparateTableRegistrar = null; -#endif - } - - #region static - -#if FANTASY_NET - /// - /// 注册程序集清单 - /// 此方法由 Source Generator 生成的 ModuleInitializer 自动调用 - /// 直接创建并缓存完整的 AssemblyManifest - /// - /// 程序集唯一标识(通过程序集名称哈希生成) - /// 程序集实例 - /// 网络协议注册器 - /// 事件系统注册器 - /// 实体系统注册器 - /// 消息分发器注册器 - /// 实体类型集合注册器 - /// 分表注册器 - /// 网络协议 OpCode 解析器接口 - /// 网络协议 Response 解析器接口 - public static void Register( - long assemblyManifestId, - System.Reflection.Assembly assembly, - INetworkProtocolRegistrar networkProtocolRegistrar, - IEventSystemRegistrar eventSystemRegistrar, - IEntitySystemRegistrar entitySystemRegistrar, - IMessageHandlerResolver messageHandlerResolver, - IEntityTypeCollectionRegistrar entityTypeCollectionRegistrar, - ISeparateTableRegistrar separateTableRegistrar, - INetworkProtocolOpCodeResolver networkProtocolOpCodeResolver, - INetworkProtocolResponseTypeResolver networkProtocolResponseTypeResolver) - { - var manifest = new AssemblyManifest - { - Assembly = assembly, - AssemblyManifestId = assemblyManifestId, - NetworkProtocolRegistrar = networkProtocolRegistrar, - EventSystemRegistrar = eventSystemRegistrar, - EntitySystemRegistrar = entitySystemRegistrar, - MessageHandlerResolver = messageHandlerResolver, - EntityTypeCollectionRegistrar = entityTypeCollectionRegistrar, - SeparateTableRegistrar = separateTableRegistrar, - NetworkProtocolOpCodeResolver = networkProtocolOpCodeResolver, - NetworkProtocolResponseTypeResolver = networkProtocolResponseTypeResolver - }; - - Manifests.TryAdd(assemblyManifestId, manifest); - AssemblyLifecycle.OnLoad(manifest).Coroutine(); - } -#endif -#if FANTASY_UNITY - /// - /// 注册程序集清单 - /// 此方法由 Source Generator 生成的 ModuleInitializer 自动调用 - /// 直接创建并缓存完整的 AssemblyManifest - /// - /// 程序集唯一标识(通过程序集名称哈希生成) - /// 程序集实例 - /// 网络协议注册器 - /// 事件系统注册器 - /// 实体系统注册器 - /// 消息分发器注册器 - /// 实体类型集合注册器 - /// 网络协议 OpCode 解析器接口 - /// 网络协议 Response 解析器接口 - public static void Register( - long assemblyManifestId, - System.Reflection.Assembly assembly, - INetworkProtocolRegistrar networkProtocolRegistrar, - IEventSystemRegistrar eventSystemRegistrar, - IEntitySystemRegistrar entitySystemRegistrar, - IMessageHandlerResolver messageHandlerResolver, - IEntityTypeCollectionRegistrar entityTypeCollectionRegistrar, - INetworkProtocolOpCodeResolver networkProtocolOpCodeResolver, - INetworkProtocolResponseTypeResolver networkProtocolResponseTypeResolver) - { - var manifest = new AssemblyManifest - { - Assembly = assembly, - AssemblyManifestId = assemblyManifestId, - NetworkProtocolRegistrar = networkProtocolRegistrar, - EventSystemRegistrar = eventSystemRegistrar, - EntitySystemRegistrar = entitySystemRegistrar, - MessageHandlerResolver = messageHandlerResolver, - EntityTypeCollectionRegistrar = entityTypeCollectionRegistrar, - NetworkProtocolOpCodeResolver = networkProtocolOpCodeResolver, - NetworkProtocolResponseTypeResolver = networkProtocolResponseTypeResolver - }; -#if FANTASY_WEBGL - Manifests[assemblyManifestId] = manifest; -#else - Manifests.TryAdd(assemblyManifestId, manifest); -#endif - AssemblyLifecycle.OnLoad(manifest).Coroutine(); - } -#endif - /// - /// 取消注册指定程序集的清单 - /// - /// 程序集唯一标识 - public static void Unregister(long assemblyManifestId) - { -#if FANTASY_WEBGL - if (Manifests.TryGetValue(assemblyManifestId, out var manifest)) - { - AssemblyLifecycle.OnUnLoad(manifest).Coroutine(); - Manifests.Remove(assemblyManifestId); - } -#else - if (Manifests.TryRemove(assemblyManifestId, out var manifest)) - { - AssemblyLifecycle.OnUnLoad(manifest).Coroutine(); - } -#endif - } - - /// - /// 获取当前框架注册的所有程序集清单 - /// 通过迭代器模式返回所有已注册的程序集清单对象 - /// - public static IEnumerable GetAssemblyManifest - { - get - { - foreach (var (_, assemblyManifest) in Manifests) - { - yield return assemblyManifest; - } - } - } - - /// - /// 释放所有程序集清单资源 - /// 卸载所有已注册的程序集,触发卸载事件,清理所有注册器和生命周期回调 - /// - /// 异步任务 - public static async FTask Dispose() - { - foreach (var (_, assemblyManifest) in Manifests) - { - await AssemblyLifecycle.OnUnLoad(assemblyManifest); - assemblyManifest.Clear(); - } - - Manifests.Clear(); - AssemblyLifecycle.Dispose(); - } - - #endregion - } -} \ No newline at end of file diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Assembly/AssemblySystem.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Assembly/AssemblySystem.cs new file mode 100644 index 0000000..deb9da6 --- /dev/null +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Assembly/AssemblySystem.cs @@ -0,0 +1,285 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Security.Cryptography; +using System.Text; +using Fantasy.Async; +using Fantasy.Helper; +#pragma warning disable CS8604 // Possible null reference argument. +#pragma warning disable CS8602 // Dereference of a possibly null reference. +#pragma warning disable CS8603 +#pragma warning disable CS8618 +namespace Fantasy.Assembly +{ + /// + /// 管理程序集加载和卸载的帮助类。 + /// + public static class AssemblySystem + { +#if FANTASY_WEBGL + private static readonly List AssemblySystems = new List(); + private static readonly Dictionary AssemblyList = new Dictionary(); +#else + private static readonly ConcurrentQueue AssemblySystems = new ConcurrentQueue(); + private static readonly ConcurrentDictionary AssemblyList = new ConcurrentDictionary(); +#endif + /// + /// 初始化 AssemblySystem。(仅限内部) + /// + /// + internal static async FTask InnerInitialize(params System.Reflection.Assembly[] assemblies) + { + await LoadAssembly(typeof(AssemblySystem).Assembly); + foreach (var assembly in assemblies) + { + await LoadAssembly(assembly); + } + } + + /// + /// 加载指定的程序集,并触发相应的事件。 + /// + /// 要加载的程序集。 + /// 如果当前Domain中已经存在同名的Assembly,使用Domain中的程序集。 + public static async FTask LoadAssembly(System.Reflection.Assembly assembly, bool isCurrentDomain = true) + { + if (isCurrentDomain) + { + var currentDomainAssemblies = System.AppDomain.CurrentDomain.GetAssemblies(); + var currentAssembly = currentDomainAssemblies.FirstOrDefault(d => d.GetName().Name == assembly.GetName().Name); + if (currentAssembly != null) + { + assembly = currentAssembly; + } + } + + var assemblyIdentity = AssemblyIdentity(assembly); + + if (AssemblyList.TryGetValue(assemblyIdentity, out var assemblyInfo)) + { + assemblyInfo.ReLoad(assembly); + foreach (var assemblySystem in AssemblySystems) + { + await assemblySystem.ReLoad(assemblyIdentity); + } + } + else + { + assemblyInfo = new AssemblyInfo(assemblyIdentity); + assemblyInfo.Load(assembly); + AssemblyList.TryAdd(assemblyIdentity, assemblyInfo); + foreach (var assemblySystem in AssemblySystems) + { + await assemblySystem.Load(assemblyIdentity); + } + } + } + + /// + /// 卸载程序集 + /// + /// + public static async FTask UnLoadAssembly(System.Reflection.Assembly assembly) + { + var assemblyIdentity = AssemblyIdentity(assembly); + + if (!AssemblyList.Remove(assemblyIdentity, out var assemblyInfo)) + { + return; + } + + assemblyInfo.Unload(); + foreach (var assemblySystem in AssemblySystems) + { + await assemblySystem.OnUnLoad(assemblyIdentity); + } + } + + /// + /// 将AssemblySystem接口的object注册到程序集管理中心 + /// + /// + public static async FTask Register(object obj) + { + if (obj is not IAssembly assemblySystem) + { + return; + } +#if FANTASY_WEBGL + AssemblySystems.Add(assemblySystem); +#else + AssemblySystems.Enqueue(assemblySystem); +#endif + foreach (var (assemblyIdentity, _) in AssemblyList) + { + await assemblySystem.Load(assemblyIdentity); + } + } + + /// + /// 程序集管理中心卸载注册的Load、ReLoad、UnLoad的接口 + /// + /// + public static void UnRegister(object obj) + { + if (obj is not IAssembly assemblySystem) + { + return; + } +#if FANTASY_WEBGL + AssemblySystems.Remove(assemblySystem); +#else + var count = AssemblySystems.Count; + + for (var i = 0; i < count; i++) + { + if (!AssemblySystems.TryDequeue(out var removeAssemblySystem)) + { + continue; + } + + if (removeAssemblySystem == assemblySystem) + { + break; + } + + AssemblySystems.Enqueue(removeAssemblySystem); + } +#endif + } + + /// + /// 获取所有已加载程序集中的所有类型。 + /// + /// 所有已加载程序集中的类型。 + public static IEnumerable ForEach() + { + foreach (var (_, assemblyInfo) in AssemblyList) + { + foreach (var type in assemblyInfo.AssemblyTypeList) + { + yield return type; + } + } + } + + /// + /// 获取指定程序集中的所有类型。 + /// + /// 程序集唯一标识。 + /// 指定程序集中的类型。 + public static IEnumerable ForEach(long assemblyIdentity) + { + if (!AssemblyList.TryGetValue(assemblyIdentity, out var assemblyInfo)) + { + yield break; + } + + foreach (var type in assemblyInfo.AssemblyTypeList) + { + yield return type; + } + } + + /// + /// 获取所有已加载程序集中实现指定类型的所有类型。 + /// + /// 要查找的基类或接口类型。 + /// 所有已加载程序集中实现指定类型的类型。 + public static IEnumerable ForEach(Type findType) + { + foreach (var (_, assemblyInfo) in AssemblyList) + { + if (!assemblyInfo.AssemblyTypeGroupList.TryGetValue(findType, out var assemblyLoad)) + { + continue; + } + + foreach (var type in assemblyLoad) + { + yield return type; + } + } + } + + /// + /// 获取指定程序集中实现指定类型的所有类型。 + /// + /// 程序集唯一标识。 + /// 要查找的基类或接口类型。 + /// 指定程序集中实现指定类型的类型。 + public static IEnumerable ForEach(long assemblyIdentity, Type findType) + { + if (!AssemblyList.TryGetValue(assemblyIdentity, out var assemblyInfo)) + { + yield break; + } + + if (!assemblyInfo.AssemblyTypeGroupList.TryGetValue(findType, out var assemblyLoad)) + { + yield break; + } + + foreach (var type in assemblyLoad) + { + yield return type; + } + } + + /// + /// 获取指定程序集的实例。 + /// + /// 程序集名称。 + /// 指定程序集的实例,如果未加载则返回 null。 + public static System.Reflection.Assembly GetAssembly(long assemblyIdentity) + { + return !AssemblyList.TryGetValue(assemblyIdentity, out var assemblyInfo) ? null : assemblyInfo.Assembly; + } + + /// + /// 获取当前框架注册的Assembly + /// + /// + public static IEnumerable ForEachAssembly + { + get + { + foreach (var (_, assemblyInfo) in AssemblyList) + { + yield return assemblyInfo.Assembly; + } + } + } + + /// + /// 根据Assembly的强命名计算唯一标识。 + /// + /// + /// + private static long AssemblyIdentity(System.Reflection.Assembly assembly) + { + return HashCodeHelper.ComputeHash64(assembly.GetName().Name); + } + + /// + /// 释放资源,卸载所有加载的程序集。 + /// + public static void Dispose() + { + DisposeAsync().Coroutine(); + } + + private static async FTask DisposeAsync() + { + foreach (var (_, assemblyInfo) in AssemblyList.ToArray()) + { + await UnLoadAssembly(assemblyInfo.Assembly); + } + + AssemblyList.Clear(); + AssemblySystems.Clear(); + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Assembly/IAssembly.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Assembly/IAssembly.cs new file mode 100644 index 0000000..185e037 --- /dev/null +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Assembly/IAssembly.cs @@ -0,0 +1,27 @@ +using System; +using Fantasy.Async; + +namespace Fantasy.Assembly +{ + /// + /// 实现这个接口、会再程序集首次加载、卸载、重载的时候调用 + /// + public interface IAssembly : IDisposable + { + /// + /// 程序集加载时调用 + /// + /// 程序集标识 + public FTask Load(long assemblyIdentity); + /// + /// 程序集重新加载的时候调用 + /// + /// 程序集标识 + public FTask ReLoad(long assemblyIdentity); + /// + /// 卸载的时候调用 + /// + /// 程序集标识 + public FTask OnUnLoad(long assemblyIdentity); + } +} \ No newline at end of file diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Assembly/Interface/IAssemblyLifecycle.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Assembly/Interface/IAssemblyLifecycle.cs deleted file mode 100644 index 57d7ad9..0000000 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Assembly/Interface/IAssemblyLifecycle.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using Fantasy.Async; - -namespace Fantasy.Assembly -{ - /// - /// 程序集生命周期回调接口 - /// 实现此接口的类型可以接收程序集的加载、卸载、重载事件通知 - /// 通过 AssemblySystem.Add() 注册后,在程序集状态变化时会自动调用对应的生命周期方法 - /// - internal interface IAssemblyLifecycle - { - /// - /// 程序集加载或重载时调用 - /// 当新的程序集被加载到框架中时触发此回调,重新加载已存在的程序集时也会调用 - /// - /// 程序集清单对象,包含程序集的元数据和注册器 - /// 异步任务 - FTask OnLoad(AssemblyManifest assemblyManifest); - - /// - /// 程序集卸载时调用 - /// 当程序集从框架中卸载时触发此回调,应在此方法中清理该程序集相关的资源 - /// - /// 程序集清单对象,包含程序集的元数据和注册器 - /// 异步任务 - FTask OnUnload(AssemblyManifest assemblyManifest); - } -} \ No newline at end of file diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Assembly/Interface/IEntitySystemRegistrar.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Assembly/Interface/IEntitySystemRegistrar.cs deleted file mode 100644 index 8c2f8c7..0000000 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Assembly/Interface/IEntitySystemRegistrar.cs +++ /dev/null @@ -1,73 +0,0 @@ -using System; -using System.Collections.Generic; -using Fantasy.Entitas; -using Fantasy.Entitas.Interface; - -namespace Fantasy.Assembly -{ - /// - /// 实体系统注册器接口 - /// 由 Source Generator 自动生成实现类,用于在程序集加载时注册实体系统 - /// - public interface IEntitySystemRegistrar - { -#if FANTASY_NET - /// - /// 注册该程序集中的所有实体系统 - /// - /// Awake 系统容器 - /// Update 系统容器 - /// Destroy 系统容器 - /// Deserialize 系统容器 - void RegisterSystems( - Dictionary> awakeSystems, - Dictionary> updateSystems, - Dictionary> destroySystems, - Dictionary> deserializeSystems); - - /// - /// 取消注册该程序集中的所有实体系统(热重载卸载时调用) - /// - /// Awake 系统容器 - /// Update 系统容器 - /// Destroy 系统容器 - /// Deserialize 系统容器 - void UnRegisterSystems( - Dictionary> awakeSystems, - Dictionary> updateSystems, - Dictionary> destroySystems, - Dictionary> deserializeSystems); -#endif -#if FANTASY_UNITY - /// - /// 注册该程序集中的所有实体系统 - /// - /// Awake 系统容器 - /// Update 系统容器 - /// Destroy 系统容器 - /// Deserialize 系统容器 - /// LateUpdate 系统容器 - void RegisterSystems( - Dictionary> awakeSystems, - Dictionary> updateSystems, - Dictionary> destroySystems, - Dictionary> deserializeSystems, - Dictionary> lateUpdateSystems); - - /// - /// 取消注册该程序集中的所有实体系统(热重载卸载时调用) - /// - /// Awake 系统容器 - /// Update 系统容器 - /// Destroy 系统容器 - /// Deserialize 系统容器 - /// LateUpdate 系统容器 - void UnRegisterSystems( - Dictionary> awakeSystems, - Dictionary> updateSystems, - Dictionary> destroySystems, - Dictionary> deserializeSystems, - Dictionary> lateUpdateSystems); -#endif - } -} \ No newline at end of file diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Assembly/Interface/IEntityTypeCollectionRegistrar.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Assembly/Interface/IEntityTypeCollectionRegistrar.cs deleted file mode 100644 index 32297d8..0000000 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Assembly/Interface/IEntityTypeCollectionRegistrar.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Fantasy.Assembly -{ - /// - /// 实体类型集合注册器接口 - /// 由 Source Generator 自动生成实现类,用于收集和提供程序集中定义的所有实体类型 - /// - public interface IEntityTypeCollectionRegistrar - { - /// - /// 获取该程序集中定义的所有实体类型 - /// 返回继承自 Entity 的所有类型列表 - /// - /// 实体类型列表 - List GetEntityTypes(); - } -} \ No newline at end of file diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Assembly/Interface/IEventSystemRegistrar.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Assembly/Interface/IEventSystemRegistrar.cs deleted file mode 100644 index 1fdd602..0000000 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Assembly/Interface/IEventSystemRegistrar.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; -using Fantasy.DataStructure.Collection; -using Fantasy.Event; - -namespace Fantasy.Assembly -{ - /// - /// 事件系统注册器接口 - /// 由 Source Generator 自动生成实现类,用于在程序集加载时注册事件系统 - /// - public interface IEventSystemRegistrar : IDisposable - { - /// - /// 注册该程序集中的所有事件系统 - /// - /// 同步事件容器 - /// 异步事件容器 - /// 领域事件容器 - void RegisterSystems( - OneToManyList events, - OneToManyList asyncEvents, - OneToManyList sphereEvents); - - /// - /// 取消注册该程序集中的所有事件系统(热重载卸载时调用) - /// - /// 同步事件容器 - /// 异步事件容器 - /// 领域事件容器 - void UnRegisterSystems( - OneToManyList events, - OneToManyList asyncEvents, - OneToManyList sphereEvents); - } -} \ No newline at end of file diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Assembly/Interface/IMessageHandlerResolver.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Assembly/Interface/IMessageHandlerResolver.cs deleted file mode 100644 index 9a12fa1..0000000 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Assembly/Interface/IMessageHandlerResolver.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System; -using System.Collections.Generic; -using Fantasy.DataStructure.Dictionary; -using Fantasy.Entitas; -using Fantasy.Network; -using Fantasy.Network.Interface; -using Fantasy.Async; - -namespace Fantasy.Assembly -{ - /// - /// 消息分发器注册器接口 - /// 由 Source Generator 自动生成实现类,用于在程序集加载时注册网络消息处理器 - /// - public interface IMessageHandlerResolver - { - /// - /// - /// - /// - int GetMessageHandlerCount(); - /// - /// - /// - /// - /// - /// - /// - /// - bool MessageHandler(Session session, uint rpcId, uint protocolCode, object message); -#if FANTASY_NET - /// - /// - /// - /// - int GetRouteMessageHandlerCount(); - /// - /// - /// - /// - /// - /// - /// - /// - /// - FTask RouteMessageHandler(Session session, Entity entity, uint rpcId, uint protocolCode, object message); -#endif - } -} \ No newline at end of file diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Assembly/Interface/INetworkProtocolOpCodeResolver.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Assembly/Interface/INetworkProtocolOpCodeResolver.cs deleted file mode 100644 index 6251037..0000000 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Assembly/Interface/INetworkProtocolOpCodeResolver.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; - -namespace Fantasy.Assembly -{ - /// - /// 网络协议 OpCode 解析器接口 - /// 用于通过生成的 switch 表达式实现高性能的 OpCode 到 Type 的解析 - /// - /// - /// 此接口由 SourceGenerator 自动生成的类实现。 - /// 每个包含网络协议的程序集都会生成自己的解析器实现。 - /// 生成的实现使用 switch 表达式而不是字典查找,以获得更好的性能。 - /// - public interface INetworkProtocolOpCodeResolver - { - /// - /// 获取当前OpCode数量 - /// - /// 返回对应的OpCode数量 - int GetOpCodeCount(); - /// - /// 获取当前RouteType数量 - /// - /// 返回对应的RouteType数量 - int GetCustomRouteTypeCount(); - /// - /// 根据指定的 OpCode 获取对应的 Type - /// - /// 网络协议操作码 - /// OpCode 对应的类型;如果未找到则返回 null - Type GetOpCodeType(uint opCode); - /// - /// 根据指定的 OpCode 获取对应的 CustomRouteType - /// - /// 网络协议操作码 - /// OpCode 对应的CustomRouteType;如果未找到则返回 null - int? GetCustomRouteType(uint opCode); - - } -} \ No newline at end of file diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Assembly/Interface/INetworkProtocolRegistrar.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Assembly/Interface/INetworkProtocolRegistrar.cs deleted file mode 100644 index 24b8cb0..0000000 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Assembly/Interface/INetworkProtocolRegistrar.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Fantasy.Assembly -{ - /// - /// NetworkProtocol 类型注册器接口 - /// 由 Source Generator 自动生成实现类,用于收集和提供程序集中需要 NetworkProtocol 序列化的类型 - /// - public interface INetworkProtocolRegistrar - { - /// - /// 获取该程序集中需要 NetworkProtocol 序列化的所有类型 - /// 返回所有使用 NetworkProtocol 序列化特性标记的类型列表 - /// - /// NetworkProtocol 序列化类型列表 - List GetNetworkProtocolTypes(); - } -} \ No newline at end of file diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Assembly/Interface/INetworkProtocolResponseTypeResolver.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Assembly/Interface/INetworkProtocolResponseTypeResolver.cs deleted file mode 100644 index 0385244..0000000 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Assembly/Interface/INetworkProtocolResponseTypeResolver.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; - -namespace Fantasy.Assembly -{ - /// - /// 网络协议响应类型解析器接口 - /// 用于根据 OpCode 解析对应的响应消息类型 - /// 此接口通常由 NetworkProtocol SourceGenerator 自动生成实现 - /// - public interface INetworkProtocolResponseTypeResolver - { - /// - /// 获取已注册的 Response 总数 - /// - /// 协议系统中可用的 OpCode 数量 - int GetRequestCount(); - - /// - /// 获取指定 OpCode 对应的响应消息类型 - /// 用于在处理网络消息时确定期望的响应类型 - /// - /// 要解析的 OpCode - /// 响应消息的类型,如果该 OpCode 没有关联响应类型则返回 null - Type GetResponseType(uint opCode); - } -} \ No newline at end of file diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Assembly/Interface/ISeparateTableRegistrar.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Assembly/Interface/ISeparateTableRegistrar.cs deleted file mode 100644 index 821050e..0000000 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Assembly/Interface/ISeparateTableRegistrar.cs +++ /dev/null @@ -1,34 +0,0 @@ -#if FANTASY_NET -namespace Fantasy.Assembly; - -/// -/// 分表注册器接口,用于自动注册标记了 SeparateTableAttribute 的实体类型。 -/// 通过 Source Generator 自动生成实现类,替代运行时反射,提升性能。 -/// -public interface ISeparateTableRegistrar -{ - /// - /// 分表信息记录,包含父实体类型、子实体类型和数据库集合名称。 - /// - /// 父实体的类型,表示子实体属于哪个父实体。 - /// 子实体的类型,即标记了 SeparateTableAttribute 的实体类型。 - /// 在数据库中使用的集合名称。 - public sealed record SeparateTableInfo(Type RootType, Type EntityType, string TableName); - - /// - /// 注册所有分表信息。 - /// 返回包含所有标记了 SeparateTableAttribute 的实体及其配置信息的列表。 - /// - /// 分表信息列表。 - List Register(); - - /// - /// 反注册所有分表信息。 - /// 返回需要移除的分表信息列表。 - /// - /// 分表信息列表。 - List UnRegister(); -} - - -#endif diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Component/EntityComponent.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Component/EntityComponent.cs new file mode 100644 index 0000000..67dc9ef --- /dev/null +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Component/EntityComponent.cs @@ -0,0 +1,523 @@ +// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; +using Fantasy.Assembly; +using Fantasy.Async; +using Fantasy.DataStructure.Collection; +using Fantasy.Entitas; +using Fantasy.Entitas.Interface; +using Fantasy.Helper; +#pragma warning disable CS8602 // Dereference of a possibly null reference. +#pragma warning disable CS8765 // Nullability of type of parameter doesn't match overridden member (possibly because of nullability attributes). + +#pragma warning disable CS8604 // Possible null reference argument. +#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type. +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +namespace Fantasy.Entitas +{ + internal sealed class UpdateQueueInfo + { + public bool IsStop; + public readonly Type Type; + public readonly long RunTimeId; + + public UpdateQueueInfo(Type type, long runTimeId) + { + Type = type; + IsStop = false; + RunTimeId = runTimeId; + } + } + + internal sealed class FrameUpdateQueueInfo + { + public readonly Type Type; + public readonly long RunTimeId; + + public FrameUpdateQueueInfo(Type type, long runTimeId) + { + Type = type; + RunTimeId = runTimeId; + } + } + + internal struct CustomEntitiesSystemKey : IEquatable + { + public int CustomEventType { get; } + public Type EntitiesType { get; } + public CustomEntitiesSystemKey(int customEventType, Type entitiesType) + { + CustomEventType = customEventType; + EntitiesType = entitiesType; + } + public bool Equals(CustomEntitiesSystemKey other) + { + return CustomEventType == other.CustomEventType && EntitiesType == other.EntitiesType; + } + + public override bool Equals(object obj) + { + return obj is CustomEntitiesSystemKey other && Equals(other); + } + + public override int GetHashCode() + { + return HashCode.Combine(CustomEventType, EntitiesType); + } + } + + /// + /// Entity管理组件 + /// +#if FANTASY_UNITY + public sealed class EntityComponent : Entity, ISceneUpdate, ISceneLateUpdate, IAssembly +#else + public sealed class EntityComponent : Entity, ISceneUpdate, IAssembly +#endif + { + private readonly OneToManyList _assemblyList = new(); + private readonly OneToManyList _assemblyHashCodes = new(); + + private readonly Dictionary _awakeSystems = new(); + private readonly Dictionary _updateSystems = new(); + private readonly Dictionary _destroySystems = new(); + private readonly Dictionary _deserializeSystems = new(); + + private readonly OneToManyList _assemblyCustomSystemList = new(); + private readonly Dictionary _customEntitiesSystems = new Dictionary(); + + private readonly Dictionary _hashCodes = new Dictionary(); + private readonly Queue _updateQueue = new Queue(); + + private readonly Dictionary _updateQueueDic = new Dictionary(); +#if FANTASY_UNITY + private readonly Dictionary _lateUpdateSystems = new(); + private readonly Queue _lateUpdateQueue = new Queue(); + private readonly Dictionary _lateUpdateQueueDic = new Dictionary(); +#endif + + internal async FTask Initialize() + { + await AssemblySystem.Register(this); + return this; + } + + #region Assembly + + public FTask Load(long assemblyIdentity) + { + var task = FTask.Create(false); + Scene?.ThreadSynchronizationContext.Post(() => + { + LoadInner(assemblyIdentity); + task.SetResult(); + }); + return task; + } + + public FTask ReLoad(long assemblyIdentity) + { + var task = FTask.Create(false); + Scene?.ThreadSynchronizationContext.Post(() => + { + OnUnLoadInner(assemblyIdentity); + LoadInner(assemblyIdentity); + task.SetResult(); + }); + + return task; + } + + public FTask OnUnLoad(long assemblyIdentity) + { + var task = FTask.Create(false); + Scene?.ThreadSynchronizationContext.Post(() => + { + OnUnLoadInner(assemblyIdentity); + task.SetResult(); + }); + return task; + } + + private void LoadInner(long assemblyIdentity) + { + foreach (var entityType in AssemblySystem.ForEach(assemblyIdentity, typeof(IEntity))) + { + _hashCodes.Add(entityType, HashCodeHelper.ComputeHash64(entityType.FullName)); + _assemblyHashCodes.Add(assemblyIdentity, entityType); + } + + foreach (var entitiesSystemType in AssemblySystem.ForEach(assemblyIdentity, typeof(IEntitiesSystem))) + { + Type entitiesType = null; + var entity = Activator.CreateInstance(entitiesSystemType); + + switch (entity) + { + case IAwakeSystem iAwakeSystem: + { + entitiesType = iAwakeSystem.EntitiesType(); + _awakeSystems.Add(entitiesType, iAwakeSystem); + break; + } + case IDestroySystem iDestroySystem: + { + entitiesType = iDestroySystem.EntitiesType(); + _destroySystems.Add(entitiesType, iDestroySystem); + break; + } + case IDeserializeSystem iDeserializeSystem: + { + entitiesType = iDeserializeSystem.EntitiesType(); + _deserializeSystems.Add(entitiesType, iDeserializeSystem); + break; + } + case IUpdateSystem iUpdateSystem: + { + entitiesType = iUpdateSystem.EntitiesType(); + _updateSystems.Add(entitiesType, iUpdateSystem); + break; + } +#if FANTASY_UNITY + case ILateUpdateSystem iLateUpdateSystem: + { + entitiesType = iLateUpdateSystem.EntitiesType(); + _lateUpdateSystems.Add(entitiesType, iLateUpdateSystem); + break; + } +#endif + default: + { + Log.Error($"IEntitiesSystem not support type {entitiesSystemType}"); + return; + } + } + + _assemblyList.Add(assemblyIdentity, entitiesType); + } + + foreach (var customEntitiesSystemType in AssemblySystem.ForEach(assemblyIdentity, typeof(ICustomEntitiesSystem))) + { + var entity = (ICustomEntitiesSystem)Activator.CreateInstance(customEntitiesSystemType); + var customEntitiesSystemKey = new CustomEntitiesSystemKey(entity.CustomEventType, entity.EntitiesType()); + _customEntitiesSystems.Add(customEntitiesSystemKey, entity); + _assemblyCustomSystemList.Add(assemblyIdentity, customEntitiesSystemKey); + } + } + + private void OnUnLoadInner(long assemblyIdentity) + { + if (_assemblyHashCodes.TryGetValue(assemblyIdentity, out var entityType)) + { + foreach (var type in entityType) + { + _hashCodes.Remove(type); + } + + _assemblyHashCodes.RemoveByKey(assemblyIdentity); + } + + if (_assemblyList.TryGetValue(assemblyIdentity, out var assembly)) + { + foreach (var type in assembly) + { + _awakeSystems.Remove(type); + _updateSystems.Remove(type); + _destroySystems.Remove(type); +#if FANTASY_UNITY + _lateUpdateSystems.Remove(type); +#endif + _deserializeSystems.Remove(type); + } + + _assemblyList.RemoveByKey(assemblyIdentity); + } + + if (_assemblyCustomSystemList.TryGetValue(assemblyIdentity, out var customSystemAssembly)) + { + foreach (var customEntitiesSystemKey in customSystemAssembly) + { + _customEntitiesSystems.Remove(customEntitiesSystemKey); + } + + _assemblyCustomSystemList.RemoveByKey(assemblyIdentity); + } + } + + #endregion + + #region Event + + /// + /// 触发实体的唤醒方法 + /// + /// 实体对象 + public void Awake(Entity entity) + { + if (!_awakeSystems.TryGetValue(entity.Type, out var awakeSystem)) + { + return; + } + + try + { + awakeSystem.Invoke(entity); + } + catch (Exception e) + { + Log.Error($"{entity.Type.FullName} Error {e}"); + } + } + + /// + /// 触发实体的销毁方法 + /// + /// 实体对象 + public void Destroy(Entity entity) + { + if (!_destroySystems.TryGetValue(entity.Type, out var system)) + { + return; + } + + try + { + system.Invoke(entity); + } + catch (Exception e) + { + Log.Error($"{entity.Type.FullName} Destroy Error {e}"); + } + } + + /// + /// 触发实体的反序列化方法 + /// + /// 实体对象 + public void Deserialize(Entity entity) + { + if (!_deserializeSystems.TryGetValue(entity.Type, out var system)) + { + return; + } + + try + { + system.Invoke(entity); + } + catch (Exception e) + { + Log.Error($"{entity.Type.FullName} Deserialize Error {e}"); + } + } + + #endregion + + #region CustomEvent + + public void CustomSystem(Entity entity, int customEventType) + { + var customEntitiesSystemKey = new CustomEntitiesSystemKey(customEventType, entity.Type); + + if (!_customEntitiesSystems.TryGetValue(customEntitiesSystemKey, out var system)) + { + return; + } + + try + { + system.Invoke(entity); + } + catch (Exception e) + { + Log.Error($"{entity.Type.FullName} CustomSystem Error {e}"); + } + } + + #endregion + + #region Update + + /// + /// 将实体加入Update队列,准备进行Update + /// + /// 实体对象 + public void StartUpdate(Entity entity) + { + var type = entity.Type; + var entityRuntimeId = entity.RuntimeId; + + if (!_updateSystems.ContainsKey(type)) + { + return; + } + + var updateQueueInfo = new UpdateQueueInfo(type, entityRuntimeId); + _updateQueue.Enqueue(updateQueueInfo); + _updateQueueDic.Add(entityRuntimeId, updateQueueInfo); + } + + /// + /// 停止实体Update + /// + /// 实体对象 + public void StopUpdate(Entity entity) + { + if (!_updateQueueDic.Remove(entity.RuntimeId, out var updateQueueInfo)) + { + return; + } + + updateQueueInfo.IsStop = true; + } + + /// + /// 执行实体系统的Update + /// + public void Update() + { + var updateQueueCount = _updateQueue.Count; + + while (updateQueueCount-- > 0) + { + var updateQueueStruct = _updateQueue.Dequeue(); + + if (updateQueueStruct.IsStop) + { + continue; + } + + if (!_updateSystems.TryGetValue(updateQueueStruct.Type, out var updateSystem)) + { + continue; + } + + var entity = Scene.GetEntity(updateQueueStruct.RunTimeId); + + if (entity == null || entity.IsDisposed) + { + _updateQueueDic.Remove(updateQueueStruct.RunTimeId); + continue; + } + + _updateQueue.Enqueue(updateQueueStruct); + + try + { + updateSystem.Invoke(entity); + } + catch (Exception e) + { + Log.Error($"{updateQueueStruct.Type.FullName} Update Error {e}"); + } + } + } + + #endregion + +#if FANTASY_UNITY + #region LateUpdate + + /// + /// 将实体加入LateUpdate队列,准备进行LateUpdate + /// + /// 实体对象 + public void StartLateUpdate(Entity entity) + { + var type = entity.Type; + var entityRuntimeId = entity.RuntimeId; + + if (!_lateUpdateSystems.ContainsKey(type)) + { + return; + } + + var updateQueueInfo = new UpdateQueueInfo(type, entityRuntimeId); + _lateUpdateQueue.Enqueue(updateQueueInfo); + _lateUpdateQueueDic.Add(entityRuntimeId, updateQueueInfo); + } + + /// + /// 停止实体进行LateUpdate + /// + /// 实体对象 + public void StopLateUpdate(Entity entity) + { + if (!_lateUpdateQueueDic.Remove(entity.RuntimeId, out var updateQueueInfo)) + { + return; + } + + updateQueueInfo.IsStop = true; + } + + public void LateUpdate() + { + var lateUpdateQueue = _lateUpdateQueue.Count; + + while (lateUpdateQueue-- > 0) + { + var lateUpdateQueueStruct = _lateUpdateQueue.Dequeue(); + + if (lateUpdateQueueStruct.IsStop) + { + continue; + } + + if (!_lateUpdateSystems.TryGetValue(lateUpdateQueueStruct.Type, out var lateUpdateSystem)) + { + continue; + } + + var entity = Scene.GetEntity(lateUpdateQueueStruct.RunTimeId); + + if (entity == null || entity.IsDisposed) + { + _lateUpdateQueueDic.Remove(lateUpdateQueueStruct.RunTimeId); + continue; + } + + _lateUpdateQueue.Enqueue(lateUpdateQueueStruct); + + try + { + lateUpdateSystem.Invoke(entity); + } + catch (Exception e) + { + Log.Error($"{lateUpdateQueueStruct.Type.FullName} Update Error {e}"); + } + } + } + #endregion +#endif + public long GetHashCode(Type type) + { + return _hashCodes[type]; + } + + /// + /// 释放实体系统管理器资源 + /// + public override void Dispose() + { + _updateQueue.Clear(); + _updateQueueDic.Clear(); +#if FANTASY_UNITY + _lateUpdateQueue.Clear(); + _lateUpdateQueueDic.Clear(); + _lateUpdateSystems.Clear(); +#endif + _assemblyList.Clear(); + _awakeSystems.Clear(); + _updateSystems.Clear(); + _destroySystems.Clear(); + _deserializeSystems.Clear(); + + AssemblySystem.UnRegister(this); + base.Dispose(); + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Component/EntityComponent/EntityComponent.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Component/EntityComponent/EntityComponent.cs deleted file mode 100644 index cb1ee99..0000000 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Component/EntityComponent/EntityComponent.cs +++ /dev/null @@ -1,476 +0,0 @@ -using System; -using System.Collections.Generic; -using Fantasy.Assembly; -using Fantasy.Async; -using Fantasy.Entitas.Interface; -// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract -// ReSharper disable ForCanBeConvertedToForeach -#pragma warning disable CS0649 // Field is never assigned to, and will always have its default value -#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. -#pragma warning disable CS8602 // Dereference of a possibly null reference. - -namespace Fantasy.Entitas -{ - /// - /// 更新队列节点,用于存储需要每帧更新的实体信息 - /// - internal sealed class UpdateQueueNode - { - /// - /// 实体类型 - /// - public long TypeHashCode; - /// - /// 实体运行时ID - /// - public long RunTimeId; - } - /// - /// 实体组件系统管理器,负责管理所有实体的生命周期和系统调度 - /// 支持程序集热重载、实体生命周期事件和每帧更新循环 - /// -#if FANTASY_UNITY - public sealed class EntityComponent : Entity, ISceneUpdate, ISceneLateUpdate, IAssemblyLifecycle -#else - public sealed class EntityComponent : Entity, ISceneUpdate, IAssemblyLifecycle -#endif - { - /// - /// 已加载的程序集清单ID集合 - /// - private readonly HashSet _assemblyManifests = new(); - /// - /// 实体唤醒系统字典,Key为实体类型TypeHashCode,Value为对应的唤醒系统 - /// - private readonly Dictionary> _awakeSystems = new(); - /// - /// 实体更新系统字典,Key为实体类型TypeHashCode,Value为对应的更新系统 - /// - private readonly Dictionary> _updateSystems = new(); - /// - /// 实体销毁系统字典,Key为实体类型TypeHashCode,Value为对应的销毁系统 - /// - private readonly Dictionary> _destroySystems = new(); - /// - /// 实体反序列化系统字典,Key为实体类型TypeHashCode,Value为对应的反序列化系统 - /// - private readonly Dictionary> _deserializeSystems = new(); - /// - /// 更新队列,使用链表实现循环遍历和O(1)删除 - /// - private readonly LinkedList _updateQueue = new(); - /// - /// 更新节点字典,Key为实体RuntimeId,Value为对应的链表节点,用于快速查找和删除 - /// - private readonly Dictionary> _updateNodes = new(); -#if FANTASY_UNITY - private readonly Dictionary> _lateUpdateSystems = new(); - /// - /// Late更新队列,使用链表实现循环遍历和O(1)删除 - /// - private readonly LinkedList _lateUpdateQueue = new(); - /// - /// Late更新节点字典,Key为实体RuntimeId,Value为对应的链表节点,用于快速查找和删除 - /// - private readonly Dictionary> _lateUpdateNodes = new(); -#endif - /// - /// 销毁时会清理组件里的所有数据 - /// - public override void Dispose() - { - if (IsDisposed) - { - return; - } - - _assemblyManifests.Clear(); - _awakeSystems.Clear(); - _updateSystems.Clear(); - _destroySystems.Clear(); - - _updateQueue.Clear(); - _updateNodes.Clear(); -#if FANTASY_UNITY - _lateUpdateSystems.Clear(); - _lateUpdateQueue.Clear(); - _lateUpdateNodes.Clear(); -#endif - AssemblyLifecycle.Remove(this); - base.Dispose(); - } - - #region AssemblyManifest - - /// - /// 初始化EntityComponent,将其注册到程序集系统中 - /// - /// 返回初始化后的EntityComponent实例 - internal async FTask Initialize() - { - await AssemblyLifecycle.Add(this); - return this; - } - - /// - /// 加载程序集,注册该程序集中的所有实体系统 - /// 支持热重载:如果程序集已加载,会先卸载再重新加载 - /// - /// 程序集清单对象,包含程序集的元数据和注册器 - /// 异步任务 - public FTask OnLoad(AssemblyManifest assemblyManifest) - { - var task = FTask.Create(false); - var assemblyManifestId = assemblyManifest.AssemblyManifestId; - Scene?.ThreadSynchronizationContext.Post(() => - { - // 如果程序集已加载,先卸载旧的 - if (_assemblyManifests.Contains(assemblyManifestId)) - { - OnUnLoadInner(assemblyManifest); - } -#if FANTASY_NET - assemblyManifest.EntitySystemRegistrar.RegisterSystems( - _awakeSystems, - _updateSystems, - _destroySystems, - _deserializeSystems); -#endif -#if FANTASY_UNITY - assemblyManifest.EntitySystemRegistrar.RegisterSystems( - _awakeSystems, - _updateSystems, - _destroySystems, - _deserializeSystems, - _lateUpdateSystems); -#endif - _assemblyManifests.Add(assemblyManifestId); - task.SetResult(); - }); - return task; - } - - /// - /// 卸载程序集,取消注册该程序集中的所有实体系统 - /// - /// 程序集清单对象,包含程序集的元数据和注册器 - /// 异步任务 - public FTask OnUnload(AssemblyManifest assemblyManifest) - { - var task = FTask.Create(false); - Scene?.ThreadSynchronizationContext.Post(() => - { - OnUnLoadInner(assemblyManifest); - task.SetResult(); - }); - return task; - } - - /// - /// 卸载程序集的内部实现 - /// 会清理该程序集注册的所有系统,并移除更新队列中对应的实体 - /// - /// 程序集清单对象,包含程序集的元数据和注册器 - private void OnUnLoadInner(AssemblyManifest assemblyManifest) - { -#if FANTASY_NET - assemblyManifest.EntitySystemRegistrar.UnRegisterSystems( - _awakeSystems, - _updateSystems, - _destroySystems, - _deserializeSystems); -#endif -#if FANTASY_UNITY - assemblyManifest.EntitySystemRegistrar.UnRegisterSystems( - _awakeSystems, - _updateSystems, - _destroySystems, - _deserializeSystems, - _lateUpdateSystems); -#endif - _assemblyManifests.Remove(assemblyManifest.AssemblyManifestId); - // 清理更新队列中已失效的节点(系统被卸载后,对应实体的更新系统不再存在) - var node = _updateQueue.First; - while (node != null) - { - var next = node.Next; - if (!_updateSystems.ContainsKey(node.Value.TypeHashCode)) - { - _updateQueue.Remove(node); - _updateNodes.Remove(node.Value.RunTimeId); - } - node = next; - } -#if FANTASY_UNITY - var lateNode = _lateUpdateQueue.First; - while (lateNode != null) - { - var next = lateNode.Next; - if (!_lateUpdateSystems.ContainsKey(lateNode.Value.TypeHashCode)) - { - _lateUpdateQueue.Remove(lateNode); - _lateUpdateNodes.Remove(lateNode.Value.RunTimeId); - } - lateNode = next; - } -#endif - } - - #endregion - - #region Event - - /// - /// 触发实体的唤醒事件,调用对应的AwakeSystem - /// - /// 需要唤醒的实体 - public void Awake(Entity entity) - { - if (!_awakeSystems.TryGetValue(entity.TypeHashCode, out var awakeSystem)) - { - return; - } - - try - { - awakeSystem(entity); - } - catch (Exception e) - { - Log.Error($"{entity.Type.FullName} Error {e}"); - } - } - - /// - /// 触发实体的销毁事件,调用对应的DestroySystem - /// - /// 需要销毁的实体 - public void Destroy(Entity entity) - { - if (!_destroySystems.TryGetValue(entity.TypeHashCode, out var system)) - { - return; - } - - try - { - system(entity); - } - catch (Exception e) - { - Log.Error($"{entity.Type.FullName} Destroy Error {e}"); - } - } - - /// - /// 触发实体的反序列化事件,调用对应的DeserializeSystem - /// - /// 需要反序列化的实体 - public void Deserialize(Entity entity) - { - if (!_deserializeSystems.TryGetValue(entity.TypeHashCode, out var system)) - { - return; - } - - try - { - system(entity); - } - catch (Exception e) - { - Log.Error($"{entity.Type.FullName} Deserialize Error {e}"); - } - } - - #endregion - - #region Update - - /// - /// 注册实体到每帧更新循环 - /// 实体将在每帧Update时执行对应的UpdateSystem - /// - /// 需要注册更新的实体 - public void RegisterUpdate(Entity entity) - { - var typeHashCode = entity.TypeHashCode; - // 检查该实体类型是否有对应的更新系统 - if (!_updateSystems.ContainsKey(typeHashCode)) - { - return; - } - - var runtimeId = entity.RuntimeId; - // 防止重复注册 - if (_updateNodes.ContainsKey(runtimeId)) - { - return; - } - - // 创建节点并加入链表尾部 - var nodeData = new UpdateQueueNode { TypeHashCode = typeHashCode, RunTimeId = runtimeId }; - var node = _updateQueue.AddLast(nodeData); - _updateNodes.Add(runtimeId, node); - } - - /// - /// 从每帧更新循环中注销实体 - /// 实体将不再执行UpdateSystem - /// - /// 需要注销更新的实体 - public void UnregisterUpdate(Entity entity) - { - if (!_updateNodes.Remove(entity.RuntimeId, out var node)) - { - return; - } - - // 利用链表节点实现O(1)时间复杂度删除 - _updateQueue.Remove(node); - } - - /// - /// 每帧更新循环,遍历所有已注册的实体并调用对应的UpdateSystem - /// 使用链表实现循环队列,已删除的实体会自动清理 - /// - public void Update() - { - var scene = Scene; - var node = _updateQueue.First; - var count = _updateQueue.Count; - - // 遍历当前所有节点,count确保只遍历本帧的节点 - while (count-- > 0 && node != null) - { - var next = node.Next; // 提前保存下一个节点,防止当前节点被删除 - var data = node.Value; - - // 检查更新系统是否存在(可能被热重载卸载) - if (!_updateSystems.TryGetValue(data.TypeHashCode, out var updateSystem)) - { - node = next; - continue; - } - - var entity = scene.GetEntity(data.RunTimeId); - - // 如果实体已销毁,自动清理 - if (entity == null || entity.IsDisposed) - { - _updateQueue.Remove(node); - _updateNodes.Remove(data.RunTimeId); - } - else - { - try - { - updateSystem.Invoke(entity); - } - catch (Exception e) - { - Log.Error($"Update Error {e}"); - } - } - - node = next; - } - } - - #endregion - - #region LateUpdate -#if FANTASY_UNITY - /// - /// 注册实体到每帧更新循环 - /// 实体将在每帧LateUUpdate时执行对应的LateUUpdateSystem - /// - /// 需要注册更新的实体 - public void RegisterLateUpdate(Entity entity) - { - var typeHashCode = entity.TypeHashCode; - // 检查该实体类型是否有对应的更新系统 - if (!_lateUpdateSystems.ContainsKey(typeHashCode)) - { - return; - } - - var runtimeId = entity.RuntimeId; - // 防止重复注册 - if (_lateUpdateNodes.ContainsKey(runtimeId)) - { - return; - } - - // 创建节点并加入链表尾部 - var nodeData = new UpdateQueueNode { TypeHashCode = typeHashCode, RunTimeId = runtimeId }; - var node = _lateUpdateQueue.AddLast(nodeData); - _lateUpdateNodes.Add(runtimeId, node); - } - - /// - /// 从每帧更新循环中注销实体 - /// 实体将不再执行LateUpdateSystem - /// - /// 需要注销更新的实体 - public void UnregisterLateUpdate(Entity entity) - { - if (!_lateUpdateNodes.Remove(entity.RuntimeId, out var node)) - { - return; - } - - // 利用链表节点实现O(1)时间复杂度删除 - _lateUpdateQueue.Remove(node); - } - - /// - /// 每帧更新循环,遍历所有已注册的实体并调用对应的LateUpdateSystem - /// 使用链表实现循环队列,已删除的实体会自动清理 - /// - public void LateUpdate() - { - var scene = Scene; - var node = _lateUpdateQueue.First; - var count = _lateUpdateQueue.Count; - - // 遍历当前所有节点,count确保只遍历本帧的节点 - while (count-- > 0 && node != null) - { - var next = node.Next; // 提前保存下一个节点,防止当前节点被删除 - var data = node.Value; - - // 检查更新系统是否存在(可能被热重载卸载) - if (!_lateUpdateSystems.TryGetValue(data.TypeHashCode, out var lateUpdateSystem)) - { - node = next; - continue; - } - - var entity = scene.GetEntity(data.RunTimeId); - - // 如果实体已销毁,自动清理 - if (entity == null || entity.IsDisposed) - { - _lateUpdateQueue.Remove(node); - _lateUpdateNodes.Remove(data.RunTimeId); - } - else - { - try - { - lateUpdateSystem.Invoke(entity); - } - catch (Exception e) - { - Log.Error($"Update Error {e}"); - } - } - - node = next; - } - } -#endif - #endregion - } -} diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Component/EventComponent/EventComponent.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Component/EventComponent/EventComponent.cs index 4a106e5..30ca61e 100644 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Component/EventComponent/EventComponent.cs +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Component/EventComponent/EventComponent.cs @@ -1,108 +1,129 @@ using System; -using System.Collections.Generic; +using System.Reflection; using Fantasy.Assembly; using Fantasy.Async; using Fantasy.DataStructure.Collection; using Fantasy.Entitas; + +// ReSharper disable PossibleMultipleEnumeration +#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type. +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member // ReSharper disable MethodOverloadWithOptionalParameter namespace Fantasy.Event { - /// - /// 事件组件系统,负责管理和调度所有事件 - /// - public sealed class EventComponent : Entity, IAssemblyLifecycle + internal sealed class EventCache { - private readonly HashSet _assemblyManifests = new(); - private readonly OneToManyList _events = new(); - private readonly OneToManyList _asyncEvents = new(); - private readonly OneToManyList _sphereEvents = new(); - - /// - /// 销毁时会清理组件里的所有数据 - /// - public override void Dispose() + public readonly Type EnventType; + public readonly object Obj; + public EventCache(Type enventType, object obj) { - if (IsDisposed) - { - return; - } - - _assemblyManifests.Clear(); - _events.Clear(); - _asyncEvents.Clear(); - _sphereEvents.Clear(); - AssemblyLifecycle.Remove(this); - base.Dispose(); + EnventType = enventType; + Obj = obj; } + } - #region AssemblyManifest + public sealed class EventComponent : Entity, IAssembly + { + private readonly OneToManyList _events = new(); + private readonly OneToManyList _asyncEvents = new(); + private readonly OneToManyList _assemblyEvents = new(); + private readonly OneToManyList _assemblyAsyncEvents = new(); - /// - /// 初始化EventComponent,将其注册到程序集系统中 - /// - /// 返回初始化后的EventComponent实例 internal async FTask Initialize() { - await AssemblyLifecycle.Add(this); + await AssemblySystem.Register(this); return this; } - - /// - /// 加载程序集,注册该程序集中的所有事件系统 - /// 支持热重载:如果程序集已加载,会先卸载再重新加载 - /// - /// 程序集清单对象,包含程序集的元数据和注册器 - /// 异步任务 - public async FTask OnLoad(AssemblyManifest assemblyManifest) + + #region Assembly + + public async FTask Load(long assemblyIdentity) { var tcs = FTask.Create(false); - var assemblyManifestId = assemblyManifest.AssemblyManifestId; Scene?.ThreadSynchronizationContext.Post(() => { - // 如果程序集已加载,先卸载旧的 - if (_assemblyManifests.Contains(assemblyManifestId)) + LoadInner(assemblyIdentity); + tcs.SetResult(); + }); + await tcs; + } + + public async FTask ReLoad(long assemblyIdentity) + { + var tcs = FTask.Create(false); + Scene?.ThreadSynchronizationContext.Post(() => + { + OnUnLoadInner(assemblyIdentity); + LoadInner(assemblyIdentity); + tcs.SetResult(); + }); + await tcs; + } + + public async FTask OnUnLoad(long assemblyIdentity) + { + var tcs = FTask.Create(false); + Scene?.ThreadSynchronizationContext.Post(() => + { + OnUnLoadInner(assemblyIdentity); + tcs.SetResult(); + }); + await tcs; + } + + private void LoadInner(long assemblyIdentity) + { + foreach (var type in AssemblySystem.ForEach(assemblyIdentity, typeof(IEvent))) + { + var @event = (IEvent)Activator.CreateInstance(type); + + if (@event == null) { - OnUnLoadInner(assemblyManifest); + continue; } - assemblyManifest.EventSystemRegistrar.RegisterSystems( - _events, - _asyncEvents, - _sphereEvents); - _assemblyManifests.Add(assemblyManifestId); - tcs.SetResult(); - }); - await tcs; - } - - /// - /// 卸载程序集,取消注册该程序集中的所有实体系统 - /// - /// 程序集清单对象,包含程序集的元数据和注册器 - /// 异步任务 - public async FTask OnUnload(AssemblyManifest assemblyManifest) - { - var tcs = FTask.Create(false); - Scene?.ThreadSynchronizationContext.Post(() => + + var eventType = @event.EventType(); + _events.Add(eventType, @event); + _assemblyEvents.Add(assemblyIdentity, new EventCache(eventType, @event)); + } + + foreach (var type in AssemblySystem.ForEach(assemblyIdentity, typeof(IAsyncEvent))) { - OnUnLoadInner(assemblyManifest); - tcs.SetResult(); - }); - await tcs; + var @event = (IAsyncEvent)Activator.CreateInstance(type); + + if (@event == null) + { + continue; + } + + var eventType = @event.EventType(); + _asyncEvents.Add(eventType, @event); + _assemblyAsyncEvents.Add(assemblyIdentity, new EventCache(eventType, @event)); + } } - - /// - /// 卸载程序集的内部实现 - /// 会清理该程序集注册的所有系统 - /// - /// 程序集清单对象,包含程序集的元数据和注册器 - private void OnUnLoadInner(AssemblyManifest assemblyManifest) + + private void OnUnLoadInner(long assemblyIdentity) { - assemblyManifest.EventSystemRegistrar.UnRegisterSystems( - _events, - _asyncEvents, - _sphereEvents); - _assemblyManifests.Remove(assemblyManifest.AssemblyManifestId); + if (_assemblyEvents.TryGetValue(assemblyIdentity, out var events)) + { + foreach (var @event in events) + { + _events.RemoveValue(@event.EnventType, (IEvent)@event.Obj); + } + + _assemblyEvents.RemoveByKey(assemblyIdentity); + } + + if (_assemblyAsyncEvents.TryGetValue(assemblyIdentity, out var asyncEvents)) + { + foreach (var @event in asyncEvents) + { + _asyncEvents.RemoveValue(@event.EnventType, (IAsyncEvent)@event.Obj); + } + + _assemblyAsyncEvents.RemoveByKey(assemblyIdentity); + } } #endregion @@ -110,13 +131,13 @@ namespace Fantasy.Event #region Publish /// - /// 发布同步事件(struct类型) + /// 发布一个值类型的事件数据。 /// - /// 事件数据类型(值类型) - /// 事件数据 + /// 事件数据类型(值类型)。 + /// 事件数据实例。 public void Publish(TEventData eventData) where TEventData : struct { - if (!_events.TryGetValue(typeof(TEventData).TypeHandle, out var list)) + if (!_events.TryGetValue(typeof(TEventData), out var list)) { return; } @@ -125,7 +146,7 @@ namespace Fantasy.Event { try { - ((IEvent)@event).Invoke(eventData); + @event.Invoke(eventData); } catch (Exception e) { @@ -135,14 +156,14 @@ namespace Fantasy.Event } /// - /// 发布同步事件(Entity类型) + /// 发布一个继承自 Entity 的事件数据。 /// - /// 事件数据类型(Entity类型) - /// 事件数据 - /// 事件处理完成后是否自动销毁Entity + /// 事件数据类型(继承自 Entity)。 + /// 事件数据实例。 + /// 是否释放事件数据。 public void Publish(TEventData eventData, bool isDisposed = true) where TEventData : Entity { - if (!_events.TryGetValue(typeof(TEventData).TypeHandle, out var list)) + if (!_events.TryGetValue(typeof(TEventData), out var list)) { return; } @@ -151,8 +172,7 @@ namespace Fantasy.Event { try { - // 转换为泛型接口,Entity是引用类型但仍避免虚方法调用开销 - ((IEvent)@event).Invoke(eventData); + @event.Invoke(eventData); } catch (Exception e) { @@ -165,72 +185,68 @@ namespace Fantasy.Event eventData.Dispose(); } } + + /// + /// 异步发布一个值类型的事件数据。 + /// + /// 事件数据类型(值类型)。 + /// 事件数据实例。 + /// 表示异步操作的任务。 + public async FTask PublishAsync(TEventData eventData) where TEventData : struct + { + if (!_asyncEvents.TryGetValue(typeof(TEventData), out var list)) + { + return; + } + + using var tasks = ListPool.Create(); + + foreach (var @event in list) + { + tasks.Add(@event.InvokeAsync(eventData)); + } + + await FTask.WaitAll(tasks); + } + + /// + /// 异步发布一个继承自 Entity 的事件数据。 + /// + /// 事件数据类型(继承自 Entity)。 + /// 事件数据实例。 + /// 是否释放事件数据。 + /// 表示异步操作的任务。 + public async FTask PublishAsync(TEventData eventData, bool isDisposed = true) where TEventData : Entity + { + if (!_asyncEvents.TryGetValue(eventData.GetType(), out var list)) + { + return; + } + + using var tasks = ListPool.Create(); + + foreach (var @event in list) + { + tasks.Add(@event.InvokeAsync(eventData)); + } + + await FTask.WaitAll(tasks); + + if (isDisposed) + { + eventData.Dispose(); + } + } - /// - /// 发布异步事件(struct类型) - /// - /// 事件数据类型(值类型) - /// 事件数据 - public async FTask PublishAsync(TEventData eventData) where TEventData : struct - { - if (!_asyncEvents.TryGetValue(typeof(TEventData).TypeHandle, out var list)) - { - return; - } - - using var tasks = ListPool.Create(); - - foreach (var @event in list) - { - try - { - tasks.Add(((IAsyncEvent)@event).InvokeAsync(eventData)); - } - catch (Exception e) - { - Log.Error(e); - } - } - - await FTask.WaitAll(tasks); - } - - /// - /// 发布异步事件(Entity类型) - /// - /// 事件数据类型(Entity类型) - /// 事件数据 - /// 事件处理完成后是否自动销毁Entity - public async FTask PublishAsync(TEventData eventData, bool isDisposed = true) where TEventData : Entity - { - if (!_asyncEvents.TryGetValue(typeof(TEventData).TypeHandle, out var list)) - { - return; - } - - using var tasks = ListPool.Create(); - - foreach (var @event in list) - { - try - { - tasks.Add(((IAsyncEvent)@event).InvokeAsync(eventData)); - } - catch (Exception e) - { - Log.Error(e); - } - } - - await FTask.WaitAll(tasks); - - if (isDisposed) - { - eventData.Dispose(); - } - } - #endregion - } -} + public override void Dispose() + { + _events.Clear(); + _asyncEvents.Clear(); + _assemblyEvents.Clear(); + _assemblyAsyncEvents.Clear(); + base.Dispose(); + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Component/EventComponent/Interface/IEvent.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Component/EventComponent/Interface/IEvent.cs index e093db9..5314995 100644 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Component/EventComponent/Interface/IEvent.cs +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Component/EventComponent/Interface/IEvent.cs @@ -13,53 +13,35 @@ namespace Fantasy.Event /// /// Type EventType(); + /// + /// 时间内部使用的入口 + /// + /// + void Invoke(object self); } - + /// - /// 事件的泛型接口 + /// 异步事件的接口 /// - /// 事件数据类型 - public interface IEvent : IEvent + public interface IAsyncEvent { /// - /// 事件内部使用的入口 + /// /// - /// 事件数据 - void Invoke(T self); - } - - /// - /// 异步事件的泛型接口 - /// - /// 事件数据类型 - public interface IAsyncEvent : IEvent - { + /// + Type EventType(); /// - /// 异步事件调用入口 + /// /// - /// 事件数据 - FTask InvokeAsync(T self); - } - - /// - /// 领域事件的泛型接口 - /// - /// 事件数据类型 - public interface ISphereEvent : IEvent - { - /// - /// 领域事件调用入口 - /// - /// 事件数据 - FTask Invoke(T self); + /// + FTask InvokeAsync(object self); } /// /// 事件的抽象类,要使用事件必须要继承这个抽象接口。 - /// 同时实现泛型和非泛型接口,支持零装箱调用 /// /// 要监听的事件泛型类型 - public abstract class EventSystem : IEvent + public abstract class EventSystem : IEvent { private readonly Type _selfType = typeof(T); /// @@ -70,22 +52,20 @@ namespace Fantasy.Event { return _selfType; } - /// /// 事件调用的方法,要在这个方法里编写事件发生的逻辑 /// /// protected abstract void Handler(T self); - /// - /// 泛型调用入口 + /// /// - /// 事件数据 - public void Invoke(T self) + /// + public void Invoke(object self) { try { - Handler(self); + Handler((T) self); } catch (Exception e) { @@ -95,10 +75,9 @@ namespace Fantasy.Event } /// /// 异步事件的抽象类,要使用事件必须要继承这个抽象接口。 - /// 同时实现泛型和非泛型接口,支持零装箱调用 /// /// 要监听的事件泛型类型 - public abstract class AsyncEventSystem : IAsyncEvent + public abstract class AsyncEventSystem : IAsyncEvent { private readonly Type _selfType = typeof(T); /// @@ -114,55 +93,15 @@ namespace Fantasy.Event /// /// protected abstract FTask Handler(T self); - /// - /// 泛型异步调用入口 - /// - /// 事件数据 - public async FTask InvokeAsync(T self) - { - try - { - await Handler(self); - } - catch (Exception e) - { - Log.Error($"{_selfType.Name} Error {e}"); - } - } - } - /// - /// 领域事件的抽象类,要使用事件必须要继承这个抽象接口。 - /// 同时实现泛型和非泛型接口,支持零装箱调用 - /// - /// 要监听的事件泛型类型 - public abstract class SphereEventSystem : ISphereEvent - { - private readonly Type _selfType = typeof(T); - /// - /// + /// /// /// - public Type EventType() - { - return _selfType; - } - - /// - /// 事件调用的方法,要在这个方法里编写事件发生的逻辑 - /// - /// - protected abstract FTask Handler(T self); - - /// - /// 泛型调用入口 - /// - /// 事件数据 - public async FTask Invoke(T self) + public async FTask InvokeAsync(object self) { try { - await Handler(self); + await Handler((T) self); } catch (Exception e) { diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Component/MessagePoolComponent.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Component/MessagePoolComponent.cs index 8227e06..f1ddb23 100644 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Component/MessagePoolComponent.cs +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Component/MessagePoolComponent.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.Runtime.CompilerServices; using Fantasy.DataStructure.Collection; using Fantasy.Entitas; -using Fantasy.Network.Interface; using Fantasy.Pool; using Fantasy.Serialize; @@ -18,8 +17,8 @@ namespace Fantasy.Entitas { private int _poolCount; private const int MaxCapacity = ushort.MaxValue; - private readonly OneToManyQueue _poolQueue = new OneToManyQueue(); - private readonly Dictionary> _typeCheckCache = new Dictionary>(); + private readonly OneToManyQueue _poolQueue = new OneToManyQueue(); + private readonly Dictionary> _typeCheckCache = new Dictionary>(); /// /// 销毁组件 /// @@ -37,7 +36,7 @@ namespace Fantasy.Entitas /// public T Rent() where T : AMessage, new() { - if (!_poolQueue.TryDequeue(typeof(T).TypeHandle, out var queue)) + if (!_poolQueue.TryDequeue(typeof(T), out var queue)) { var instance = new T(); instance.SetScene(Scene); @@ -59,10 +58,9 @@ namespace Fantasy.Entitas [MethodImpl(MethodImplOptions.AggressiveInlining)] public AMessage Rent(Type type) { - var runtimeTypeHandle = type.TypeHandle; - if (!_poolQueue.TryDequeue(runtimeTypeHandle, out var queue)) + if (!_poolQueue.TryDequeue(type, out var queue)) { - if (!_typeCheckCache.TryGetValue(runtimeTypeHandle, out var createInstance)) + if (!_typeCheckCache.TryGetValue(type, out var createInstance)) { if (!typeof(AMessage).IsAssignableFrom(type)) { @@ -71,7 +69,7 @@ namespace Fantasy.Entitas else { createInstance = CreateInstance.CreateMessage(type); - _typeCheckCache[runtimeTypeHandle] = createInstance; + _typeCheckCache[type] = createInstance; } } @@ -108,7 +106,7 @@ namespace Fantasy.Entitas _poolCount++; obj.SetIsPool(false); - _poolQueue.Enqueue(obj.GetType().TypeHandle, obj); + _poolQueue.Enqueue(obj.GetType(), obj); } /// @@ -135,7 +133,7 @@ namespace Fantasy.Entitas _poolCount++; obj.SetIsPool(false); - _poolQueue.Enqueue(typeof(T).TypeHandle, obj); + _poolQueue.Enqueue(typeof(T), obj); } } } \ No newline at end of file diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Component/SeparateTableComponent/SeparateTableAttribute.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Component/SeparateTableComponent/SeparateTableAttribute.cs deleted file mode 100644 index 3a63d0c..0000000 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Component/SeparateTableComponent/SeparateTableAttribute.cs +++ /dev/null @@ -1,49 +0,0 @@ -#if FANTASY_NET -namespace Fantasy.SeparateTable; - -/// -/// 分表存储特性,用于标记需要进行数据库分表存储的实体类型。 -/// 当实体标记此特性后,该实体将作为父实体的子组件,并在数据库中使用独立的集合进行存储。 -/// -/// -/// 使用场景: -/// - 当父实体的某些数据量较大,需要拆分到独立的数据库表中存储时 -/// - 需要优化数据库查询性能,避免单表数据过大时 -/// - Source Generator 会自动生成注册代码,无需手动反射处理 -/// -/// -/// -/// [SeparateTable(typeof(Player), "PlayerInventory")] -/// public class PlayerInventoryEntity : Entity -/// { -/// // 实体字段... -/// } -/// -/// -[AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = false)] -public class SeparateTableAttribute : Attribute -{ - /// - /// 获取父实体的类型,指示此实体属于哪个父实体的子集合。 - /// 通过此属性建立父子实体的逻辑关联关系。 - /// - public readonly Type RootType; - - /// - /// 获取在数据库中使用的集合名称(表名)。 - /// 此实体的数据将单独存储到此命名的集合中。 - /// - public readonly string CollectionName; - - /// - /// 初始化 类的新实例,指定父实体类型和数据库集合名称。 - /// - /// 父实体的类型,表示此分表实体从属于哪个父实体。 - /// 在数据库中存储此实体的集合名称(表名)。 - public SeparateTableAttribute(Type rootType, string collectionName) - { - RootType = rootType; - CollectionName = collectionName; - } -} -#endif diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Component/SeparateTableComponent/SeparateTableComponent.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Component/SeparateTableComponent/SeparateTableComponent.cs deleted file mode 100644 index e1848e1..0000000 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Component/SeparateTableComponent/SeparateTableComponent.cs +++ /dev/null @@ -1,221 +0,0 @@ -#if FANTASY_NET -// ReSharper disable SuspiciousTypeConversion.Global - -using Fantasy.Assembly; -using Fantasy.Async; -using Fantasy.DataStructure.Collection; -using Fantasy.Entitas; -using Fantasy.Entitas.Interface; -using Fantasy.Helper; -// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract -#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member -#pragma warning disable CS8604 // Possible null reference argument. -#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. - -namespace Fantasy.SeparateTable -{ - /// - /// 分表组件,用于管理实体的数据库分表存储功能。 - /// 负责注册、加载和保存标记了 的实体数据。 - /// - /// - /// 此组件实现了程序集生命周期接口,会在程序集加载/卸载时自动注册/反注册分表信息。 - /// 通过 Source Generator 生成的注册器自动管理分表实体的元数据,避免运行时反射。 - /// - public sealed class SeparateTableComponent : Entity, IAssemblyLifecycle - { - /// - /// 存储已加载的程序集清单ID集合,用于追踪哪些程序集的分表信息已注册。 - /// - private readonly HashSet _assemblyManifests = new(); - - /// - /// 分表信息映射表,键为父实体类型,值为该父实体对应的所有分表信息集合。 - /// 用于快速查询某个实体类型有哪些子实体需要分表存储。 - /// - private readonly OneToManyHashSet _separateTables = new (); - - #region AssemblyManifest - - /// - /// 初始化分表组件,将其注册到程序集生命周期管理器中。 - /// - /// 返回初始化后的分表组件实例。 - internal async FTask Initialize() - { - await AssemblyLifecycle.Add(this); - return this; - } - - /// - /// 当程序集加载时的回调方法,负责注册该程序集中的所有分表信息。 - /// - /// 加载的程序集清单,包含该程序集的所有元数据。 - /// 异步任务。 - /// - /// 此方法在线程同步上下文中执行,确保线程安全。 - /// 如果程序集已被加载过(如热重载场景),会先卸载旧的注册信息再重新注册。 - /// - public async FTask OnLoad(AssemblyManifest assemblyManifest) - { - var tcs = FTask.Create(false); - var assemblyManifestId = assemblyManifest.AssemblyManifestId; - Scene?.ThreadSynchronizationContext.Post(() => - { - // 如果程序集已加载,先卸载旧的 - if (_assemblyManifests.Contains(assemblyManifestId)) - { - OnUnLoadInner(assemblyManifest); - } - - // 从 Source Generator 生成的注册器中获取分表信息 - var separateTableInfos = assemblyManifest.SeparateTableRegistrar.Register(); - - // 将分表信息按父实体类型进行分组注册 - foreach (var separateTableInfo in separateTableInfos) - { - _separateTables.Add(TypeHashCache.GetHashCode(separateTableInfo.RootType), separateTableInfo); - } - - _assemblyManifests.Add(assemblyManifestId); - tcs.SetResult(); - }); - await tcs; - } - - /// - /// 当程序集卸载时的回调方法,负责反注册该程序集中的所有分表信息。 - /// - /// 卸载的程序集清单。 - /// 异步任务。 - /// - /// 此方法在线程同步上下文中执行,确保线程安全。 - /// - public FTask OnUnload(AssemblyManifest assemblyManifest) - { - var task = FTask.Create(false); - Scene?.ThreadSynchronizationContext.Post(() => - { - OnUnLoadInner(assemblyManifest); - task.SetResult(); - }); - return task; - } - - /// - /// 卸载程序集的内部实现,从映射表中移除该程序集的所有分表信息。 - /// - /// 要卸载的程序集清单。 - private void OnUnLoadInner(AssemblyManifest assemblyManifest) - { - // 获取该程序集需要反注册的分表信息 - var separateTableInfos = assemblyManifest.SeparateTableRegistrar.UnRegister(); - - // 从映射表中逐个移除 - foreach (var separateTableInfo in separateTableInfos) - { - _separateTables.RemoveValue(TypeHashCache.GetHashCode(separateTableInfo.RootType), separateTableInfo); - } - - _assemblyManifests.Remove(assemblyManifest.AssemblyManifestId); - } - - #endregion - - #region Collections - - /// - /// 从数据库加载指定实体的所有分表数据,并自动建立父子关系。 - /// - /// 需要加载分表数据的实体实例。 - /// 实体的泛型类型,必须继承自 - /// 异步任务。 - /// - /// 此方法会根据实体类型查找其关联的所有分表配置,逐个从数据库中加载对应的分表实体, - /// 并通过 AddComponent 方法将这些分表实体作为组件添加到父实体上,建立父子关系。 - /// 如果实体类型没有配置分表信息,则直接返回不做任何操作。 - /// - /// - /// - /// var player = await db.Query<Player>(playerId); - /// await separateTableComponent.Load(player); // 加载玩家的所有分表数据 - /// - /// - public async FTask LoadWithSeparateTables(T entity) where T : Entity - { - // 检查该实体类型是否配置了分表 - if (!_separateTables.TryGetValue(entity.TypeHashCode, out var separateTables)) - { - return; - } - - var worldDateBase = Scene.World.DataBase; - - // 遍历所有分表配置,逐个加载 - foreach (var separateTable in separateTables) - { - // 使用实体ID作为查询条件,从指定的集合中加载分表实体 - var separateTableEntity = await worldDateBase.QueryNotLock( - entity.Id, true, separateTable.TableName); - - if (separateTableEntity == null) - { - continue; - } - - // 将加载的分表实体作为组件添加到父实体上 - entity.AddComponent(separateTableEntity); - } - } - - /// - /// 将实体及其所有分表组件保存到数据库中。 - /// - /// 需要保存的实体实例。 - /// 实体的泛型类型,必须继承自 并具有无参构造函数。 - /// 异步任务。 - /// - /// 此方法会检查实体是否配置了分表信息: - /// - 如果没有配置分表,则直接保存实体本身到数据库。 - /// - 如果配置了分表,会收集实体上所有需要分表存储的组件,统一批量保存到数据库。 - /// 使用对象池优化列表分配,避免频繁 GC。 - /// - /// - /// - /// player.Inventory.Items.Add(newItem); - /// await separateTableComponent.Save(player); // 保存玩家及分表数据 - /// - /// - public async FTask PersistAggregate(T entity) where T : Entity, new() - { - // 检查该实体类型是否配置了分表 - if (!_separateTables.TryGetValue(entity.TypeHashCode, out var separateTables)) - { - // 没有分表配置,直接保存实体 - await entity.Scene.World.DataBase.Save(entity); - return; - } - - // 使用对象池创建列表,避免 GC - using var saveSeparateTables = ListPool.Create(entity); - - // 收集所有需要分表保存的组件 - foreach (var separateTableInfo in separateTables) - { - var separateTableEntity = entity.GetComponent(separateTableInfo.EntityType); - if (separateTableEntity == null) - { - continue; - } - saveSeparateTables.Add(separateTableEntity); - } - - // 批量保存实体ID及其所有分表组件 - await entity.Scene.World.DataBase.Save(entity.Id, saveSeparateTables); - } - - #endregion - } -} - -#endif \ No newline at end of file diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Component/SingleCollectionComponent/SingleCollectionComponent.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Component/SingleCollectionComponent/SingleCollectionComponent.cs new file mode 100644 index 0000000..5c0969d --- /dev/null +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Component/SingleCollectionComponent/SingleCollectionComponent.cs @@ -0,0 +1,167 @@ +// ReSharper disable SuspiciousTypeConversion.Global + +using Fantasy.Assembly; +using Fantasy.Async; +using Fantasy.DataStructure.Collection; +using Fantasy.Entitas; +using Fantasy.Entitas.Interface; +using Fantasy.Helper; + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +#pragma warning disable CS8604 // Possible null reference argument. +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. +#if FANTASY_NET +namespace Fantasy.SingleCollection +{ + /// + /// 用于处理Entity下的实体进行数据库分表存储的组件 + /// + public sealed class SingleCollectionComponent : Entity, IAssembly + { + private CoroutineLock _coroutineLock; + private readonly OneToManyHashSet _collection = new OneToManyHashSet(); + + private readonly OneToManyList _assemblyCollections = + new OneToManyList(); + + private sealed class SingleCollectionInfo(Type rootType, string collectionName) + { + public readonly Type RootType = rootType; + public readonly string CollectionName = collectionName; + } + + internal async FTask Initialize() + { + var coroutineLockType = HashCodeHelper.ComputeHash64(GetType().FullName); + _coroutineLock = Scene.CoroutineLockComponent.Create(coroutineLockType); + await AssemblySystem.Register(this); + return this; + } + + #region Assembly + + public async FTask Load(long assemblyIdentity) + { + var tcs = FTask.Create(false); + Scene?.ThreadSynchronizationContext.Post(() => + { + LoadInner(assemblyIdentity); + tcs.SetResult(); + }); + await tcs; + } + + public async FTask ReLoad(long assemblyIdentity) + { + var tcs = FTask.Create(false); + Scene?.ThreadSynchronizationContext.Post(() => + { + OnUnLoadInner(assemblyIdentity); + LoadInner(assemblyIdentity); + tcs.SetResult(); + }); + await tcs; + } + + public async FTask OnUnLoad(long assemblyIdentity) + { + var tcs = FTask.Create(false); + Scene?.ThreadSynchronizationContext.Post(() => + { + OnUnLoadInner(assemblyIdentity); + tcs.SetResult(); + }); + await tcs; + } + + private void LoadInner(long assemblyIdentity) + { + foreach (var type in AssemblySystem.ForEach(assemblyIdentity, typeof(ISupportedSingleCollection))) + { + var customAttributes = type.GetCustomAttributes(typeof(SingleCollectionAttribute), false); + if (customAttributes.Length == 0) + { + Log.Error( + $"type {type.FullName} Implemented the interface of ISingleCollection, requiring the implementation of SingleCollectionAttribute"); + continue; + } + + var singleCollectionAttribute = (SingleCollectionAttribute)customAttributes[0]; + var rootType = singleCollectionAttribute.RootType; + var collectionName = singleCollectionAttribute.CollectionName; + _collection.Add(rootType, collectionName); + _assemblyCollections.Add(assemblyIdentity, new SingleCollectionInfo(rootType, collectionName)); + } + } + + private void OnUnLoadInner(long assemblyIdentity) + { + if (!_assemblyCollections.TryGetValue(assemblyIdentity, out var types)) + { + return; + } + + foreach (var singleCollectionInfo in types) + { + _collection.RemoveValue(singleCollectionInfo.RootType, singleCollectionInfo.CollectionName); + } + + _assemblyCollections.RemoveByKey(assemblyIdentity); + } + + #endregion + + #region Collections + + /// + /// 通过数据库获取某一个实体类型下所有的分表数据到当前实体下,并且会自动建立父子关系。 + /// + /// 实体实例 + /// 实体泛型类型 + public async FTask GetCollections(T entity) where T : Entity, ISingleCollectionRoot + { + if (!_collection.TryGetValue(typeof(T), out var collections)) + { + return; + } + + var worldDateBase = Scene.World.DataBase; + + using (await _coroutineLock.Wait(entity.Id)) + { + foreach (var collectionName in collections) + { + var singleCollection = await worldDateBase.QueryNotLock(entity.Id, true, collectionName); + entity.AddComponent(singleCollection); + } + } + } + + /// + /// 存储当前实体下支持分表的组件到数据中,包括存储实体本身。 + /// + /// 实体实例 + /// 实体泛型类型 + public async FTask SaveCollections(T entity) where T : Entity, ISingleCollectionRoot + { + using var collections = ListPool.Create(); + + foreach (var treeEntity in entity.ForEachSingleCollection) + { + if (treeEntity is not ISupportedSingleCollection) + { + continue; + } + + collections.Add(treeEntity); + } + + collections.Add(entity); + await entity.Scene.World.DataBase.Save(entity.Id, collections); + } + + #endregion + } +} + +#endif \ No newline at end of file diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Entity.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Entity.cs index 258f59a..99faad7 100644 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Entity.cs +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Entity.cs @@ -1,9 +1,7 @@ using System; using System.Collections.Generic; -using System.Runtime.CompilerServices; using System.Runtime.Serialization; using Fantasy.Entitas.Interface; -using Fantasy.IdFactory; using Fantasy.Pool; using MongoDB.Bson.Serialization.Attributes; using Newtonsoft.Json; @@ -32,7 +30,15 @@ namespace Fantasy.Entitas public abstract partial class Entity : IEntity { #region Members - + + /// + /// 获取一个值,表示实体是否支持对象池。 + /// + [BsonIgnore] + [JsonIgnore] + [ProtoIgnore] + [IgnoreDataMember] + private bool _isPool; /// /// 实体的Id /// @@ -80,14 +86,6 @@ namespace Fantasy.Entitas [IgnoreDataMember] [ProtoIgnore] public Type Type { get; protected set; } - /// - /// 实体的真实Type的HashCode - /// - [BsonIgnore] - [JsonIgnore] - [IgnoreDataMember] - [ProtoIgnore] - public long TypeHashCode { get; private set; } #if FANTASY_NET [BsonElement("t")] [BsonIgnoreIfNull] private EntityList _treeDb; [BsonElement("m")] [BsonIgnoreIfNull] private EntityList _multiDb; @@ -100,7 +98,6 @@ namespace Fantasy.Entitas /// /// 父实体的泛型类型 /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] public T GetParent() where T : Entity, new() { return Parent as T; @@ -141,11 +138,10 @@ namespace Fantasy.Entitas { if (!typeof(Entity).IsAssignableFrom(type)) { - throw new NotSupportedException($"Type:{type.FullName} must inherit from Entity"); + throw new NotSupportedException($"{type.FullName} Type:{type.FullName} must inherit from Entity"); } Entity entity = null; - var runtimeTypeHandle = type.TypeHandle; if (isPool) { @@ -153,10 +149,10 @@ namespace Fantasy.Entitas } else { - if (!scene.TypeInstance.TryGetValue(runtimeTypeHandle, out var createInstance)) + if (!scene.TypeInstance.TryGetValue(type, out var createInstance)) { createInstance = CreateInstance.CreateIPool(type); - scene.TypeInstance[runtimeTypeHandle] = createInstance; + scene.TypeInstance[type] = createInstance; } entity = (Entity)createInstance(); @@ -164,18 +160,17 @@ namespace Fantasy.Entitas entity.Scene = scene; entity.Type = type; - entity.TypeHashCode = TypeHashCache.GetHashCode(type); entity.SetIsPool(isPool); entity.Id = id; - entity.RuntimeId = scene.RuntimeIdFactory.Create(isPool); + entity.RuntimeId = scene.RuntimeIdFactory.Create; scene.AddEntity(entity); if (isRunEvent) { scene.EntityComponent.Awake(entity); - scene.EntityComponent.RegisterUpdate(entity); + scene.EntityComponent.StartUpdate(entity); #if FANTASY_UNITY - scene.EntityComponent.RegisterLateUpdate(entity); + scene.EntityComponent.StartLateUpdate(entity); #endif } @@ -209,18 +204,17 @@ namespace Fantasy.Entitas var entity = isPool ? scene.EntityPool.Rent() : new T(); entity.Scene = scene; entity.Type = typeof(T); - entity.TypeHashCode = EntityTypeHashCache.HashCode; entity.SetIsPool(isPool); entity.Id = id; - entity.RuntimeId = scene.RuntimeIdFactory.Create(isPool); + entity.RuntimeId = scene.RuntimeIdFactory.Create; scene.AddEntity(entity); if (isRunEvent) { scene.EntityComponent.Awake(entity); - scene.EntityComponent.RegisterUpdate(entity); + scene.EntityComponent.StartUpdate(entity); #if FANTASY_UNITY - scene.EntityComponent.RegisterLateUpdate(entity); + scene.EntityComponent.StartLateUpdate(entity); #endif } @@ -239,13 +233,13 @@ namespace Fantasy.Entitas /// 返回添加到实体上组件的实例 public T AddComponent(bool isPool = true) where T : Entity, new() { - var id = EntitySupportedChecker.IsMulti ? Scene.EntityIdFactory.Create : Id; + var id = SupportedMultiEntityChecker.IsSupported ? Scene.EntityIdFactory.Create : Id; var entity = Create(Scene, id, isPool, false); AddComponent(entity); Scene.EntityComponent.Awake(entity); - Scene.EntityComponent.RegisterUpdate(entity); + Scene.EntityComponent.StartUpdate(entity); #if FANTASY_UNITY - Scene.EntityComponent.RegisterLateUpdate(entity); + Scene.EntityComponent.StartLateUpdate(entity); #endif return entity; } @@ -262,9 +256,9 @@ namespace Fantasy.Entitas var entity = Create(Scene, id, isPool, false); AddComponent(entity); Scene.EntityComponent.Awake(entity); - Scene.EntityComponent.RegisterUpdate(entity); + Scene.EntityComponent.StartUpdate(entity); #if FANTASY_UNITY - Scene.EntityComponent.RegisterLateUpdate(entity); + Scene.EntityComponent.StartLateUpdate(entity); #endif return entity; } @@ -304,7 +298,13 @@ namespace Fantasy.Entitas } else { - var typeHashCode = component.TypeHashCode; +#if FANTASY_NET + if (component is ISupportedSingleCollection && component.Id != Id) + { + Log.Error($"component type :{type.FullName} for implementing ISupportedSingleCollection, it is required that the Id must be the same as the parent"); + } +#endif + var typeHashCode = Scene.EntityComponent.GetHashCode(type);; if (_tree == null) { @@ -337,6 +337,14 @@ namespace Fantasy.Entitas /// 要添加组件的泛型类型 public void AddComponent(T component) where T : Entity { + var type = typeof(T); + + if (type == typeof(Entity)) + { + Log.Error("Cannot add a generic Entity type as a component. Specify a more specific type."); + return; + } + if (this == component) { Log.Error("Cannot add oneself to one's own components"); @@ -345,18 +353,18 @@ namespace Fantasy.Entitas if (component.IsDisposed) { - Log.Error($"component is Disposed {typeof(T).FullName}"); + Log.Error($"component is Disposed {type.FullName}"); return; } component.Parent?.RemoveComponent(component, false); - if (EntitySupportedChecker.IsMulti) + if (SupportedMultiEntityChecker.IsSupported) { _multi ??= Scene.EntitySortedDictionaryPool.Rent(); _multi.Add(component.Id, component); #if FANTASY_NET - if (EntitySupportedChecker.IsDataBase) + if (SupportedDataBaseChecker.IsSupported) { _multiDb ??= Scene.EntityListPool.Rent(); _multiDb.Add(component); @@ -365,7 +373,13 @@ namespace Fantasy.Entitas } else { - var typeHashCode = component.TypeHashCode; +#if FANTASY_NET + if (SupportedSingleCollectionChecker.IsSupported && component.Id != Id) + { + Log.Error($"component type :{type.FullName} for implementing ISupportedSingleCollection, it is required that the Id must be the same as the parent"); + } +#endif + var typeHashCode = Scene.EntityComponent.GetHashCode(type); if (_tree == null) { @@ -373,13 +387,13 @@ namespace Fantasy.Entitas } else if (_tree.ContainsKey(typeHashCode)) { - Log.Error($"type:{typeof(T).FullName} If you want to add multiple components of the same type, please implement IMultiEntity"); + Log.Error($"type:{type.FullName} If you want to add multiple components of the same type, please implement IMultiEntity"); return; } _tree.Add(typeHashCode, component); #if FANTASY_NET - if (EntitySupportedChecker.IsDataBase) + if (SupportedDataBaseChecker.IsSupported) { _treeDb ??= Scene.EntityListPool.Rent(); _treeDb.Add(component); @@ -392,7 +406,7 @@ namespace Fantasy.Entitas } /// - /// 添加一个组件到当前实体上 + /// 添加一个组件到当前实体上 /// /// 组件的类型 /// 是否在对象池创建 @@ -403,9 +417,9 @@ namespace Fantasy.Entitas var entity = Entity.Create(Scene, type, id, isPool, false); AddComponent(entity); Scene.EntityComponent.Awake(entity); - Scene.EntityComponent.RegisterUpdate(entity); + Scene.EntityComponent.StartUpdate(entity); #if FANTASY_UNITY - Scene.EntityComponent.RegisterLateUpdate(entity); + Scene.EntityComponent.StartLateUpdate(entity); #endif return entity; } @@ -419,15 +433,9 @@ namespace Fantasy.Entitas /// /// /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool HasComponent() where T : Entity, new() { - if (_tree == null) - { - return false; - } - - return _tree.ContainsKey(EntityTypeHashCache.HashCode); + return HasComponent(typeof(T)); } /// @@ -435,15 +443,14 @@ namespace Fantasy.Entitas /// /// /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool HasComponent(Type type) { if (_tree == null) { return false; } - - return _tree.ContainsKey(TypeHashCache.GetHashCode(type)); + + return _tree.ContainsKey(Scene.EntityComponent.GetHashCode(type)); } /// @@ -452,7 +459,6 @@ namespace Fantasy.Entitas /// /// /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool HasComponent(long id) where T : Entity, ISupportedMultiEntity, new() { if (_multi == null) @@ -468,11 +474,10 @@ namespace Fantasy.Entitas #region GetComponent /// - /// 当前实体上查找一个子实体 + /// 当前实体上查找一个字实体 /// /// 要查找实体泛型类型 /// 查找的实体实例 - [MethodImpl(MethodImplOptions.AggressiveInlining)] public T GetComponent() where T : Entity, new() { if (_tree == null) @@ -480,15 +485,15 @@ namespace Fantasy.Entitas return null; } - return _tree.TryGetValue(EntityTypeHashCache.HashCode, out var component) ? (T)component : null; + var typeHashCode = Scene.EntityComponent.GetHashCode(typeof(T)); + return _tree.TryGetValue(typeHashCode, out var component) ? (T)component : null; } /// - /// 当前实体上查找一个子实体 + /// 当前实体上查找一个字实体 /// /// 要查找实体类型 /// 查找的实体实例 - [MethodImpl(MethodImplOptions.AggressiveInlining)] public Entity GetComponent(Type type) { if (_tree == null) @@ -496,28 +501,28 @@ namespace Fantasy.Entitas return null; } - return _tree.GetValueOrDefault(TypeHashCache.GetHashCode(type)); + var typeHashCode = Scene.EntityComponent.GetHashCode(type); + return _tree.TryGetValue(typeHashCode, out var component) ? component : null; } /// - /// 当前实体上查找一个子实体 + /// 当前实体上查找一个字实体 /// /// 要查找实体的Id /// 要查找实体泛型类型 /// 查找的实体实例 - [MethodImpl(MethodImplOptions.AggressiveInlining)] public T GetComponent(long id) where T : Entity, ISupportedMultiEntity, new() { if (_multi == null) { - return null; + return default; } - return _multi.TryGetValue(id, out var entity) ? (T)entity : null; + return _multi.TryGetValue(id, out var entity) ? (T)entity : default; } /// - /// 当前实体上查找一个子实体,如果没有就创建一个新的并添加到当前实体上 + /// 当前实体上查找一个字实体,如果没有就创建一个新的并添加到当前实体上 /// /// 是否从对象池创建 /// 要查找或添加实体泛型类型 @@ -539,7 +544,7 @@ namespace Fantasy.Entitas /// public void RemoveComponent(bool isDispose = true) where T : Entity, new() { - if (EntitySupportedChecker.IsMulti) + if (SupportedMultiEntityChecker.IsSupported) { throw new NotSupportedException($"{typeof(T).FullName} message:Cannot delete components that implement the ISupportedMultiEntity interface"); } @@ -549,13 +554,14 @@ namespace Fantasy.Entitas return; } - var typeHashCode = EntityTypeHashCache.HashCode; + var type = typeof(T); + var typeHashCode = Scene.EntityComponent.GetHashCode(type); if (!_tree.TryGetValue(typeHashCode, out var component)) { return; } #if FANTASY_NET - if (_treeDb != null && EntitySupportedChecker.IsDataBase) + if (_treeDb != null && SupportedDataBaseChecker.IsSupported) { _treeDb.Remove(component); @@ -598,7 +604,7 @@ namespace Fantasy.Entitas return; } #if FANTASY_NET - if (_multiDb != null && EntitySupportedChecker.IsDataBase) + if (SupportedDataBaseChecker.IsSupported) { _multiDb.Remove(component); if (_multiDb.Count == 0) @@ -662,7 +668,7 @@ namespace Fantasy.Entitas } else if (_tree != null) { - var typeHashCode = component.TypeHashCode; + var typeHashCode = Scene.EntityComponent.GetHashCode(component.Type); if (!_tree.ContainsKey(typeHashCode)) { return; @@ -706,8 +712,14 @@ namespace Fantasy.Entitas { return; } - - if (EntitySupportedChecker.IsMulti) + + if (typeof(T) == typeof(Entity)) + { + Log.Error("Cannot remove a generic Entity type as a component. Specify a more specific type."); + return; + } + + if (SupportedMultiEntityChecker.IsSupported) { if (_multi != null) { @@ -716,7 +728,7 @@ namespace Fantasy.Entitas return; } #if FANTASY_NET - if (EntitySupportedChecker.IsDataBase) + if (SupportedDataBaseChecker.IsSupported) { _multiDb.Remove(component); if (_multiDb.Count == 0) @@ -736,13 +748,13 @@ namespace Fantasy.Entitas } else if (_tree != null) { - var typeHashCode = EntityTypeHashCache.HashCode; + var typeHashCode = Scene.EntityComponent.GetHashCode(typeof(T)); if (!_tree.ContainsKey(typeHashCode)) { return; } #if FANTASY_NET - if (_treeDb != null && EntitySupportedChecker.IsDataBase) + if (_treeDb != null && SupportedDataBaseChecker.IsSupported) { _treeDb.Remove(component); @@ -789,7 +801,7 @@ namespace Fantasy.Entitas { Scene = scene; Type ??= GetType(); - RuntimeId = Scene.RuntimeIdFactory.Create(false); + RuntimeId = Scene.RuntimeIdFactory.Create; if (resetId) { Id = RuntimeId; @@ -802,7 +814,8 @@ namespace Fantasy.Entitas { entity.Parent = this; entity.Type = entity.GetType(); - _tree.Add(TypeHashCache.GetHashCode(entity.Type), entity); + var typeHashCode = Scene.EntityComponent.GetHashCode(entity.Type); + _tree.Add(typeHashCode, entity); entity.Deserialize(scene, resetId); } } @@ -835,7 +848,66 @@ namespace Fantasy.Entitas #endregion #region ForEach - +#if FANTASY_NET + /// + /// 查询当前实体下支持数据库分表存储实体 + /// + [BsonIgnore] + [JsonIgnore] + [IgnoreDataMember] + [ProtoIgnore] + public IEnumerable ForEachSingleCollection + { + get + { + foreach (var (_, treeEntity) in _tree) + { + if (treeEntity is not ISupportedSingleCollection) + { + continue; + } + + yield return treeEntity; + } + } + } + /// + /// 查询当前实体下支持传送实体 + /// + [BsonIgnore] + [JsonIgnore] + [IgnoreDataMember] + [ProtoIgnore] + public IEnumerable ForEachTransfer + { + get + { + if (_tree != null) + { + foreach (var (_, treeEntity) in _tree) + { + if (treeEntity is ISupportedTransfer) + { + yield return treeEntity; + } + } + } + + if (_multiDb != null) + { + foreach (var treeEntity in _multiDb) + { + if (treeEntity is not ISupportedTransfer) + { + continue; + } + + yield return treeEntity; + } + } + } + } +#endif /// /// 查询当前实体下的实现了ISupportedMultiEntity接口的实体 /// @@ -924,6 +996,11 @@ namespace Fantasy.Entitas #if FANTASY_NET if (_treeDb != null) { + foreach (var entity in _treeDb) + { + entity.Dispose(); + } + _treeDb.Clear(); scene.EntityListPool.Return(_treeDb); _treeDb = null; @@ -931,6 +1008,11 @@ namespace Fantasy.Entitas if (_multiDb != null) { + foreach (var entity in _multiDb) + { + entity.Dispose(); + } + _multiDb.Clear(); scene.EntityListPool.Return(_multiDb); _multiDb = null; @@ -948,7 +1030,12 @@ namespace Fantasy.Entitas Scene = null; Parent = null; scene.RemoveEntity(runTimeId); - scene.EntityPool.Return(Type, this); + + if (IsPool()) + { + scene.EntityPool.Return(Type, this); + } + Type = null; } @@ -960,27 +1047,20 @@ namespace Fantasy.Entitas /// 获取一个值,该值指示当前实例是否为对象池中的实例。 /// /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool IsPool() { - return IdFactoryHelper.RuntimeIdTool.GetIsPool(RuntimeId); + return _isPool; } /// /// 设置一个值,该值指示当前实例是否为对象池中的实例。 /// /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void SetIsPool(bool isPool) { } + public void SetIsPool(bool isPool) + { + _isPool = isPool; + } #endregion } - - /// - /// Entity的泛型抽象类,如果使用泛型Entity必须继承这个接口才可以使用 - /// - /// - public abstract partial class Entity : Entity - { - } } \ No newline at end of file diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/EntityReference.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/EntityReference.cs index cc45713..dd1a3fd 100644 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/EntityReference.cs +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/EntityReference.cs @@ -26,11 +26,6 @@ namespace Fantasy.Entitas _runTimeId = t.RuntimeId; } - /// - /// 获取实体引用 - /// - public T Value => _entity?.RuntimeId != _runTimeId ? null : _entity; - /// /// 将一个实体转换为EntityReference /// diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Interface/Supported/EntityInterface.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Interface/Supported/EntityInterface.cs deleted file mode 100644 index daf58c6..0000000 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Interface/Supported/EntityInterface.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; - -namespace Fantasy.Entitas.Interface -{ - /// - /// 支持再一个组件里添加多个同类型组件 - /// - public interface ISupportedMultiEntity : IDisposable - { - } -#if FANTASY_NET - /// - /// Entity支持数据库 - /// -// ReSharper disable once InconsistentNaming - public interface ISupportedDataBase - { - } - - // Entity支持分表存储、保存到数据库的时候不会跟随父组件保存在一个表里、会单独保存在一个表里 - // 需要配合SeparateTableAttribute一起使用、如在Entity类头部定义SeparateTableAttribute(typeOf(Unit), "UnitBag") - // SeparateTableAttribute用来定义这个Entity是属于哪个Entity的子集以及表名 - /// - /// 定义实体支持分表存储的接口。当实体需要单独存储在一个数据库表中,并且在保存到数据库时不会与父实体一起保存在同一个表中时,应实现此接口。 - /// - public interface ISupportedSeparateTable - { - } - - /// - /// Entity支持传送 - /// - public interface ISupportedTransfer - { - } - - // /// - // /// Entity保存到数据库的时候会根据子组件设置分表存储特性分表存储在不同的数据库表中 - // /// - // public interface ISeparateTableRoot - // { - // } - - -#endif -} \ No newline at end of file diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Interface/Supported/EntitySupportedChecker.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Interface/Supported/EntitySupportedChecker.cs deleted file mode 100644 index 0fab3f9..0000000 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Interface/Supported/EntitySupportedChecker.cs +++ /dev/null @@ -1,58 +0,0 @@ -namespace Fantasy.Entitas.Interface -{ - /// - /// 实体接口支持性的编译时检查器。 - /// - /// 要检查的实体类型,必须继承自 - /// - /// 性能优势: - /// - /// 静态字段在每个具体类型实例化时仅初始化一次 - /// JIT编译器会将静态布尔值内联为常量,实现分支消除优化 - /// 避免重复的运行时类型检查开销 - /// 多个相关检查集中在同一个静态类,提高CPU缓存局部性 - /// - /// - public static class EntitySupportedChecker where T : Entity - { - /// - /// 获取实体类型是否实现了 接口。 - /// 实现该接口的实体支持在父实体中添加多个同类型的组件实例。 - /// - /// - /// 如果实体类型实现了 接口,则为 true;否则为 false。 - /// - public static bool IsMulti { get; } -#if FANTASY_NET - /// - /// 获取实体类型是否实现了 接口。 - /// 实现该接口的实体支持数据库持久化存储。 - /// - /// - /// 如果实体类型实现了 接口,则为 true;否则为 false。 - /// - public static bool IsDataBase { get; } - - /// - /// 获取实体类型是否实现了 接口。 - /// 实现该接口的实体支持跨进程传输(如服务器间传送)。 - /// - /// - /// 如果实体类型实现了 接口,则为 true;否则为 false。 - /// - public static bool IsTransfer { get; } -#endif - /// - /// 静态构造函数,在首次访问该泛型类型时执行一次,缓存所有接口检查结果。 - /// - static EntitySupportedChecker() - { - var type = typeof(T); - IsMulti = typeof(ISupportedMultiEntity).IsAssignableFrom(type); -#if FANTASY_NET - IsDataBase = typeof(ISupportedDataBase).IsAssignableFrom(type); - IsTransfer = typeof(ISupportedTransfer).IsAssignableFrom(type); -#endif - } - } -} \ No newline at end of file diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Interface/Supported/ISingleCollectionRoot.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Interface/Supported/ISingleCollectionRoot.cs new file mode 100644 index 0000000..84de7a4 --- /dev/null +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Interface/Supported/ISingleCollectionRoot.cs @@ -0,0 +1,17 @@ +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +namespace Fantasy.Entitas.Interface +{ + /// + /// Entity保存到数据库的时候会根据子组件设置分离存储特性分表存储在不同的集合表中 + /// + public interface ISingleCollectionRoot { } + public static class SingleCollectionRootChecker where T : Entity + { + public static bool IsSupported { get; } + + static SingleCollectionRootChecker() + { + IsSupported = typeof(ISingleCollectionRoot).IsAssignableFrom(typeof(T)); + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Interface/Supported/ISupportedDataBase.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Interface/Supported/ISupportedDataBase.cs new file mode 100644 index 0000000..f505241 --- /dev/null +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Interface/Supported/ISupportedDataBase.cs @@ -0,0 +1,19 @@ +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +namespace Fantasy.Entitas.Interface +{ + /// + /// Entity支持数据库 + /// + // ReSharper disable once InconsistentNaming + public interface ISupportedDataBase { } + + public static class SupportedDataBaseChecker where T : Entity + { + public static bool IsSupported { get; } + + static SupportedDataBaseChecker() + { + IsSupported = typeof(ISupportedDataBase).IsAssignableFrom(typeof(T)); + } + } +} diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Interface/Supported/ISupportedMultiEntity.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Interface/Supported/ISupportedMultiEntity.cs new file mode 100644 index 0000000..ee6aeb7 --- /dev/null +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Interface/Supported/ISupportedMultiEntity.cs @@ -0,0 +1,20 @@ +using System; +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + +namespace Fantasy.Entitas.Interface +{ + /// + /// 支持再一个组件里添加多个同类型组件 + /// + public interface ISupportedMultiEntity : IDisposable { } + + public static class SupportedMultiEntityChecker where T : Entity + { + public static bool IsSupported { get; } + + static SupportedMultiEntityChecker() + { + IsSupported = typeof(ISupportedMultiEntity).IsAssignableFrom(typeof(T)); + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Interface/Supported/ISupportedSingleCollection.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Interface/Supported/ISupportedSingleCollection.cs new file mode 100644 index 0000000..302e697 --- /dev/null +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Interface/Supported/ISupportedSingleCollection.cs @@ -0,0 +1,47 @@ +using System; +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +namespace Fantasy.Entitas.Interface +{ + // Entity是单一集合、保存到数据库的时候不会跟随父组件保存在一个集合里、会单独保存在一个集合里 + // 需要配合SingleCollectionAttribute一起使用、如在Entity类头部定义SingleCollectionAttribute(typeOf(Unit)) + // SingleCollectionAttribute用来定义这个Entity是属于哪个Entity的子集 + /// + /// 定义实体支持单一集合存储的接口。当实体需要单独存储在一个集合中,并且在保存到数据库时不会与父组件一起保存在同一个集合中时,应实现此接口。 + /// + public interface ISupportedSingleCollection { } + public static class SupportedSingleCollectionChecker where T : Entity + { + public static bool IsSupported { get; } + + static SupportedSingleCollectionChecker() + { + IsSupported = typeof(ISupportedSingleCollection).IsAssignableFrom(typeof(T)); + } + } + /// + /// 表示用于指定实体的单一集合存储属性。此属性用于配合 接口使用, + /// 用于定义实体属于哪个父实体的子集合,以及在数据库中使用的集合名称。 + /// + [AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = false)] + public class SingleCollectionAttribute : Attribute + { + /// + /// 获取父实体的类型,指示此实体是属于哪个父实体的子集合。 + /// + public readonly Type RootType; + /// + /// 获取在数据库中使用的集合名称。 + /// + public readonly string CollectionName; + /// + /// 初始化 类的新实例,指定父实体类型和集合名称。 + /// + /// 父实体的类型。 + /// 在数据库中使用的集合名称。 + public SingleCollectionAttribute(Type rootType, string collectionName) + { + RootType = rootType; + CollectionName = collectionName; + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Interface/Supported/ISupportedTransfer.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Interface/Supported/ISupportedTransfer.cs new file mode 100644 index 0000000..a3ae4a9 --- /dev/null +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Interface/Supported/ISupportedTransfer.cs @@ -0,0 +1,19 @@ +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +#if FANTASY_NET +namespace Fantasy.Entitas.Interface +{ + /// + /// Entity支持传送 + /// + public interface ISupportedTransfer { } + public static class SupportedTransferChecker where T : Entity + { + public static bool IsSupported { get; } + + static SupportedTransferChecker() + { + IsSupported = typeof(ISupportedTransfer).IsAssignableFrom(typeof(T)); + } + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Interface/Supported/TypeHashCache.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Interface/Supported/TypeHashCache.cs deleted file mode 100644 index bf94597..0000000 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Interface/Supported/TypeHashCache.cs +++ /dev/null @@ -1,100 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Runtime.CompilerServices; -using Fantasy.Helper; -#pragma warning disable CS8604 // Possible null reference argument. - -namespace Fantasy.Entitas.Interface -{ - /// - /// 实体类型哈希码缓存器。 - /// 提供两种缓存机制: - /// 1. 泛型静态字段缓存(用于泛型方法,零开销) - /// 2. 全局字典缓存(用于非泛型方法,运行时查找) - /// - public static class TypeHashCache - { - /// - /// 全局类型哈希码缓存字典,用于非泛型方法的运行时查找。 - /// 使用 ConcurrentDictionary 保证线程安全。 - /// - private static readonly ConcurrentDictionary RuntimeCache = new(); - - /// - /// 获取指定实体类型的哈希码(运行时查找)。 - /// 首次访问时计算并缓存,后续访问直接返回缓存值。 - /// - /// 实体类型 - /// 实体类型的哈希码 - /// - /// 使用场景:非泛型方法中使用,如 GetComponent(Type type) - /// 性能:首次访问需要计算并插入字典,后续访问为 O(1) 字典查找 - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static long GetHashCode(Type type) - { - return RuntimeCache.GetOrAdd(type.TypeHandle, - static (_, fullName) => HashCodeHelper.ComputeHash64(fullName), - type.FullName); - } - - /// - /// 预热缓存,批量计算并缓存一组类型的哈希码。 - /// - /// 要预热的类型集合 - /// - /// 建议在程序初始化时调用,避免运行时首次查找的计算开销。 - /// - public static void Warmup(IEnumerable types) - { - foreach (var type in types) - { - if (typeof(Entity).IsAssignableFrom(type)) - { - GetHashCode(type); - } - } - } - - /// - /// 清除所有缓存(仅用于热重载场景)。 - /// - internal static void Clear() - { - RuntimeCache.Clear(); - } - } - - /// - /// 实体类型哈希码泛型缓存器。 - /// 通过泛型静态字段缓存每个实体类型的哈希码,实现零开销的类型哈希码访问。 - /// - /// 要缓存哈希码的实体类型,必须继承自 - /// - /// 性能优势: - /// - /// 每个类型的哈希码只计算一次,后续访问直接返回缓存值 - /// JIT编译器会将静态字段访问内联为常量 - /// 无需字典查找,性能远超运行时缓存 - /// 适合在泛型方法中使用,如 GetComponent<T>() - /// - /// - internal static class EntityTypeHashCache where T : Entity - { - /// - /// 获取实体类型 的哈希码。 - /// 该值在首次访问时计算并缓存,后续访问直接返回缓存值。 - /// - /// - /// 实体类型的哈希码,用于在 Entity 的 _tree 字典中快速查找组件。 - /// - public static long HashCode { get; } - - static EntityTypeHashCache() - { - // 直接调用非泛型版本,复用计算逻辑并共享缓存 - HashCode = TypeHashCache.GetHashCode(typeof(T)); - } - } -} diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Interface/System/IAwakeSystem.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Interface/System/IAwakeSystem.cs index 76897b0..f651701 100644 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Interface/System/IAwakeSystem.cs +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Interface/System/IAwakeSystem.cs @@ -3,10 +3,7 @@ using Fantasy.Async; namespace Fantasy.Entitas.Interface { - /// - /// 实体的Awake事件的接口 - /// - public interface IAwakeSystem : IEntitySystem { } + internal interface IAwakeSystem : IEntitiesSystem { } /// /// 实体的Awake事件的抽象接口 /// @@ -17,7 +14,7 @@ namespace Fantasy.Entitas.Interface /// 实体的类型 /// /// - public Type EntityType() => typeof(T); + public Type EntitiesType() => typeof(T); /// /// 事件的抽象方法,需要自己实现这个方法 /// diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Interface/System/ICustomEntitySystem.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Interface/System/ICustomEntitiesSystem.cs similarity index 89% rename from Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Interface/System/ICustomEntitySystem.cs rename to Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Interface/System/ICustomEntitiesSystem.cs index d1a2979..da42e58 100644 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Interface/System/ICustomEntitySystem.cs +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Interface/System/ICustomEntitiesSystem.cs @@ -7,7 +7,7 @@ namespace Fantasy.Entitas.Interface /// 如果需要自定义组件事件系统,请继承此接口。 /// 这个接口内部使用。不对外开放。 /// - internal interface ICustomEntitySystem + internal interface ICustomEntitiesSystem { /// /// 事件类型 @@ -18,7 +18,7 @@ namespace Fantasy.Entitas.Interface /// 实体的类型 /// /// - Type EntityType(); + Type EntitiesType(); /// /// 框架内部调用的触发事件方法 /// @@ -31,7 +31,7 @@ namespace Fantasy.Entitas.Interface /// 如果需要自定义组件事件系统,请继承此抽象类。 /// /// - public abstract class CustomSystem : ICustomEntitySystem where T : Entity + public abstract class CustomSystem : ICustomEntitiesSystem where T : Entity { /// /// 这个1表示是一个自定义事件类型,执行这个事件是时候需要用到这个1. @@ -46,7 +46,7 @@ namespace Fantasy.Entitas.Interface /// 实体的类型 /// /// - public abstract Type EntityType(); + public abstract Type EntitiesType(); /// /// 框架内部调用的触发Awake的方法。 /// diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Interface/System/IDeserializeSystem.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Interface/System/IDeserializeSystem.cs index cbadcef..9c38ab6 100644 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Interface/System/IDeserializeSystem.cs +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Interface/System/IDeserializeSystem.cs @@ -3,10 +3,7 @@ using Fantasy.Async; namespace Fantasy.Entitas.Interface { - /// - /// 实体的反序列化事件的接口 - /// - public interface IDeserializeSystem : IEntitySystem { } + internal interface IDeserializeSystem : IEntitiesSystem { } /// /// 实体的反序列化事件的抽象接口 /// @@ -17,7 +14,7 @@ namespace Fantasy.Entitas.Interface /// 实体的类型 /// /// - public Type EntityType() => typeof(T); + public Type EntitiesType() => typeof(T); /// /// 事件的抽象方法,需要自己实现这个方法 /// diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Interface/System/IDestroySystem.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Interface/System/IDestroySystem.cs index 9e01410..531ebbe 100644 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Interface/System/IDestroySystem.cs +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Interface/System/IDestroySystem.cs @@ -3,10 +3,7 @@ using Fantasy.Async; namespace Fantasy.Entitas.Interface { - /// - /// 实体销毁事件的接口 - /// - public interface IDestroySystem : IEntitySystem { } + internal interface IDestroySystem : IEntitiesSystem { } /// /// 实体销毁事件的抽象接口 /// @@ -17,7 +14,7 @@ namespace Fantasy.Entitas.Interface /// 实体的类型 /// /// - public Type EntityType() => typeof(T); + public Type EntitiesType() => typeof(T); /// /// 事件的抽象方法,需要自己实现这个方法 /// diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Interface/System/IEntitySystem.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Interface/System/IEntitiesSystem.cs similarity index 88% rename from Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Interface/System/IEntitySystem.cs rename to Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Interface/System/IEntitiesSystem.cs index 689fda7..555d21a 100644 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Interface/System/IEntitySystem.cs +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Interface/System/IEntitiesSystem.cs @@ -6,13 +6,13 @@ namespace Fantasy.Entitas.Interface /// /// ECS事件系统的核心接口,任何事件都是要继承这个接口 /// - public interface IEntitySystem + public interface IEntitiesSystem { /// /// 实体的类型 /// /// - Type EntityType(); + Type EntitiesType(); /// /// 框架内部调用的触发事件方法 /// diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Interface/System/ILateUpdateSystem.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Interface/System/ILateUpdateSystem.cs index eb39cb0..34b5f80 100644 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Interface/System/ILateUpdateSystem.cs +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Interface/System/ILateUpdateSystem.cs @@ -3,7 +3,7 @@ using System; namespace Fantasy.Entitas.Interface { - public interface ILateUpdateSystem : IEntitySystem { } + internal interface ILateUpdateSystem : IEntitiesSystem { } /// /// 实体的LateUpdate事件的抽象接口 @@ -15,7 +15,7 @@ namespace Fantasy.Entitas.Interface /// 实体的类型 /// /// - public Type EntityType() => typeof(T); + public Type EntitiesType() => typeof(T); /// /// 事件的抽象方法,需要自己实现这个方法 diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Interface/System/IUpdateSystem.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Interface/System/IUpdateSystem.cs index cd61cb0..4b34ac8 100644 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Interface/System/IUpdateSystem.cs +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Entitas/Interface/System/IUpdateSystem.cs @@ -2,10 +2,7 @@ using System; namespace Fantasy.Entitas.Interface { - /// - /// Update事件的接口 - /// - public interface IUpdateSystem : IEntitySystem { } + internal interface IUpdateSystem : IEntitiesSystem { } /// /// Update事件的抽象接口 /// @@ -16,7 +13,7 @@ namespace Fantasy.Entitas.Interface /// 实体的类型 /// /// - public Type EntityType() => typeof(T); + public Type EntitiesType() => typeof(T); /// /// 事件的抽象方法,需要自己实现这个方法 /// diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Helper/TimeHelper.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Helper/TimeHelper.cs index ae4cbe5..b0eb60a 100644 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Helper/TimeHelper.cs +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Helper/TimeHelper.cs @@ -47,7 +47,7 @@ namespace Fantasy.Helper /// /// 根据时间获取时间戳 /// - public static long Transition(this DateTime dateTime) + public static long Transition(DateTime dateTime) { return (dateTime.ToUniversalTime().Ticks - Epoch) / 10000; } @@ -55,7 +55,7 @@ namespace Fantasy.Helper /// /// 根据时间获取 时间戳 /// - public static long TransitionToSeconds(this DateTime dateTime) + public static long TransitionToSeconds(DateTime dateTime) { return (dateTime.ToUniversalTime().Ticks - Epoch) / 10000000; } diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/IdFactory/Default/EntityIdStruct.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/IdFactory/Default/EntityIdStruct.cs index cee27b4..ef30b18 100644 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/IdFactory/Default/EntityIdStruct.cs +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/IdFactory/Default/EntityIdStruct.cs @@ -114,18 +114,6 @@ namespace Fantasy.IdFactory public sealed class EntityIdFactoryTool : IIdFactoryTool { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool GetIsPool(ref long entityId) - { - throw new NotImplementedException(); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool GetIsPool(long runtimeId) - { - throw new NotImplementedException(); - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public uint GetTime(ref long entityId) { @@ -133,12 +121,6 @@ namespace Fantasy.IdFactory return (uint)(result & EntityIdStruct.MaskTime); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public uint GetTime(long entityId) - { - return GetTime(ref entityId); - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public uint GetSceneId(ref long entityId) { @@ -146,20 +128,9 @@ namespace Fantasy.IdFactory return (uint)(result & EntityIdStruct.MaskSceneId); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public uint GetSceneId(long entityId) - { - return GetSceneId(ref entityId); - } - public byte GetWorldId(ref long entityId) { throw new NotImplementedException(); } - - public byte GetWorldId(long entityId) - { - throw new NotImplementedException(); - } } } \ No newline at end of file diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/IdFactory/Default/RuntimeIdStruct.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/IdFactory/Default/RuntimeIdStruct.cs index f49f42e..a5d5e10 100644 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/IdFactory/Default/RuntimeIdStruct.cs +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/IdFactory/Default/RuntimeIdStruct.cs @@ -12,30 +12,27 @@ namespace Fantasy.IdFactory [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct RuntimeIdStruct { - // RuntimeId: IsPool(1) + time(23) + SceneId(16) + sequence(24) = 64 bits - // +------------------+-------------------+-----------------------------+--------------------------------------+ - // | IsPool(1) 对象池 | time(23) 最大60天 | SceneId(16) 最多65535个Scene | sequence(24) 每秒每个进程能生产16777215个 - // +------------------+-------------------+-----------------------------+--------------------------------------+ + // RuntimeId:23 + 8 + 8 + 25 = 64 + // +-------------------+-----------------------------+--------------------------------------+ + // | time(23) 最大60天 | SceneId(16) 最多65535个Scene | sequence(25) 每秒每个进程能生产33554431个 + // +-------------------+-----------------------------+--------------------------------------+ public uint Time { get; private set; } public uint SceneId { get; private set; } public uint Sequence { get; private set; } - public bool IsPool { get; private set; } - - public const uint MaskSequence = 0xFFFFFF; // 24位 - public const uint MaskSceneId = 0xFFFF; // 16位 - public const uint MaskTime = 0x7FFFFF; // 23位 (最高位留给 IsPool) + public const uint MaskSequence = 0x1FFFFFF; + public const uint MaskSceneId = 0xFFFF; + public const uint MaskTime = 0x7FFFFF; + /// /// RuntimeIdStruct(如果超过下面参数的设定该ID会失效)。 /// - /// /// time不能超过8388607 /// sceneId不能超过65535 - /// sequence不能超过16777215 - public RuntimeIdStruct(bool isPool, uint time, uint sceneId, uint sequence) + /// sequence不能超过33554431 + public RuntimeIdStruct(uint time, uint sceneId, uint sequence) { // 因为都是在配置表里拿到参数、所以这个不做边界判定、能节省一点点性能。 - IsPool = isPool; Time = time; SceneId = sceneId; Sequence = sequence; @@ -43,10 +40,9 @@ namespace Fantasy.IdFactory public static implicit operator long(RuntimeIdStruct runtimeIdStruct) { - ulong result = runtimeIdStruct.Sequence; // 低24位: sequence - result |= (ulong)runtimeIdStruct.SceneId << 24; // 第24-39位: sceneId - result |= (ulong)runtimeIdStruct.Time << 40; // 第40-62位: time - result |= (runtimeIdStruct.IsPool ? 1UL : 0UL) << 63; // 最高位63: isPool + ulong result = runtimeIdStruct.Sequence; + result |= (ulong)runtimeIdStruct.SceneId << 25; + result |= (ulong)runtimeIdStruct.Time << 41; return (long)result; } @@ -55,11 +51,12 @@ namespace Fantasy.IdFactory var result = (ulong)runtimeId; var runtimeIdStruct = new RuntimeIdStruct { - Sequence = (uint)(result & MaskSequence), // 低24位: sequence - SceneId = (uint)((result >> 24) & MaskSceneId), // 第24-39位: sceneId - Time = (uint)((result >> 40) & MaskTime), // 第40-62位: time - IsPool = ((result >> 63) & 1) == 1 // 最高位63: isPool + Sequence = (uint)(result & MaskSequence) }; + result >>= 25; + runtimeIdStruct.SceneId = (byte)(result & MaskSceneId); + result >>= 16; + runtimeIdStruct.Time = (uint)(result & MaskTime); return runtimeIdStruct; } } @@ -94,100 +91,45 @@ namespace Fantasy.IdFactory } } - public long Create(bool isPool) + public long Create { - var time = (uint)((TimeHelper.Now - _epochNow) / 1000); - - if (time > _lastTime) + get { - _lastTime = time; - _lastSequence = 0; - } - else if (++_lastSequence > RuntimeIdStruct.MaskSequence - 1) - { - _lastTime++; - _lastSequence = 0; - } + var time = (uint)((TimeHelper.Now - _epochNow) / 1000); + + if (time > _lastTime) + { + _lastTime = time; + _lastSequence = 0; + } + else if (++_lastSequence > RuntimeIdStruct.MaskSequence - 1) + { + _lastTime++; + _lastSequence = 0; + } - return new RuntimeIdStruct(isPool, time, _sceneId, _lastSequence); + return new RuntimeIdStruct(time, _sceneId, _lastSequence); + } } } public sealed class RuntimeIdFactoryTool : IIdFactoryTool { - /// - /// 获取 RuntimeId 中的 IsPool 标志 - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool GetIsPool(long runtimeId) - { - return GetIsPool(ref runtimeId); - } - - /// - /// 获取 RuntimeId 中的 IsPool 标志 - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool GetIsPool(ref long runtimeId) - { - return (((ulong)runtimeId >> 63) & 1) == 1; // 最高位 - } - - /// - /// 获取 RuntimeId 中的时间部分 - /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public uint GetTime(ref long runtimeId) { - var result = (ulong)runtimeId >> 40; // 右移40位到第40-62位 + var result = (ulong)runtimeId >> 41; return (uint)(result & RuntimeIdStruct.MaskTime); } - - /// - /// 获取 RuntimeId 中的时间部分 - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public uint GetTime(long runtimeId) - { - return GetTime(ref runtimeId); - } - - /// - /// 获取 RuntimeId 中的 SceneId 部分 - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public uint GetSceneId(ref long runtimeId) - { - var result = (ulong)runtimeId >> 24; // 右移24位到第24-39位 - return (uint)(result & RuntimeIdStruct.MaskSceneId); - } - - /// - /// 获取 RuntimeId 中的 SceneId 部分 - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public uint GetSceneId(long runtimeId) - { - return GetSceneId(ref runtimeId); - } - - /// - /// 获取 RuntimeId 中的 Sequence 部分 - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public uint GetSequence(ref long runtimeId) - { - return (uint)((ulong)runtimeId & RuntimeIdStruct.MaskSequence); // 低24位 - } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public byte GetWorldId(ref long entityId) + public uint GetSceneId(ref long runtimeId) { - throw new NotImplementedException(); + var result = (ulong)runtimeId >> 25; + return (uint)(result & RuntimeIdStruct.MaskSceneId); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public byte GetWorldId(long entityId) + public byte GetWorldId(ref long entityId) { throw new NotImplementedException(); } diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/IdFactory/IdFactoryHelper.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/IdFactory/IdFactoryHelper.cs index 1d6e141..c60da0b 100644 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/IdFactory/IdFactoryHelper.cs +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/IdFactory/IdFactoryHelper.cs @@ -118,17 +118,17 @@ namespace Fantasy.IdFactory } } - internal static long RuntimeId(bool isPool, uint time, uint sceneId, byte wordId, uint sequence) + internal static long RuntimeId(uint time, uint sceneId, byte wordId, uint sequence) { switch (_idFactoryType) { case IdFactoryType.Default: { - return new RuntimeIdStruct(isPool, time, sceneId, sequence); + return new RuntimeIdStruct(time, sceneId, sequence); } case IdFactoryType.World: { - return new WorldRuntimeIdStruct(isPool, time, sceneId, wordId, sequence); + return new WorldRuntimeIdStruct(time, sceneId, wordId, sequence); } default: { diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/IdFactory/Interface/IIdFactory.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/IdFactory/Interface/IIdFactory.cs index 8a7af93..d71c15b 100644 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/IdFactory/Interface/IIdFactory.cs +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/IdFactory/Interface/IIdFactory.cs @@ -19,6 +19,6 @@ namespace Fantasy.IdFactory /// /// 创建一个新的Id /// - public long Create(bool isPool); + public long Create { get; } } } \ No newline at end of file diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/IdFactory/Interface/IIdFactoryTool.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/IdFactory/Interface/IIdFactoryTool.cs index 48676fe..8dd1e99 100644 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/IdFactory/Interface/IIdFactoryTool.cs +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/IdFactory/Interface/IIdFactoryTool.cs @@ -5,18 +5,6 @@ namespace Fantasy.IdFactory /// public interface IIdFactoryTool { - /// - /// 获取 RuntimeId 中的 IsPool 标志 - /// - /// - /// - public bool GetIsPool(ref long runtimeId); - /// - /// 获取 RuntimeId 中的 IsPool 标志 - /// - /// - /// - public bool GetIsPool(long runtimeId); /// /// 获得创建时间 /// @@ -24,34 +12,16 @@ namespace Fantasy.IdFactory /// public uint GetTime(ref long entityId); /// - /// 获得创建时间 - /// - /// - /// - public uint GetTime(long entityId); - /// /// 获得SceneId /// /// /// public uint GetSceneId(ref long entityId); /// - /// 获得SceneId - /// - /// - /// - public uint GetSceneId(long entityId); - /// /// 获得WorldId /// /// /// public byte GetWorldId(ref long entityId); - /// - /// 获得WorldId - /// - /// - /// - public byte GetWorldId(long entityId); } } \ No newline at end of file diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/IdFactory/World/WorldEntityIdFactory.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/IdFactory/World/WorldEntityIdFactory.cs index 97241b0..dbba88f 100644 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/IdFactory/World/WorldEntityIdFactory.cs +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/IdFactory/World/WorldEntityIdFactory.cs @@ -127,18 +127,6 @@ namespace Fantasy.IdFactory public sealed class WorldEntityIdFactoryTool : IIdFactoryTool { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool GetIsPool(ref long runtimeId) - { - throw new NotImplementedException(); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool GetIsPool(long runtimeId) - { - throw new NotImplementedException(); - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public uint GetTime(ref long entityId) { @@ -146,12 +134,6 @@ namespace Fantasy.IdFactory return (uint)(result & WorldEntityIdStruct.MaskTime); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public uint GetTime(long entityId) - { - return GetTime(ref entityId); - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public uint GetSceneId(ref long entityId) { @@ -161,23 +143,11 @@ namespace Fantasy.IdFactory return (uint)(result & WorldEntityIdStruct.MaskSceneId) + worldId; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public uint GetSceneId(long entityId) - { - return GetSceneId(ref entityId); - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public byte GetWorldId(ref long entityId) { var result = (ulong)entityId >> 18; return (byte)(result & WorldEntityIdStruct.MaskWordId); } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public byte GetWorldId(long entityId) - { - return GetWorldId(ref entityId); - } } } \ No newline at end of file diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/IdFactory/World/WorldRuntimeIdFactory.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/IdFactory/World/WorldRuntimeIdFactory.cs index 3822d9d..a5d582b 100644 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/IdFactory/World/WorldRuntimeIdFactory.cs +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/IdFactory/World/WorldRuntimeIdFactory.cs @@ -13,33 +13,30 @@ namespace Fantasy.IdFactory [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct WorldRuntimeIdStruct { - // RuntimeId: IsPool(1) + time(23) + SceneId(8) + WordId(8) + sequence(24) = 64 bits - // +------------------+-------------------+--------------------------+-----------------------+--------------------------------------+ - // | IsPool(1) 对象池 | time(23) 最大60天 | SceneId(8) 最多255个Scene | WordId(8) 最多255个世界 | sequence(24) 每秒每个进程能生产16777215个 - // +------------------+-------------------+--------------------------+-----------------------+--------------------------------------+ + // RuntimeId:23 + 8 + 8 + 25 = 64 + // +-------------------+--------------------------+-----------------------+--------------------------------------+ + // | time(23) 最大60天 | SceneId(8) 最多255个Scene | WordId(8) 最多255个世界 | sequence(25) 每秒每个进程能生产33554431个 + // +-------------------+--------------------------+-----------------------+--------------------------------------+ public uint Time { get; private set; } public uint SceneId { get; private set; } public byte WordId { get; private set; } public uint Sequence { get; private set; } - public bool IsPool { get; private set; } - public const uint MaskSequence = 0xFFFFFF; // 24位(从25位减少到24位,为 IsPool 腾出空间) - public const uint MaskSceneId = 0xFF; // 8位 - public const uint MaskWordId = 0xFF; // 8位 - public const uint MaskTime = 0x7FFFFF; // 23位(最高位留给 IsPool) + public const uint MaskSequence = 0x1FFFFFF; + public const uint MaskSceneId = 0xFF; + public const uint MaskWordId = 0xFF; + public const uint MaskTime = 0x7FFFFF; /// /// WorldRuntimeIdStruct(如果超过下面参数的设定该ID会失效)。 /// - /// 是否来自对象池 /// time不能超过8388607 /// sceneId不能超过255 /// wordId不能超过255 - /// sequence不能超过16777215(24位) - public WorldRuntimeIdStruct(bool isPool, uint time, uint sceneId, byte wordId, uint sequence) + /// sequence不能超过33554431 + public WorldRuntimeIdStruct(uint time, uint sceneId, byte wordId, uint sequence) { // 因为都是在配置表里拿到参数、所以这个不做边界判定、能节省一点点性能。 - IsPool = isPool; Time = time; SceneId = sceneId; WordId = wordId; @@ -48,26 +45,26 @@ namespace Fantasy.IdFactory public static implicit operator long(WorldRuntimeIdStruct runtimeIdStruct) { - ulong result = runtimeIdStruct.Sequence; // 低24位: sequence - result |= (ulong)runtimeIdStruct.WordId << 24; // 第24-31位: wordId - result |= (ulong)(runtimeIdStruct.SceneId % (runtimeIdStruct.WordId * 1000)) << 32; // 第32-39位: sceneId - result |= (ulong)runtimeIdStruct.Time << 40; // 第40-62位: time - result |= (runtimeIdStruct.IsPool ? 1UL : 0UL) << 63; // 最高位63: isPool + ulong result = runtimeIdStruct.Sequence; + result |= (ulong)runtimeIdStruct.WordId << 25; + result |= (ulong)(runtimeIdStruct.SceneId % (runtimeIdStruct.WordId * 1000)) << 33; + result |= (ulong)runtimeIdStruct.Time << 41; return (long)result; } public static implicit operator WorldRuntimeIdStruct(long runtimeId) { var result = (ulong)runtimeId; - var wordId = (byte)((result >> 24) & MaskWordId); // 第24-31位: wordId var runtimeIdStruct = new WorldRuntimeIdStruct { - Sequence = (uint)(result & MaskSequence), // 低24位: sequence - WordId = wordId, - SceneId = (uint)((result >> 32) & MaskSceneId) + (uint)wordId * 1000, // 第32-39位: sceneId - Time = (uint)((result >> 40) & MaskTime), // 第40-62位: time - IsPool = ((result >> 63) & 1) == 1 // 最高位63: isPool + Sequence = (uint)(result & MaskSequence) }; + result >>= 25; + runtimeIdStruct.WordId = (byte)(result & MaskWordId); + result >>= 8; + runtimeIdStruct.SceneId = (uint)(result & MaskSceneId) + (uint)runtimeIdStruct.WordId * 1000; + result >>= 8; + runtimeIdStruct.Time = (uint)(result & MaskTime); return runtimeIdStruct; } } @@ -108,120 +105,51 @@ namespace Fantasy.IdFactory } } - public long Create(bool isPool) + public long Create { - var time = (uint)((TimeHelper.Now - _epochNow) / 1000); - - if (time > _lastTime) + get { - _lastTime = time; - _lastSequence = 0; - } - else if (++_lastSequence > WorldRuntimeIdStruct.MaskSequence - 1) - { - _lastTime++; - _lastSequence = 0; - } + var time = (uint)((TimeHelper.Now - _epochNow) / 1000); + + if (time > _lastTime) + { + _lastTime = time; + _lastSequence = 0; + } + else if (++_lastSequence > WorldRuntimeIdStruct.MaskSequence - 1) + { + _lastTime++; + _lastSequence = 0; + } - return new WorldRuntimeIdStruct(isPool, time, _sceneId, _worldId, _lastSequence); + return new WorldRuntimeIdStruct(time, _sceneId, _worldId, _lastSequence); + } } } public sealed class WorldRuntimeIdFactoryTool : IIdFactoryTool { - /// - /// 获取 RuntimeId 中的 IsPool 标志 - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool GetIsPool(long runtimeId) - { - return GetIsPool(ref runtimeId); - } - - /// - /// 获取 RuntimeId 中的 IsPool 标志 - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool GetIsPool(ref long runtimeId) - { - return (((ulong)runtimeId >> 63) & 1) == 1; // 最高位 - } - - /// - /// 获取 RuntimeId 中的时间部分 - /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public uint GetTime(ref long runtimeId) { - var result = (ulong)runtimeId >> 40; // 右移40位到第40-62位 + var result = (ulong)runtimeId >> 41; return (uint)(result & WorldRuntimeIdStruct.MaskTime); } - - /// - /// 获取 RuntimeId 中的时间部分 - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public uint GetTime(long runtimeId) - { - return GetTime(ref runtimeId); - } - - /// - /// 获取 RuntimeId 中的 SceneId 部分 - /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public uint GetSceneId(ref long runtimeId) { - var result = (ulong)runtimeId >> 24; // 右移24位 - var worldId = (uint)(result & WorldRuntimeIdStruct.MaskWordId) * 1000; // 第24-31位: worldId + var result = (ulong)runtimeId >> 25; + var worldId = (uint)(result & WorldRuntimeIdStruct.MaskWordId) * 1000; result >>= 8; - return (uint)(result & WorldRuntimeIdStruct.MaskSceneId) + worldId; // 第32-39位: sceneId + return (uint)(result & WorldRuntimeIdStruct.MaskSceneId) + worldId; } - /// - /// 获取 RuntimeId 中的 SceneId 部分 - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public uint GetSceneId(long runtimeId) - { - return GetSceneId(ref runtimeId); - } - - /// - /// 获取 RuntimeId 中的 WorldId 部分 - /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public byte GetWorldId(ref long runtimeId) { - var result = (ulong)runtimeId >> 24; // 右移24位到第24-31位 + var result = (ulong)runtimeId >> 25; return (byte)(result & WorldRuntimeIdStruct.MaskWordId); } - - /// - /// 获取 RuntimeId 中的 WorldId 部分 - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public byte GetWorldId(long runtimeId) - { - return GetWorldId(ref runtimeId); - } - - /// - /// 获取 RuntimeId 中的 Sequence 部分 - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public uint GetSequence(ref long runtimeId) - { - return (uint)((ulong)runtimeId & WorldRuntimeIdStruct.MaskSequence); // 低24位 - } - - /// - /// 获取 RuntimeId 中的 Sequence 部分 - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public uint GetSequence(long runtimeId) - { - return GetSequence(ref runtimeId); - } } } \ No newline at end of file diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Log/Log.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Log/Log.cs index f90557f..3b9cc15 100644 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Log/Log.cs +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Log/Log.cs @@ -2,8 +2,6 @@ using System; using System.Diagnostics; #if FANTASY_NET using Fantasy.Platform.Net; -// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract -#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. #endif #pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. @@ -16,27 +14,35 @@ namespace Fantasy public static class Log { private static ILog _logCore; - + private static bool _isRegister; +#if FANTASY_NET /// /// 初始化Log系统 /// - public static void Initialize(ILog log = null) + public static void Initialize() { - if (log == null) + if (!_isRegister) { -#if FANTASY_NET - _logCore = new ConsoleLog(); + Register(new ConsoleLog()); + return; + } + + _logCore.Initialize(ProgramDefine.RuntimeMode); + } #endif -#if FANTASY_UNITY - _logCore = new UnityLog(); -#endif + /// + /// 注册一个日志系统 + /// + /// + public static void Register(ILog log) + { + if (_isRegister) + { return; } _logCore = log; -#if FANTASY_NET - _logCore.Initialize(ProgramDefine.RuntimeMode); -#endif + _isRegister = true; } /// diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Addressable/AddressableRouteComponent.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Addressable/AddressableRouteComponent.cs index d31e08f..bf439d7 100644 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Addressable/AddressableRouteComponent.cs +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Addressable/AddressableRouteComponent.cs @@ -57,12 +57,12 @@ namespace Fantasy.Network.Route public NetworkMessagingComponent NetworkMessagingComponent; public MessageDispatcherComponent MessageDispatcherComponent; - internal void Send(T message) where T : IAddressableRouteMessage + internal void Send(IAddressableRouteMessage message) { - Call(message).Coroutine(); + Call(message).Coroutine(); } - internal async FTask Send(Type requestType,APackInfo packInfo) + internal async FTask Send(Type requestType, APackInfo packInfo) { await Call(requestType, packInfo); } @@ -71,7 +71,7 @@ namespace Fantasy.Network.Route { if (IsDisposed) { - return MessageDispatcherComponent.CreateResponse(packInfo.ProtocolCode, InnerErrorCode.ErrNotFoundRoute); + return MessageDispatcherComponent.CreateResponse(requestType, InnerErrorCode.ErrNotFoundRoute); } packInfo.IsDisposed = true; @@ -92,9 +92,10 @@ namespace Fantasy.Network.Route if (AddressableRouteId == 0) { - return MessageDispatcherComponent.CreateResponse(packInfo.ProtocolCode, InnerErrorCode.ErrNotFoundRoute); + return MessageDispatcherComponent.CreateResponse(requestType, + InnerErrorCode.ErrNotFoundRoute); } - + iRouteResponse = await NetworkMessagingComponent.CallInnerRoute(AddressableRouteId, requestType, packInfo); if (runtimeId != RuntimeId) @@ -147,11 +148,11 @@ namespace Fantasy.Network.Route /// 调用可寻址路由消息并等待响应。 /// /// 可寻址路由请求。 - private async FTask Call(T request) where T : IAddressableRouteMessage + private async FTask Call(IAddressableRouteMessage request) { if (IsDisposed) { - return MessageDispatcherComponent.CreateResponse(request.OpCode(), InnerErrorCode.ErrNotFoundRoute); + return MessageDispatcherComponent.CreateResponse(request.GetType(), InnerErrorCode.ErrNotFoundRoute); } var failCount = 0; @@ -168,7 +169,8 @@ namespace Fantasy.Network.Route if (AddressableRouteId == 0) { - return MessageDispatcherComponent.CreateResponse(request.OpCode(), InnerErrorCode.ErrNotFoundRoute); + return MessageDispatcherComponent.CreateResponse(request.GetType(), + InnerErrorCode.ErrNotFoundRoute); } var iRouteResponse = await NetworkMessagingComponent.CallInnerRoute(AddressableRouteId, request); @@ -184,7 +186,8 @@ namespace Fantasy.Network.Route { if (++failCount > 20) { - Log.Error($"AddressableRouteComponent.Call failCount > 20 route send message fail, routeId: {RouteId} AddressableRouteComponent:{Id}"); + Log.Error( + $"AddressableRouteComponent.Call failCount > 20 route send message fail, routeId: {RouteId} AddressableRouteComponent:{Id}"); return iRouteResponse; } diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Addressable/AddressableScene.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Addressable/AddressableScene.cs index 3835476..3105fca 100644 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Addressable/AddressableScene.cs +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Addressable/AddressableScene.cs @@ -24,7 +24,7 @@ namespace Fantasy.Network.Route public AddressableScene(SceneConfig sceneConfig) { Id = IdFactoryHelper.EntityId(0, sceneConfig.Id, (byte)sceneConfig.WorldConfigId, 0); - RunTimeId = IdFactoryHelper.RuntimeId(false,0, sceneConfig.Id, (byte)sceneConfig.WorldConfigId, 0); + RunTimeId = IdFactoryHelper.RuntimeId(0, sceneConfig.Id, (byte)sceneConfig.WorldConfigId, 0); } } } diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Message/Dispatcher/Interface/IRouteMessageHandler.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Message/Dispatcher/Interface/IRouteMessageHandler.cs index dac9693..839cf6b 100644 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Message/Dispatcher/Interface/IRouteMessageHandler.cs +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Message/Dispatcher/Interface/IRouteMessageHandler.cs @@ -235,7 +235,7 @@ namespace Fantasy.Network.Interface } finally { - session.Send(new RouteResponse(), typeof(RouteResponse), rpcId); + session.Send(new RouteResponse(), rpcId); } } diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Message/Dispatcher/MessageDispatcherComponent.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Message/Dispatcher/MessageDispatcherComponent.cs index 44b9ac0..0a24770 100644 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Message/Dispatcher/MessageDispatcherComponent.cs +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Message/Dispatcher/MessageDispatcherComponent.cs @@ -1,416 +1,426 @@ using System; using System.Collections.Generic; +using System.Threading.Tasks; using Fantasy.Assembly; using Fantasy.Async; +using Fantasy.DataStructure.Collection; using Fantasy.DataStructure.Dictionary; using Fantasy.Entitas; using Fantasy.InnerMessage; -// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract -// ReSharper disable ForCanBeConvertedToForeach -// ReSharper disable InvertIf -#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context. -#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. -#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +using Fantasy.Network; +#pragma warning disable CS8604 // Possible null reference argument. + #pragma warning disable CS8602 // Dereference of a possibly null reference. #pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type. -#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member namespace Fantasy.Network.Interface { - public sealed class MessageDispatcherComponent : Entity, IAssemblyLifecycle + /// + /// 用于存储消息处理器的信息,包括类型和对象实例。 + /// + /// 消息处理器的类型 + internal sealed class HandlerInfo { + /// + /// 获取或设置消息处理器对象。 + /// + public T Obj; + /// + /// 获取或设置消息处理器的类型。 + /// + public Type Type; + } + + /// + /// 网络消息分发组件。 + /// + public sealed class MessageDispatcherComponent : Entity, IAssembly + { + public long AssemblyIdentity { get; set; } + private readonly Dictionary _responseTypes = new Dictionary(); + private readonly DoubleMapDictionary _networkProtocols = new DoubleMapDictionary(); + private readonly Dictionary _messageHandlers = new Dictionary(); + private readonly OneToManyList _assemblyResponseTypes = new OneToManyList(); + private readonly OneToManyList _assemblyNetworkProtocols = new OneToManyList(); + private readonly OneToManyList> _assemblyMessageHandlers = new OneToManyList>(); +#if FANTASY_UNITY + private readonly Dictionary _messageDelegateHandlers = new Dictionary(); +#endif +#if FANTASY_NET + private readonly Dictionary _customRouteMap = new Dictionary(); + private readonly OneToManyList _assemblyCustomRouteMap = new OneToManyList(); + private readonly Dictionary _routeMessageHandlers = new Dictionary(); + private readonly OneToManyList> _assemblyRouteMessageHandlers = new OneToManyList>(); +#endif private CoroutineLock _receiveRouteMessageLock; - private readonly HashSet _assemblyManifests = new(); - - private Func? _lastHitGetOpCodeType; - private Func? _lastHitMessageHandler; - - private readonly List _opCodeResolvers = new List(); - private readonly List _responseTypeResolvers = new List(); - private readonly List _messageHandlerResolver = new List(); -#if FANTASY_NET - private Func? _lastHitGetCustomRouteType; - private Func>? _lastHitRouteMessageHandler; - private readonly List _routeMessageHandlerResolver = new List(); - private readonly List _customRouteResolvers = new List(); -#endif - - #region Comparer - - private class MessageHandlerResolverComparer : IComparer - { - public int Compare(IMessageHandlerResolver? x, IMessageHandlerResolver? y) - { - if (x == null || y == null) - { - return 0; - } - - return y.GetMessageHandlerCount().CompareTo(x.GetMessageHandlerCount()); - } - } -#if FANTASY_NET - private class RouteMessageHandlerResolverComparer : IComparer - { - public int Compare(IMessageHandlerResolver? x, IMessageHandlerResolver? y) - { - if (x == null || y == null) - { - return 0; - } - - return y.GetRouteMessageHandlerCount().CompareTo(x.GetRouteMessageHandlerCount()); - } - } - private class RouteTypeResolverComparer : IComparer - { - public int Compare(INetworkProtocolOpCodeResolver? x, INetworkProtocolOpCodeResolver? y) - { - if (x == null || y == null) - { - return 0; - } - - return y.GetCustomRouteTypeCount().CompareTo(x.GetCustomRouteTypeCount()); - } - } -#endif - - private class OpCodeResolverComparer : IComparer - { - public int Compare(INetworkProtocolOpCodeResolver? x, INetworkProtocolOpCodeResolver? y) - { - if (x == null || y == null) - { - return 0; - } - - return y.GetOpCodeCount().CompareTo(x.GetOpCodeCount()); - } - } - - private class ResponseTypeResolverComparer : IComparer - { - public int Compare(INetworkProtocolResponseTypeResolver? x, INetworkProtocolResponseTypeResolver? y) - { - if (x == null || y == null) - { - return 0; - } - - return y.GetRequestCount().CompareTo(x.GetRequestCount()); - } - } - - #endregion - - public override void Dispose() - { - if (IsDisposed) - { - return; - } - _assemblyManifests.Clear(); - _opCodeResolvers.Clear(); - _responseTypeResolvers.Clear(); - _receiveRouteMessageLock.Dispose(); - _messageHandlerResolver.Clear(); -#if FANTASY_NET - _customRouteResolvers.Clear(); - _routeMessageHandlerResolver.Clear(); - _lastHitGetCustomRouteType = null; - _lastHitRouteMessageHandler = null; -#endif - _lastHitGetOpCodeType = null; - _lastHitMessageHandler = null; - _receiveRouteMessageLock = null; - AssemblyLifecycle.Remove(this); - base.Dispose(); - } - - #region AssemblyManifest + #region Initialize internal async FTask Initialize() { _receiveRouteMessageLock = Scene.CoroutineLockComponent.Create(GetType().TypeHandle.Value.ToInt64()); - await AssemblyLifecycle.Add(this); + await AssemblySystem.Register(this); return this; } - public async FTask OnLoad(AssemblyManifest assemblyManifest) + public async FTask Load(long assemblyIdentity) { var tcs = FTask.Create(false); - var assemblyManifestId = assemblyManifest.AssemblyManifestId; Scene?.ThreadSynchronizationContext.Post(() => { - if (_assemblyManifests.Contains(assemblyManifestId)) - { - OnUnLoadInner(assemblyManifest); - } - // 注册Handler - var messageHandlerResolver = assemblyManifest.MessageHandlerResolver; - var messageHandlerCount = messageHandlerResolver.GetMessageHandlerCount(); - if (messageHandlerCount > 0) - { - _messageHandlerResolver.Add(messageHandlerResolver); - _messageHandlerResolver.Sort(new MessageHandlerResolverComparer()); - } - // 注册OpCode - var opCodeResolver = assemblyManifest.NetworkProtocolOpCodeResolver; - var opCodeCount = opCodeResolver.GetOpCodeCount(); - if (opCodeCount > 0) - { - _opCodeResolvers.Add(opCodeResolver); - _opCodeResolvers.Sort(new OpCodeResolverComparer()); - } -#if FANTASY_NET - var routeMessageHandlerCount = messageHandlerResolver.GetRouteMessageHandlerCount(); - if (routeMessageHandlerCount > 0) - { - _routeMessageHandlerResolver.Add(messageHandlerResolver); - _routeMessageHandlerResolver.Sort(new RouteMessageHandlerResolverComparer()); - } - // 注册CustomRouteType - var customRouteTypeCount = opCodeResolver.GetCustomRouteTypeCount(); - if (customRouteTypeCount > 0) - { - _customRouteResolvers.Add(opCodeResolver); - _customRouteResolvers.Sort(new RouteTypeResolverComparer()); - } -#endif - // 注册ResponseType - var responseTypeResolver = assemblyManifest.NetworkProtocolResponseTypeResolver; - var requestCount = responseTypeResolver.GetRequestCount(); - if (requestCount > 0) - { - _responseTypeResolvers.Add(responseTypeResolver); - _responseTypeResolvers.Sort(new ResponseTypeResolverComparer()); - } - _assemblyManifests.Add(assemblyManifestId); + LoadInner(assemblyIdentity); tcs.SetResult(); }); await tcs; } - public async FTask OnUnload(AssemblyManifest assemblyManifest) + private void LoadInner(long assemblyIdentity) + { + // 遍历所有实现了IMessage接口的类型,获取OpCode并添加到_networkProtocols字典中 + foreach (var type in AssemblySystem.ForEach(assemblyIdentity, typeof(IMessage))) + { + var obj = (IMessage) Activator.CreateInstance(type); + var opCode = obj.OpCode(); + + _networkProtocols.Add(opCode, type); + + var responseType = type.GetProperty("ResponseType"); + + // 如果类型具有ResponseType属性,将其添加到_responseTypes字典中 + if (responseType != null) + { + _responseTypes.Add(type, responseType.PropertyType); + _assemblyResponseTypes.Add(assemblyIdentity, type); + } + + _assemblyNetworkProtocols.Add(assemblyIdentity, opCode); + } + + // 遍历所有实现了IMessageHandler接口的类型,创建实例并添加到_messageHandlers字典中 + foreach (var type in AssemblySystem.ForEach(assemblyIdentity, typeof(IMessageHandler))) + { + var obj = (IMessageHandler) Activator.CreateInstance(type); + + if (obj == null) + { + throw new Exception($"message handle {type.Name} is null"); + } + + var key = obj.Type(); + _messageHandlers.Add(key, obj); + _assemblyMessageHandlers.Add(assemblyIdentity, new HandlerInfo() + { + Obj = obj, Type = key + }); + } + + // 如果编译符号FANTASY_NET存在,遍历所有实现了IRouteMessageHandler接口的类型,创建实例并添加到_routeMessageHandlers字典中 +#if FANTASY_NET + foreach (var type in AssemblySystem.ForEach(assemblyIdentity, typeof(IRouteMessageHandler))) + { + var obj = (IRouteMessageHandler) Activator.CreateInstance(type); + + if (obj == null) + { + throw new Exception($"message handle {type.Name} is null"); + } + + var key = obj.Type(); + _routeMessageHandlers.Add(key, obj); + _assemblyRouteMessageHandlers.Add(assemblyIdentity, new HandlerInfo() + { + Obj = obj, Type = key + }); + } + + foreach (var type in AssemblySystem.ForEach(assemblyIdentity, typeof(ICustomRoute))) + { + var obj = (ICustomRoute) Activator.CreateInstance(type); + + if (obj == null) + { + throw new Exception($"message handle {type.Name} is null"); + } + + var opCode = obj.OpCode(); + _customRouteMap[opCode] = obj.RouteType; + _assemblyCustomRouteMap.Add(assemblyIdentity, opCode); + } +#endif + } + + public async FTask ReLoad(long assemblyIdentity) { var tcs = FTask.Create(false); Scene?.ThreadSynchronizationContext.Post(() => { - OnUnLoadInner(assemblyManifest); + OnUnLoadInner(assemblyIdentity); + LoadInner(assemblyIdentity); tcs.SetResult(); }); await tcs; } - private void OnUnLoadInner(AssemblyManifest assemblyManifest) + public async FTask OnUnLoad(long assemblyIdentity) { - _messageHandlerResolver.Remove(assemblyManifest.MessageHandlerResolver); - _opCodeResolvers.Remove(assemblyManifest.NetworkProtocolOpCodeResolver); - _responseTypeResolvers.Remove(assemblyManifest.NetworkProtocolResponseTypeResolver); - _messageHandlerResolver.Sort(new MessageHandlerResolverComparer()); - _opCodeResolvers.Sort(new OpCodeResolverComparer()); - _responseTypeResolvers.Sort(new ResponseTypeResolverComparer()); -#if FANTASY_NET - _routeMessageHandlerResolver.Remove(assemblyManifest.MessageHandlerResolver); - _customRouteResolvers.Remove(assemblyManifest.NetworkProtocolOpCodeResolver); - _routeMessageHandlerResolver.Sort(new RouteMessageHandlerResolverComparer()); - _customRouteResolvers.Sort(new RouteTypeResolverComparer()); -#endif - _assemblyManifests.Remove(assemblyManifest.AssemblyManifestId); + var tcs = FTask.Create(false); + Scene?.ThreadSynchronizationContext.Post(() => + { + OnUnLoadInner(assemblyIdentity); + tcs.SetResult(); + }); + await tcs; } - #endregion - - internal void MessageHandler(Session session, Type type, object message, uint rpcId, uint protocolCode) + private void OnUnLoadInner(long assemblyIdentity) { - if (_lastHitMessageHandler != null && - _lastHitMessageHandler(session, rpcId, protocolCode, message)) + // 移除程序集对应的ResponseType类型和OpCode信息 + if (_assemblyResponseTypes.TryGetValue(assemblyIdentity, out var removeResponseTypes)) + { + foreach (var removeResponseType in removeResponseTypes) + { + _responseTypes.Remove(removeResponseType); + } + + _assemblyResponseTypes.RemoveByKey(assemblyIdentity); + } + + if (_assemblyNetworkProtocols.TryGetValue(assemblyIdentity, out var removeNetworkProtocols)) + { + foreach (var removeNetworkProtocol in removeNetworkProtocols) + { + _networkProtocols.RemoveByKey(removeNetworkProtocol); + } + + _assemblyNetworkProtocols.RemoveByKey(assemblyIdentity); + } + + // 移除程序集对应的消息处理器信息 + if (_assemblyMessageHandlers.TryGetValue(assemblyIdentity, out var removeMessageHandlers)) + { + foreach (var removeMessageHandler in removeMessageHandlers) + { + _messageHandlers.Remove(removeMessageHandler.Type); + } + + _assemblyMessageHandlers.RemoveByKey(assemblyIdentity); + } + + // 如果编译符号FANTASY_NET存在,移除程序集对应的路由消息处理器信息 +#if FANTASY_NET + if (_assemblyRouteMessageHandlers.TryGetValue(assemblyIdentity, out var removeRouteMessageHandlers)) + { + foreach (var removeRouteMessageHandler in removeRouteMessageHandlers) + { + _routeMessageHandlers.Remove(removeRouteMessageHandler.Type); + } + + _assemblyRouteMessageHandlers.RemoveByKey(assemblyIdentity); + } + + if (_assemblyCustomRouteMap.TryGetValue(assemblyIdentity, out var removeCustomRouteMap)) + { + foreach (var removeCustom in removeCustomRouteMap) + { + _customRouteMap.Remove(removeCustom); + } + + _assemblyCustomRouteMap.RemoveByKey(assemblyIdentity); + } +#endif + } + +#if FANTASY_UNITY + /// + /// 手动注册一个消息处理器。 + /// + /// + /// + public void RegisterHandler(MessageDelegate @delegate) where T : IMessage + { + var type = typeof(T); + + if (!_messageDelegateHandlers.TryGetValue(type, out var messageDelegate)) + { + messageDelegate = new MessageDelegateHandler(); + _messageDelegateHandlers.Add(type,messageDelegate); + } + + messageDelegate.Register(@delegate); + } + + /// + /// 手动卸载一个消息处理器,必须是通过RegisterHandler方法注册的消息处理器。 + /// + /// + /// + public void UnRegisterHandler(MessageDelegate @delegate) where T : IMessage + { + var type = typeof(T); + + if (!_messageDelegateHandlers.TryGetValue(type, out var messageDelegate)) + { + return; + } + + if (messageDelegate.UnRegister(@delegate) != 0) { return; } - for (var i = 0; i < _messageHandlerResolver.Count; i++) + _messageDelegateHandlers.Remove(type); + } +#endif + #endregion + + /// + /// 处理普通消息,将消息分发给相应的消息处理器。 + /// + /// 会话对象 + /// 消息类型 + /// 消息对象 + /// RPC标识 + /// 协议码 + public void MessageHandler(Session session, Type type, object message, uint rpcId, uint protocolCode) + { +#if FANTASY_UNITY + if(_messageDelegateHandlers.TryGetValue(type,out var messageDelegateHandler)) { - var resolver = _messageHandlerResolver[i]; - if (resolver.MessageHandler(session, rpcId, protocolCode, message)) - { - _lastHitMessageHandler = resolver.MessageHandler; - return; - } + messageDelegateHandler.Handle(session, message); + return; + } +#endif + if (!_messageHandlers.TryGetValue(type, out var messageHandler)) + { + Log.Warning($"Scene:{session.Scene.Id} Found Unhandled Message: {message.GetType()}"); + return; } - Log.Warning($"Scene:{session.Scene.Id} Found Unhandled Message: {type}"); + // 调用消息处理器的Handle方法并启动协程执行处理逻辑 + messageHandler.Handle(session, rpcId, protocolCode, message).Coroutine(); } + + // 如果编译符号FANTASY_NET存在,定义处理路由消息的方法 #if FANTASY_NET - private async FTask InnerRouteMessageHandler(Session session, Entity entity, uint rpcId, uint protocolCode, object message) + /// + /// 处理路由消息,将消息分发给相应的路由消息处理器。 + /// + /// 会话对象 + /// 消息类型 + /// 实体对象 + /// 消息对象 + /// RPC标识 + public async FTask RouteMessageHandler(Session session, Type type, Entity entity, object message, uint rpcId) { - if (_lastHitRouteMessageHandler != null && - await _lastHitRouteMessageHandler(session, entity, rpcId, protocolCode, message)) + if (!_routeMessageHandlers.TryGetValue(type, out var routeMessageHandler)) { - return true; - } + Log.Warning($"Scene:{session.Scene.Id} Found Unhandled RouteMessage: {message.GetType()}"); - for (var i = 0; i < _routeMessageHandlerResolver.Count; i++) - { - var resolver = _routeMessageHandlerResolver[i]; - if (await resolver.RouteMessageHandler(session, entity, rpcId, protocolCode, message)) + if (message is IRouteRequest request) { - _lastHitRouteMessageHandler = resolver.RouteMessageHandler; - return true; + FailRouteResponse(session, request.GetType(), InnerErrorCode.ErrEntityNotFound, rpcId); } + + return; } + + var runtimeId = entity.RuntimeId; + var sessionRuntimeId = session.RuntimeId; - return false; - } - - internal async FTask RouteMessageHandler(Session session, Type type, Entity entity, object message, uint rpcId, uint protocolCode) - { if (entity is Scene) { // 如果是Scene的话、就不要加锁了、如果加锁很一不小心就可能会造成死锁 - if (!await InnerRouteMessageHandler(session, entity, rpcId, protocolCode, message)) - { - Log.Warning($"Scene:{session.Scene.Id} Found Unhandled RouteMessage: {type}"); - } + await routeMessageHandler.Handle(session, entity, rpcId, message); return; } - // 使用协程锁来确保消息的顺序 - var runtimeId = entity.RuntimeId; - var sessionRuntimeId = session.RuntimeId; + // 使用协程锁来确保多线程安全 using (await _receiveRouteMessageLock.Wait(runtimeId)) { if (sessionRuntimeId != session.RuntimeId) { return; } - + if (runtimeId != entity.RuntimeId) { if (message is IRouteRequest request) { - FailRouteResponse(session, request.OpCode(), InnerErrorCode.ErrEntityNotFound, rpcId); + FailRouteResponse(session, request.GetType(), InnerErrorCode.ErrEntityNotFound, rpcId); } - + return; } - if (!await InnerRouteMessageHandler(session, entity, rpcId, protocolCode, message)) - { - Log.Warning($"Scene:{session.Scene.Id} Found Unhandled RouteMessage: {message.GetType()}"); - } + await routeMessageHandler.Handle(session, entity, rpcId, message); } } -#endif - #region Response - internal void FailRouteResponse(Session session, uint requestOpCode, uint error, uint rpcId) + internal bool GetCustomRouteType(long protocolCode, out int routeType) { - session.Send(CreateRouteResponse(requestOpCode, error, out var responseType), responseType, rpcId); + return _customRouteMap.TryGetValue(protocolCode, out routeType); } - - internal IResponse CreateResponse(uint requestOpCode, uint error) +#endif + internal void FailRouteResponse(Session session, Type requestType, uint error, uint rpcId) + { + var response = CreateRouteResponse(requestType, error); + session.Send(response, rpcId); + } + + internal IResponse CreateResponse(Type requestType, uint error) { IResponse response; - for (var i = 0; i < _responseTypeResolvers.Count; i++) + if (_responseTypes.TryGetValue(requestType, out var responseType)) { - var resolver = _responseTypeResolvers[i]; - var responseType = resolver.GetResponseType(requestOpCode); - if (responseType == null) - { - continue; - } response = (IResponse) Activator.CreateInstance(responseType); - response.ErrorCode = error; - return response; } - - response = new Response(); + else + { + response = new Response(); + } + response.ErrorCode = error; return response; } - - private IRouteResponse CreateRouteResponse(uint requestOpCode, uint error, out Type responseType) + + internal IRouteResponse CreateRouteResponse(Type requestType, uint error) { IRouteResponse response; - for (var i = 0; i < _responseTypeResolvers.Count; i++) + if (_responseTypes.TryGetValue(requestType, out var responseType)) { - var resolver = _responseTypeResolvers[i]; - responseType = resolver.GetResponseType(requestOpCode); - if (responseType == null) - { - continue; - } - - response = (IRouteResponse)Activator.CreateInstance(responseType); - response.ErrorCode = error; - return response; + response = (IRouteResponse) Activator.CreateInstance(responseType); + } + else + { + response = new RouteResponse(); } - responseType = typeof(RouteResponse); - response = new RouteResponse(); response.ErrorCode = error; return response; } - - #endregion - - #region OpCode - - internal Type? GetOpCodeType(uint opCode) + + /// + /// 根据消息类型获取对应的OpCode。 + /// + /// 消息类型 + /// 消息对应的OpCode + public uint GetOpCode(Type type) { - if (_lastHitGetOpCodeType != null ) - { - var opCodeType = _lastHitGetOpCodeType(opCode); - if (opCodeType != null) - { - return opCodeType; - } - } - - for (var i = 0; i < _opCodeResolvers.Count; i++) - { - var resolver = _opCodeResolvers[i]; - var opCodeType = resolver.GetOpCodeType(opCode); - if (opCodeType != null) - { - _lastHitGetOpCodeType = resolver.GetOpCodeType; - return opCodeType; - } - } - - return null; + return _networkProtocols.GetKeyByValue(type); } -#if FANTASY_NET - internal int? GetCustomRouteType(uint protocolCode) + + /// + /// 根据OpCode获取对应的消息类型。 + /// + /// OpCode + /// OpCode对应的消息类型 + public Type GetOpCodeType(uint code) { - if (_lastHitGetCustomRouteType != null) - { - var opCodeType = _lastHitGetCustomRouteType(protocolCode); - if (opCodeType.HasValue) - { - return opCodeType.Value; - } - } - - for (var i = 0; i < _customRouteResolvers.Count; i++) - { - var resolver = _customRouteResolvers[i]; - var opCodeType = resolver.GetCustomRouteType(protocolCode); - if (opCodeType.HasValue) - { - _lastHitGetCustomRouteType = resolver.GetCustomRouteType; - return opCodeType.Value; - } - } - - return null; + return _networkProtocols.GetValueByKey(code); } -#endif - #endregion } } \ No newline at end of file diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Message/IMessage.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Message/IMessage.cs index 67c22cc..aea7d08 100644 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Message/IMessage.cs +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Message/IMessage.cs @@ -1,53 +1,8 @@ using System; -using System.Runtime.Serialization; -using Fantasy.Async; -using Fantasy.Pool; -using Fantasy.Serialize; -using MongoDB.Bson.Serialization.Attributes; -using Newtonsoft.Json; -using ProtoBuf; -#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. - #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member namespace Fantasy.Network.Interface { - public abstract class AMessage : ASerialize, IPool - { -#if FANTASY_NET || FANTASY_UNITY || FANTASY_CONSOLE - [BsonIgnore] - [JsonIgnore] - [IgnoreDataMember] - [ProtoIgnore] - private Scene _scene; - protected Scene GetScene() - { - return _scene; - } - - public void SetScene(Scene scene) - { - _scene = scene; - } -#endif -#if FANTASY_NET - [BsonIgnore] -#endif - [JsonIgnore] - [IgnoreDataMember] - [ProtoIgnore] - private bool _isPool; - - public bool IsPool() - { - return _isPool; - } - - public void SetIsPool(bool isPool) - { - _isPool = isPool; - } - } /// /// 表示通用消息接口。 /// diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Message/InnerMessage.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Message/InnerMessage.cs index f621bcc..643c92b 100644 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Message/InnerMessage.cs +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Message/InnerMessage.cs @@ -21,7 +21,6 @@ namespace Fantasy.InnerMessage return Fantasy.Network.OpCode.BenchmarkMessage; } } - [ProtoContract] public partial class BenchmarkRequest : AMessage, IRequest { diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Message/PacketParser/Handler/BufferPacketParser.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Message/PacketParser/Handler/BufferPacketParser.cs index 427fe97..b1c6c84 100644 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Message/PacketParser/Handler/BufferPacketParser.cs +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Message/PacketParser/Handler/BufferPacketParser.cs @@ -96,9 +96,9 @@ namespace Fantasy.PacketParser return true; } - public override MemoryStreamBuffer Pack(ref uint rpcId, ref long routeId, MemoryStreamBuffer memoryStream, IMessage message, Type messageType) + public override MemoryStreamBuffer Pack(ref uint rpcId, ref long routeId, MemoryStreamBuffer memoryStream, IMessage message) { - return memoryStream == null ? Pack(ref rpcId, ref routeId, message, messageType) : Pack(ref rpcId, ref routeId, memoryStream); + return memoryStream == null ? Pack(ref rpcId, ref routeId, message) : Pack(ref rpcId, ref routeId, memoryStream); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -114,12 +114,12 @@ namespace Fantasy.PacketParser } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private MemoryStreamBuffer Pack(ref uint rpcId, ref long routeId, IMessage message, Type messageType) + private MemoryStreamBuffer Pack(ref uint rpcId, ref long routeId, IMessage message) { var memoryStreamLength = 0; + var messageType = message.GetType(); var memoryStream = Network.MemoryStreamBufferPool.RentMemoryStream(MemoryStreamBufferSource.Pack); - var opCode = message.OpCode(); - OpCodeIdStruct opCodeIdStruct = opCode; + OpCodeIdStruct opCodeIdStruct = message.OpCode(); memoryStream.Seek(Packet.InnerPacketHeadLength, SeekOrigin.Begin); if (SerializerManager.TryGetSerializer(opCodeIdStruct.OpCodeProtocolType, out var serializer)) @@ -131,7 +131,8 @@ namespace Fantasy.PacketParser { Log.Error($"type:{messageType} Does not support processing protocol"); } - + + var opCode = Scene.MessageDispatcherComponent.GetOpCode(messageType); var packetBodyCount = memoryStreamLength - Packet.InnerPacketHeadLength; if (packetBodyCount == 0) @@ -208,9 +209,9 @@ namespace Fantasy.PacketParser return true; } - public override MemoryStreamBuffer Pack(ref uint rpcId, ref long routeId, MemoryStreamBuffer memoryStream, IMessage message, Type messageType) + public override MemoryStreamBuffer Pack(ref uint rpcId, ref long routeId, MemoryStreamBuffer memoryStream, IMessage message) { - return memoryStream == null ? Pack(ref rpcId, message, messageType) : Pack(ref rpcId, memoryStream); + return memoryStream == null ? Pack(ref rpcId, message) : Pack(ref rpcId, memoryStream); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -227,12 +228,12 @@ namespace Fantasy.PacketParser } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private MemoryStreamBuffer Pack(ref uint rpcId, IMessage message, Type messageType) + private MemoryStreamBuffer Pack(ref uint rpcId, IMessage message) { var memoryStreamLength = 0; + var messageType = message.GetType(); var memoryStream = Network.MemoryStreamBufferPool.RentMemoryStream(MemoryStreamBufferSource.Pack); - var opCode = message.OpCode(); - OpCodeIdStruct opCodeIdStruct = opCode; + OpCodeIdStruct opCodeIdStruct = message.OpCode(); memoryStream.Seek(Packet.OuterPacketHeadLength, SeekOrigin.Begin); if (SerializerManager.TryGetSerializer(opCodeIdStruct.OpCodeProtocolType, out var serializer)) @@ -245,8 +246,7 @@ namespace Fantasy.PacketParser Log.Error($"type:{messageType} Does not support processing protocol"); } - - // var opCode = Scene.MessageDispatcherComponent.GetOpCode(messageType); + var opCode = Scene.MessageDispatcherComponent.GetOpCode(messageType); var packetBodyCount = memoryStreamLength - Packet.OuterPacketHeadLength; if (packetBodyCount == 0) @@ -324,9 +324,9 @@ namespace Fantasy.PacketParser return true; } - public override MemoryStreamBuffer Pack(ref uint rpcId, ref long routeId, MemoryStreamBuffer memoryStream, IMessage message, Type messageType) + public override MemoryStreamBuffer Pack(ref uint rpcId, ref long routeId, MemoryStreamBuffer memoryStream, IMessage message) { - return memoryStream == null ? Pack(ref rpcId, message, messageType) : Pack(ref rpcId, memoryStream); + return memoryStream == null ? Pack(ref rpcId, message) : Pack(ref rpcId, memoryStream); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -343,12 +343,12 @@ namespace Fantasy.PacketParser } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private MemoryStreamBuffer Pack(ref uint rpcId, IMessage message, Type messageType) + private MemoryStreamBuffer Pack(ref uint rpcId, IMessage message) { var memoryStreamLength = 0; + var messageType = message.GetType(); var memoryStream = Network.MemoryStreamBufferPool.RentMemoryStream(MemoryStreamBufferSource.UnPack); - var opCode = message.OpCode(); - OpCodeIdStruct opCodeIdStruct = opCode; + OpCodeIdStruct opCodeIdStruct = message.OpCode(); memoryStream.Seek(Packet.OuterPacketHeadLength, SeekOrigin.Begin); if (SerializerManager.TryGetSerializer(opCodeIdStruct.OpCodeProtocolType, out var serializer)) @@ -361,6 +361,7 @@ namespace Fantasy.PacketParser Log.Error($"type:{messageType} Does not support processing protocol"); } + var opCode = Scene.MessageDispatcherComponent.GetOpCode(messageType); var packetBodyCount = memoryStreamLength - Packet.OuterPacketHeadLength; if (packetBodyCount == 0) diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Message/PacketParser/Handler/OuterBufferPacketParserHelper.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Message/PacketParser/Handler/OuterBufferPacketParserHelper.cs index 49b4186..0047c39 100644 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Message/PacketParser/Handler/OuterBufferPacketParserHelper.cs +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Message/PacketParser/Handler/OuterBufferPacketParserHelper.cs @@ -19,18 +19,17 @@ namespace Fantasy.PacketParser /// scene /// 如果是RPC消息需要传递一个rpcId /// 打包的网络消息 - /// 打包的网络消息类型 /// 序列化后流的长度 /// 打包完成会返回一个MemoryStreamBuffer /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static MemoryStreamBuffer Pack(Scene scene, uint rpcId, IMessage message, Type messageType, out int memoryStreamLength) + public static MemoryStreamBuffer Pack(Scene scene, uint rpcId, IMessage message, out int memoryStreamLength) { memoryStreamLength = 0; + var messageType = message.GetType(); var memoryStream = new MemoryStreamBuffer(); memoryStream.MemoryStreamBufferSource = MemoryStreamBufferSource.Pack; - var opCode = message.OpCode(); - OpCodeIdStruct opCodeIdStruct = opCode; + OpCodeIdStruct opCodeIdStruct = message.OpCode(); memoryStream.Seek(Packet.OuterPacketHeadLength, SeekOrigin.Begin); if (SerializerManager.TryGetSerializer(opCodeIdStruct.OpCodeProtocolType, out var serializer)) @@ -42,7 +41,8 @@ namespace Fantasy.PacketParser { Log.Error($"type:{messageType} Does not support processing protocol"); } - ; + + var opCode = scene.MessageDispatcherComponent.GetOpCode(messageType); var packetBodyCount = memoryStreamLength - Packet.OuterPacketHeadLength; if (packetBodyCount == 0) diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Message/PacketParser/Handler/ReadOnlyMemoryPacketParser.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Message/PacketParser/Handler/ReadOnlyMemoryPacketParser.cs index 34e491b..dc2203b 100644 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Message/PacketParser/Handler/ReadOnlyMemoryPacketParser.cs +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Message/PacketParser/Handler/ReadOnlyMemoryPacketParser.cs @@ -143,9 +143,9 @@ namespace Fantasy.PacketParser return false; } - public override MemoryStreamBuffer Pack(ref uint rpcId, ref long routeId, MemoryStreamBuffer memoryStream, IMessage message, Type messageType) + public override MemoryStreamBuffer Pack(ref uint rpcId, ref long routeId, MemoryStreamBuffer memoryStream, IMessage message) { - return memoryStream == null ? Pack(ref rpcId, ref routeId, message, messageType) : Pack(ref rpcId, ref routeId, memoryStream); + return memoryStream == null ? Pack(ref rpcId, ref routeId, message) : Pack(ref rpcId, ref routeId, memoryStream); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -161,12 +161,12 @@ namespace Fantasy.PacketParser } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private MemoryStreamBuffer Pack(ref uint rpcId, ref long routeId, IMessage message, Type messageType) + private MemoryStreamBuffer Pack(ref uint rpcId, ref long routeId, IMessage message) { var memoryStreamLength = 0; + var messageType = message.GetType(); var memoryStream = Network.MemoryStreamBufferPool.RentMemoryStream(MemoryStreamBufferSource.Pack); - var opCode = message.OpCode(); - OpCodeIdStruct opCodeIdStruct = opCode; + OpCodeIdStruct opCodeIdStruct = message.OpCode(); memoryStream.Seek(Packet.InnerPacketHeadLength, SeekOrigin.Begin); if (SerializerManager.TryGetSerializer(opCodeIdStruct.OpCodeProtocolType, out var serializer)) @@ -179,7 +179,7 @@ namespace Fantasy.PacketParser Log.Error($"type:{messageType} Does not support processing protocol"); } - + var opCode = Scene.MessageDispatcherComponent.GetOpCode(messageType); var packetBodyCount = memoryStreamLength - Packet.InnerPacketHeadLength; if (packetBodyCount == 0) @@ -306,9 +306,9 @@ namespace Fantasy.PacketParser return false; } - public override MemoryStreamBuffer Pack(ref uint rpcId, ref long routeId, MemoryStreamBuffer memoryStream, IMessage message, Type messageType) + public override MemoryStreamBuffer Pack(ref uint rpcId, ref long routeId, MemoryStreamBuffer memoryStream, IMessage message) { - return memoryStream == null ? Pack(ref rpcId, message, messageType) : Pack(ref rpcId, memoryStream); + return memoryStream == null ? Pack(ref rpcId, message) : Pack(ref rpcId, memoryStream); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -326,12 +326,12 @@ namespace Fantasy.PacketParser } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private unsafe MemoryStreamBuffer Pack(ref uint rpcId, IMessage message, Type messageType) + private unsafe MemoryStreamBuffer Pack(ref uint rpcId, IMessage message) { var memoryStreamLength = 0; + var messageType = message.GetType(); var memoryStream = Network.MemoryStreamBufferPool.RentMemoryStream(MemoryStreamBufferSource.Pack); - var opCode = message.OpCode(); - OpCodeIdStruct opCodeIdStruct = opCode; + OpCodeIdStruct opCodeIdStruct = message.OpCode(); memoryStream.Seek(Packet.OuterPacketHeadLength, SeekOrigin.Begin); if (SerializerManager.TryGetSerializer(opCodeIdStruct.OpCodeProtocolType, out var serializer)) @@ -344,7 +344,7 @@ namespace Fantasy.PacketParser Log.Error($"type:{messageType} Does not support processing protocol"); } - + var opCode = Scene.MessageDispatcherComponent.GetOpCode(messageType); var packetBodyCount = memoryStreamLength - Packet.OuterPacketHeadLength; if (packetBodyCount == 0) diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Message/PacketParser/Interface/APacketParser.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Message/PacketParser/Interface/APacketParser.cs index 132689b..579cf6f 100644 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Message/PacketParser/Interface/APacketParser.cs +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Message/PacketParser/Interface/APacketParser.cs @@ -19,7 +19,7 @@ namespace Fantasy.PacketParser.Interface internal ANetwork Network; internal MessageDispatcherComponent MessageDispatcherComponent; protected bool IsDisposed { get; private set; } - public abstract MemoryStreamBuffer Pack(ref uint rpcId, ref long routeId, MemoryStreamBuffer memoryStream, IMessage message, Type messageType); + public abstract MemoryStreamBuffer Pack(ref uint rpcId, ref long routeId, MemoryStreamBuffer memoryStream, IMessage message); public virtual void Dispose() { IsDisposed = true; diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Message/PacketParser/OpCode.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Message/PacketParser/OpCode.cs index 1cd325e..27c5dbb 100644 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Message/PacketParser/OpCode.cs +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Message/PacketParser/OpCode.cs @@ -86,43 +86,37 @@ namespace Fantasy.Network public static class OpCode { - // 格式: Index | (OpCodeProtocolType << 23) | (Protocol << 27) - // 所有值已预先计算,使 SourceGenerator 可以在编译时获取这些常量 - - public const uint BenchmarkMessage = 142606335; // Create(ProtoBuf=0, OuterMessage=1, 8388607) - public const uint BenchmarkRequest = 276824063; // Create(ProtoBuf=0, OuterRequest=2, 8388607) - public const uint BenchmarkResponse = 411041791; // Create(ProtoBuf=0, OuterResponse=3, 8388607) - public const uint PingRequest = 4026531841; // Create(ProtoBuf=0, OuterPingRequest=30, 1) - public const uint PingResponse = 4160749569; // Create(ProtoBuf=0, OuterPingResponse=31, 1) - public const uint DefaultResponse = 805306369; // Create(ProtoBuf=0, InnerResponse=6, 1) - public const uint DefaultRouteResponse = 1207959559; // Create(ProtoBuf=0, InnerRouteResponse=9, 7) - public const uint AddressableAddRequest = 1073741825; // Create(ProtoBuf=0, InnerRouteRequest=8, 1) - public const uint AddressableAddResponse = 1207959553; // Create(ProtoBuf=0, InnerRouteResponse=9, 1) - public const uint AddressableGetRequest = 1073741826; // Create(ProtoBuf=0, InnerRouteRequest=8, 2) - public const uint AddressableGetResponse = 1207959554; // Create(ProtoBuf=0, InnerRouteResponse=9, 2) - public const uint AddressableRemoveRequest = 1073741827; // Create(ProtoBuf=0, InnerRouteRequest=8, 3) - public const uint AddressableRemoveResponse = 1207959555; // Create(ProtoBuf=0, InnerRouteResponse=9, 3) - public const uint AddressableLockRequest = 1073741828; // Create(ProtoBuf=0, InnerRouteRequest=8, 4) - public const uint AddressableLockResponse = 1207959556; // Create(ProtoBuf=0, InnerRouteResponse=9, 4) - public const uint AddressableUnLockRequest = 1073741829; // Create(ProtoBuf=0, InnerRouteRequest=8, 5) - public const uint AddressableUnLockResponse = 1207959557; // Create(ProtoBuf=0, InnerRouteResponse=9, 5) - public const uint LinkRoamingRequest = 1073741830; // Create(ProtoBuf=0, InnerRouteRequest=8, 6) - public const uint LinkRoamingResponse = 1207959558; // Create(ProtoBuf=0, InnerRouteResponse=9, 6) - public const uint UnLinkRoamingRequest = 1073741832; // Create(ProtoBuf=0, InnerRouteRequest=8, 8) - public const uint UnLinkRoamingResponse = 1207959560; // Create(ProtoBuf=0, InnerRouteResponse=9, 8) - public const uint LockTerminusIdRequest = 1073741833; // Create(ProtoBuf=0, InnerRouteRequest=8, 9) - public const uint LockTerminusIdResponse = 1207959561; // Create(ProtoBuf=0, InnerRouteResponse=9, 9) - public const uint UnLockTerminusIdRequest = 1073741834; // Create(ProtoBuf=0, InnerRouteRequest=8, 10) - public const uint UnLockTerminusIdResponse = 1207959562; // Create(ProtoBuf=0, InnerRouteResponse=9, 10) - public const uint GetTerminusIdRequest = 1073741835; // Create(ProtoBuf=0, InnerRouteRequest=8, 11) - public const uint GetTerminusIdResponse = 1207959563; // Create(ProtoBuf=0, InnerRouteResponse=9, 11) - - public const uint TransferTerminusRequest = 1082130433; // Create(Bson=1, InnerRouteRequest=8, 1) - public const uint TransferTerminusResponse = 1216348161; // Create(Bson=1, InnerRouteResponse=9, 1) - - /// - /// 创建 OpCode(运行时使用) - /// + public static readonly uint BenchmarkMessage = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.OuterMessage, 8388607); + public static readonly uint BenchmarkRequest = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.OuterRequest, 8388607); + public static readonly uint BenchmarkResponse = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.OuterResponse, 8388607); + public static readonly uint PingRequest = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.OuterPingRequest, 1); + public static readonly uint PingResponse = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.OuterPingResponse, 1); + public static readonly uint DefaultResponse = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerResponse, 1); + public static readonly uint DefaultRouteResponse = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteResponse, 7); + public static readonly uint AddressableAddRequest = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteRequest, 1); + public static readonly uint AddressableAddResponse = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteResponse, 1); + public static readonly uint AddressableGetRequest = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteRequest, 2); + public static readonly uint AddressableGetResponse = Create(OpCodeProtocolType.ProtoBuf,OpCodeType.InnerRouteResponse,2); + public static readonly uint AddressableRemoveRequest = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteRequest, 3); + public static readonly uint AddressableRemoveResponse = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteResponse, 3); + public static readonly uint AddressableLockRequest = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteRequest, 4); + public static readonly uint AddressableLockResponse = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteResponse, 4); + public static readonly uint AddressableUnLockRequest = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteRequest, 5); + public static readonly uint AddressableUnLockResponse = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteResponse, 5); + public static readonly uint LinkRoamingRequest = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteRequest, 6); + public static readonly uint LinkRoamingResponse = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteResponse, 6); + public static readonly uint UnLinkRoamingRequest = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteRequest, 8); + public static readonly uint UnLinkRoamingResponse = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteResponse, 8); + public static readonly uint LockTerminusIdRequest = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteRequest, 9); + public static readonly uint LockTerminusIdResponse = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteResponse, 9); + public static readonly uint UnLockTerminusIdRequest = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteRequest, 10); + public static readonly uint UnLockTerminusIdResponse = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteResponse, 10); + public static readonly uint GetTerminusIdRequest = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteRequest, 11); + public static readonly uint GetTerminusIdResponse = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteResponse, 11); + + public static readonly uint TransferTerminusRequest = Create(OpCodeProtocolType.Bson, OpCodeType.InnerRouteRequest, 1); + public static readonly uint TransferTerminusResponse = Create(OpCodeProtocolType.Bson, OpCodeType.InnerRouteResponse, 1); + public static uint Create(uint opCodeProtocolType, uint protocol, uint index) { return new OpCodeIdStruct(opCodeProtocolType, protocol, index); diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Message/PacketParser/Pack/InnerPackInfo.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Message/PacketParser/Pack/InnerPackInfo.cs index d01538f..e9870e7 100644 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Message/PacketParser/Pack/InnerPackInfo.cs +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Message/PacketParser/Pack/InnerPackInfo.cs @@ -14,7 +14,7 @@ namespace Fantasy.PacketParser { public sealed class InnerPackInfo : APackInfo { - private readonly Dictionary> _createInstances = new Dictionary>(); + private readonly Dictionary> _createInstances = new Dictionary>(); public override void Dispose() { @@ -53,13 +53,13 @@ namespace Fantasy.PacketParser if (MemoryStream.Length == 0) { - if (_createInstances.TryGetValue(messageType.TypeHandle, out var createInstance)) + if (_createInstances.TryGetValue(messageType, out var createInstance)) { return createInstance(); } createInstance = CreateInstance.CreateObject(messageType); - _createInstances.Add(messageType.TypeHandle, createInstance); + _createInstances.Add(messageType, createInstance); return createInstance(); } diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Message/PacketParser/Pack/ProcessPackInfo.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Message/PacketParser/Pack/ProcessPackInfo.cs index adf062a..fb28df6 100644 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Message/PacketParser/Pack/ProcessPackInfo.cs +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Message/PacketParser/Pack/ProcessPackInfo.cs @@ -19,7 +19,7 @@ namespace Fantasy.PacketParser private int _disposeCount; public Type MessageType { get; private set; } private static readonly ConcurrentQueue Caches = new ConcurrentQueue(); - private readonly ConcurrentDictionary> _createInstances = new ConcurrentDictionary>(); + private readonly ConcurrentDictionary> _createInstances = new ConcurrentDictionary>(); public override void Dispose() { @@ -54,8 +54,7 @@ namespace Fantasy.PacketParser packInfo.IsDisposed = false; var memoryStream = new MemoryStreamBuffer(); memoryStream.MemoryStreamBufferSource = MemoryStreamBufferSource.Pack; - var opCode = message.OpCode(); - OpCodeIdStruct opCodeIdStruct = opCode; + OpCodeIdStruct opCodeIdStruct = message.OpCode(); memoryStream.Seek(Packet.InnerPacketHeadLength, SeekOrigin.Begin); if (SerializerManager.TryGetSerializer(opCodeIdStruct.OpCodeProtocolType, out var serializer)) @@ -67,7 +66,8 @@ namespace Fantasy.PacketParser { Log.Error($"type:{type} Does not support processing protocol"); } - + + var opCode = scene.MessageDispatcherComponent.GetOpCode(packInfo.MessageType); var packetBodyCount = memoryStreamLength - Packet.InnerPacketHeadLength; if (packetBodyCount == 0) @@ -128,24 +128,25 @@ namespace Fantasy.PacketParser Log.Debug("Deserialize MemoryStream is null"); return null; } - - var messageTypeTypeHandle = messageType.TypeHandle; + + object obj = null; MemoryStream.Seek(Packet.InnerPacketHeadLength, SeekOrigin.Begin); + if (MemoryStream.Length == 0) { - if (_createInstances.TryGetValue(messageTypeTypeHandle, out var createInstance)) + if (_createInstances.TryGetValue(messageType, out var createInstance)) { return createInstance(); } createInstance = CreateInstance.CreateObject(messageType); - _createInstances.TryAdd(messageTypeTypeHandle, createInstance); + _createInstances.TryAdd(messageType, createInstance); return createInstance(); } if (SerializerManager.TryGetSerializer(OpCodeIdStruct.OpCodeProtocolType, out var serializer)) { - var obj = serializer.Deserialize(messageType, MemoryStream); + obj = serializer.Deserialize(messageType, MemoryStream); MemoryStream.Seek(0, SeekOrigin.Begin); return obj; } diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Message/Scheduler/InnerMessageScheduler.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Message/Scheduler/InnerMessageScheduler.cs index 275ecd3..b1b55f6 100644 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Message/Scheduler/InnerMessageScheduler.cs +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Message/Scheduler/InnerMessageScheduler.cs @@ -6,7 +6,6 @@ using Fantasy.Network; using Fantasy.Network.Interface; using Fantasy.PacketParser; using Fantasy.PacketParser.Interface; -#pragma warning disable CS8602 // Dereference of a possibly null reference. #pragma warning disable CS8604 // Possible null reference argument. #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member @@ -86,12 +85,12 @@ namespace Fantasy.Scheduler if (!Scene.TryGetEntity(packInfo.RouteId, out var entity)) { - Scene.MessageDispatcherComponent.FailRouteResponse(session, packInfo.ProtocolCode, InnerErrorCode.ErrNotFoundRoute, packInfo.RpcId); + Scene.MessageDispatcherComponent.FailRouteResponse(session, messageType, InnerErrorCode.ErrNotFoundRoute, packInfo.RpcId); return; } var obj = packInfo.Deserialize(messageType); - await Scene.MessageDispatcherComponent.RouteMessageHandler(session, messageType, entity, (IMessage)obj, packInfo.RpcId, packInfo.ProtocolCode); + await Scene.MessageDispatcherComponent.RouteMessageHandler(session, messageType, entity, (IMessage)obj, packInfo.RpcId); } return; @@ -111,12 +110,12 @@ namespace Fantasy.Scheduler if (!Scene.TryGetEntity(packInfo.RouteId, out var entity)) { - Scene.MessageDispatcherComponent.FailRouteResponse(session, packInfo.ProtocolCode, InnerErrorCode.ErrNotFoundRoute, packInfo.RpcId); + Scene.MessageDispatcherComponent.FailRouteResponse(session, messageType, InnerErrorCode.ErrNotFoundRoute, packInfo.RpcId); return; } var obj = packInfo.Deserialize(messageType); - await Scene.MessageDispatcherComponent.RouteMessageHandler(session, messageType, entity, (IMessage)obj, packInfo.RpcId, packInfo.ProtocolCode); + await Scene.MessageDispatcherComponent.RouteMessageHandler(session, messageType, entity, (IMessage)obj, packInfo.RpcId); } return; @@ -159,7 +158,7 @@ namespace Fantasy.Scheduler case OpCodeType.OuterAddressableRequest: case OpCodeType.OuterAddressableMessage: { - Scene.MessageDispatcherComponent.FailRouteResponse(session, packInfo.ProtocolCode, InnerErrorCode.ErrNotFoundRoute, packInfo.RpcId); + Scene.MessageDispatcherComponent.FailRouteResponse(session, messageType, InnerErrorCode.ErrNotFoundRoute, packInfo.RpcId); return; } } @@ -191,7 +190,7 @@ namespace Fantasy.Scheduler } var obj = packInfo.Deserialize(messageType); - await Scene.MessageDispatcherComponent.RouteMessageHandler(session, messageType, entity, (IMessage)obj, packInfo.RpcId, packInfo.ProtocolCode); + await Scene.MessageDispatcherComponent.RouteMessageHandler(session, messageType, entity, (IMessage)obj, packInfo.RpcId); } return; diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Message/Scheduler/MessageHelper/NetworkMessagingComponent.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Message/Scheduler/MessageHelper/NetworkMessagingComponent.cs index b176a4a..7580dd9 100644 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Message/Scheduler/MessageHelper/NetworkMessagingComponent.cs +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Message/Scheduler/MessageHelper/NetworkMessagingComponent.cs @@ -71,7 +71,7 @@ namespace Fantasy.Scheduler public readonly SortedDictionary RequestCallback = new(); public readonly Dictionary TimeoutRouteMessageSenders = new(); - public void SendInnerRoute(long routeId, T message) where T : IRouteMessage + public void SendInnerRoute(long routeId, IRouteMessage message) { if (routeId == 0) { @@ -93,7 +93,7 @@ namespace Fantasy.Scheduler Scene.GetSession(routeId).Send(0, routeId, messageType, packInfo); } - public void SendInnerRoute(ICollection routeIdCollection, T message) where T : IRouteMessage + public void SendInnerRoute(ICollection routeIdCollection, IRouteMessage message) { if (routeIdCollection.Count <= 0) { @@ -109,7 +109,7 @@ namespace Fantasy.Scheduler } } - public async FTask SendAddressable(long addressableId, T message) where T : IRouteMessage + public async FTask SendAddressable(long addressableId, IRouteMessage message) { await CallAddressable(addressableId, message); } @@ -121,7 +121,7 @@ namespace Fantasy.Scheduler Log.Error($"CallInnerRoute routeId == 0"); return null; } - + var rpcId = ++_rpcId; var session = Scene.GetSession(routeId); var requestCallback = FTask.Create(false); @@ -130,7 +130,7 @@ namespace Fantasy.Scheduler return await requestCallback; } - public async FTask CallInnerRouteBySession(Session session, long routeId, T request) where T : IRouteMessage + public async FTask CallInnerRouteBySession(Session session, long routeId, IRouteMessage request) { var rpcId = ++_rpcId; var requestCallback = FTask.Create(false); @@ -139,7 +139,7 @@ namespace Fantasy.Scheduler return await requestCallback; } - public async FTask CallInnerRoute(long routeId, T request) where T : IRouteMessage + public async FTask CallInnerRoute(long routeId, IRouteMessage request) { if (routeId == 0) { @@ -151,11 +151,11 @@ namespace Fantasy.Scheduler var session = Scene.GetSession(routeId); var requestCallback = FTask.Create(false); RequestCallback.Add(rpcId, MessageSender.Create(rpcId, request, requestCallback)); - session.Send(request, rpcId, routeId); + session.Send(request, rpcId, routeId); return await requestCallback; } - public async FTask CallAddressable(long addressableId, T request) where T : IRouteMessage + public async FTask CallAddressable(long addressableId, IRouteMessage request) { var failCount = 0; @@ -172,7 +172,7 @@ namespace Fantasy.Scheduler if (addressableRouteId == 0) { - return MessageDispatcherComponent.CreateResponse(request.OpCode(), InnerErrorCode.ErrNotFoundRoute); + return MessageDispatcherComponent.CreateResponse(request.GetType(), InnerErrorCode.ErrNotFoundRoute); } var iRouteResponse = await CallInnerRoute(addressableRouteId, request); @@ -246,8 +246,8 @@ namespace Fantasy.Scheduler } case IRequest iRequest: { + var response = MessageDispatcherComponent.CreateResponse(iRequest.GetType(), InnerErrorCode.ErrRpcFail); var responseRpcId = messageSender.RpcId; - var response = MessageDispatcherComponent.CreateResponse(iRequest.OpCode(), InnerErrorCode.ErrRpcFail); ResponseHandler(responseRpcId, response); Log.Warning($"timeout rpcId:{rpcId} responseRpcId:{responseRpcId} {iRequest.ToJson()}"); break; @@ -256,7 +256,7 @@ namespace Fantasy.Scheduler { Log.Error(messageSender.Request != null ? $"Unsupported protocol type {messageSender.Request.GetType()} rpcId:{rpcId} messageSender.Request != null" - : $"Unsupported protocol type:{messageSender.MessageType} rpcId:{rpcId}"); + : $"Unsupported protocol type:{messageSender.MessageType.FullName} rpcId:{rpcId}"); RequestCallback.Remove(rpcId); break; } diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Message/Scheduler/OuterMessageScheduler.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Message/Scheduler/OuterMessageScheduler.cs index b7a95f2..2c963ff 100644 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Message/Scheduler/OuterMessageScheduler.cs +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Message/Scheduler/OuterMessageScheduler.cs @@ -10,7 +10,6 @@ using Fantasy.PacketParser; using Fantasy.Helper; using Fantasy.InnerMessage; using Fantasy.Roaming; -#pragma warning disable CS8604 // Possible null reference argument. #endif #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member @@ -126,7 +125,7 @@ namespace Fantasy.Scheduler { throw new Exception("OuterMessageScheduler error session does not have an AddressableRouteComponent component"); } - + await addressableRouteComponent.Send(messageType, packInfo); } finally @@ -165,8 +164,7 @@ namespace Fantasy.Scheduler // session可能已经断开了,所以这里需要判断 if (session.RuntimeId == runtimeId) { - var responseType = MessageDispatcherComponent.GetOpCodeType(response.OpCode()); - session.Send(response, responseType, rpcId); + session.Send(response, rpcId); } } finally @@ -186,9 +184,7 @@ namespace Fantasy.Scheduler try { - var routeType = MessageDispatcherComponent.GetCustomRouteType(packInfoProtocolCode); - - if (!routeType.HasValue) + if (!MessageDispatcherComponent.GetCustomRouteType(packInfoProtocolCode, out var routeType)) { throw new Exception($"OuterMessageScheduler error 可能遭受到恶意发包或没有协议定义ProtocolCode ProtocolCode:{packInfo.ProtocolCode}"); } @@ -207,7 +203,7 @@ namespace Fantasy.Scheduler throw new Exception($"OuterMessageScheduler CustomRouteType session does not have an routeComponent component messageType:{messageType.FullName} ProtocolCode:{packInfo.ProtocolCode}"); } - if (!routeComponent.TryGetRouteId(routeType.Value, out var routeId)) + if (!routeComponent.TryGetRouteId(routeType, out var routeId)) { throw new Exception($"OuterMessageScheduler RouteComponent cannot find RouteId with RouteType {routeType}"); } @@ -231,9 +227,7 @@ namespace Fantasy.Scheduler try { - var routeType = MessageDispatcherComponent.GetCustomRouteType(packInfoProtocolCode); - - if (!routeType.HasValue) + if (!MessageDispatcherComponent.GetCustomRouteType(packInfoProtocolCode, out var routeType)) { throw new Exception($"OuterMessageScheduler error 可能遭受到恶意发包或没有协议定义ProtocolCode ProtocolCode:{packInfo.ProtocolCode}"); } @@ -252,7 +246,7 @@ namespace Fantasy.Scheduler throw new Exception("OuterMessageScheduler CustomRouteType session does not have an routeComponent component"); } - if (!routeComponent.TryGetRouteId(routeType.Value, out var routeId)) + if (!routeComponent.TryGetRouteId(routeType, out var routeId)) { throw new Exception($"OuterMessageScheduler RouteComponent cannot find RouteId with RouteType {routeType}"); } @@ -263,8 +257,7 @@ namespace Fantasy.Scheduler // session可能已经断开了,所以这里需要判断 if (session.RuntimeId == runtimeId) { - var responseType = MessageDispatcherComponent.GetOpCodeType(response.OpCode()); - session.Send(response, responseType, rpcId); + session.Send(response, rpcId); } } finally @@ -284,9 +277,7 @@ namespace Fantasy.Scheduler try { - var routeType = MessageDispatcherComponent.GetCustomRouteType(packInfoProtocolCode); - - if (!routeType.HasValue) + if (!MessageDispatcherComponent.GetCustomRouteType(packInfoProtocolCode, out var routeType)) { throw new Exception($"OuterMessageScheduler error 可能遭受到恶意发包或没有协议定义ProtocolCode ProtocolCode:{packInfo.ProtocolCode}"); } @@ -305,7 +296,7 @@ namespace Fantasy.Scheduler throw new Exception($"OuterMessageScheduler Roaming session does not have an sessionRoamingComponent component messageType:{messageType.FullName} ProtocolCode:{packInfo.ProtocolCode}"); } - await sessionRoamingComponent.Send(routeType.Value, messageType, packInfo); + await sessionRoamingComponent.Send(routeType, messageType, packInfo); } finally { @@ -324,9 +315,7 @@ namespace Fantasy.Scheduler try { - var routeType = MessageDispatcherComponent.GetCustomRouteType(packInfoProtocolCode); - - if (!routeType.HasValue) + if (!MessageDispatcherComponent.GetCustomRouteType(packInfoProtocolCode, out var routeType)) { throw new Exception($"OuterMessageScheduler error 可能遭受到恶意发包或没有协议定义ProtocolCode ProtocolCode:{packInfo.ProtocolCode}"); } @@ -347,12 +336,11 @@ namespace Fantasy.Scheduler var rpcId = packInfo.RpcId; var runtimeId = session.RuntimeId; - var response = await sessionRoamingComponent.Call(routeType.Value, messageType, packInfo); + var response = await sessionRoamingComponent.Call(routeType, messageType, packInfo); // session可能已经断开了,所以这里需要判断 if (session.RuntimeId == runtimeId) { - var responseType = MessageDispatcherComponent.GetOpCodeType(response.OpCode()); - session.Send(response, responseType, rpcId); + session.Send(response, rpcId); } } finally diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Protocol/HTTP/HTTPServerNetwork.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Protocol/HTTP/HTTPServerNetwork.cs index 98a3aca..b3806fa 100644 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Protocol/HTTP/HTTPServerNetwork.cs +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Protocol/HTTP/HTTPServerNetwork.cs @@ -67,18 +67,19 @@ namespace Fantasy.Network.HTTP // 注册控制器服务 var addControllers = builder.Services.AddControllers() .AddJsonOptions(options => { options.JsonSerializerOptions.PropertyNamingPolicy = null; }); - foreach (var assemblyManifest in AssemblyManifest.GetAssemblyManifest) + foreach (var assembly in AssemblySystem.ForEachAssembly) { - addControllers.AddApplicationPart(assemblyManifest.Assembly); + addControllers.AddApplicationPart(assembly); } var app = builder.Build(); - var listenUrl = $"http://{bindIp}:{port}/"; + var listenUrl = $"http://{bindIp}:{port}/";; app.Urls.Add(listenUrl); // 启用开发者工具 if (app.Environment.IsDevelopment()) { app.UseDeveloperExceptionPage(); } + // 路由注册 app.MapControllers(); // 开启监听 diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Protocol/Interface/AClientNetwork.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Protocol/Interface/AClientNetwork.cs index 7556ff1..322c811 100644 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Protocol/Interface/AClientNetwork.cs +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Protocol/Interface/AClientNetwork.cs @@ -17,7 +17,7 @@ namespace Fantasy.Network.Interface protected bool IsInit; public Session Session { get; protected set; } public abstract Session Connect(string remoteAddress, Action onConnectComplete, Action onConnectFail, Action onConnectDisconnect, bool isHttps, int connectTimeout = 5000); - public abstract void Send(uint rpcId, long routeId, MemoryStreamBuffer memoryStream, IMessage message, Type messageType); + public abstract void Send(uint rpcId, long routeId, MemoryStreamBuffer memoryStream, IMessage message); public override void Dispose() { IsInit = false; diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Protocol/Interface/ANetworkServerChannel.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Protocol/Interface/ANetworkServerChannel.cs index 1e1bda4..788a235 100644 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Protocol/Interface/ANetworkServerChannel.cs +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Protocol/Interface/ANetworkServerChannel.cs @@ -49,7 +49,7 @@ namespace Fantasy.Network.Interface } } - public abstract void Send(uint rpcId, long routeId, MemoryStreamBuffer memoryStream, IMessage message, Type messageType); + public abstract void Send(uint rpcId, long routeId, MemoryStreamBuffer memoryStream, IMessage message); } } #endif \ No newline at end of file diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Protocol/Interface/INetworkChannel.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Protocol/Interface/INetworkChannel.cs index 2d7e727..52f341a 100644 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Protocol/Interface/INetworkChannel.cs +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Protocol/Interface/INetworkChannel.cs @@ -9,6 +9,6 @@ namespace Fantasy.Network.Interface { public Session Session { get;} public bool IsDisposed { get;} - public void Send(uint rpcId, long routeId, MemoryStreamBuffer memoryStream, IMessage message, Type messageType); + public void Send(uint rpcId, long routeId, MemoryStreamBuffer memoryStream, IMessage message); } } \ No newline at end of file diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Protocol/KCP/Client/KCPClientNetwork.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Protocol/KCP/Client/KCPClientNetwork.cs index ab45e49..f0f8868 100644 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Protocol/KCP/Client/KCPClientNetwork.cs +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Protocol/KCP/Client/KCPClientNetwork.cs @@ -520,14 +520,14 @@ namespace Fantasy.Network.KCP private const byte KcpHeaderRequestConnection = (byte)KcpHeader.RequestConnection; private const byte KcpHeaderConfirmConnection = (byte)KcpHeader.ConfirmConnection; - public override void Send(uint rpcId, long routeId, MemoryStreamBuffer memoryStream, IMessage message, Type messageType) + public override void Send(uint rpcId, long routeId, MemoryStreamBuffer memoryStream, IMessage message) { if (_cancellationTokenSource.IsCancellationRequested) { return; } - - var buffer = _packetParser.Pack(ref rpcId, ref routeId, memoryStream, message, messageType); + + var buffer = _packetParser.Pack(ref rpcId, ref routeId, memoryStream, message); if (!_isConnected) { diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Protocol/KCP/Server/KCPServerNetworkChannel.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Protocol/KCP/Server/KCPServerNetworkChannel.cs index ff85d94..4216fa7 100644 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Protocol/KCP/Server/KCPServerNetworkChannel.cs +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Protocol/KCP/Server/KCPServerNetworkChannel.cs @@ -105,7 +105,7 @@ namespace Fantasy.Network.KCP } } - public override void Send(uint rpcId, long routeId, MemoryStreamBuffer memoryStream, IMessage message, Type messageType) + public override void Send(uint rpcId, long routeId, MemoryStreamBuffer memoryStream, IMessage message) { if (IsDisposed) { @@ -120,7 +120,7 @@ namespace Fantasy.Network.KCP return; } - var buffer = _packetParser.Pack(ref rpcId, ref routeId, memoryStream, message, messageType); + var buffer = _packetParser.Pack(ref rpcId, ref routeId, memoryStream, message); Kcp.Send(buffer.GetBuffer().AsSpan(0, (int)buffer.Position)); if (buffer.MemoryStreamBufferSource == MemoryStreamBufferSource.Pack) diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Protocol/TCP/Client/TCPClientNetwork.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Protocol/TCP/Client/TCPClientNetwork.cs index 3a1df5b..233fb7b 100644 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Protocol/TCP/Client/TCPClientNetwork.cs +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Protocol/TCP/Client/TCPClientNetwork.cs @@ -316,9 +316,9 @@ namespace Fantasy.Network.TCP #region Send - public override void Send(uint rpcId, long routeId, MemoryStreamBuffer memoryStream, IMessage message, Type messageType) + public override void Send(uint rpcId, long routeId, MemoryStreamBuffer memoryStream, IMessage message) { - _sendBuffers.Enqueue(_packetParser.Pack(ref rpcId, ref routeId, memoryStream, message, messageType)); + _sendBuffers.Enqueue(_packetParser.Pack(ref rpcId, ref routeId, memoryStream, message)); if (!_isSending) { diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Protocol/TCP/Server/TCPServerNetworkChannel.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Protocol/TCP/Server/TCPServerNetworkChannel.cs index 654f41a..15f3f66 100644 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Protocol/TCP/Server/TCPServerNetworkChannel.cs +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Protocol/TCP/Server/TCPServerNetworkChannel.cs @@ -211,9 +211,9 @@ namespace Fantasy.Network.TCP #region Send - public override void Send(uint rpcId, long routeId, MemoryStreamBuffer memoryStream, IMessage message, Type messageType) + public override void Send(uint rpcId, long routeId, MemoryStreamBuffer memoryStream, IMessage message) { - _sendBuffers.Enqueue(_packetParser.Pack(ref rpcId, ref routeId, memoryStream, message, messageType)); + _sendBuffers.Enqueue(_packetParser.Pack(ref rpcId, ref routeId, memoryStream, message)); if (!_isSending) { diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Protocol/WebSocket/Client/WebSocketClientNetwork.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Protocol/WebSocket/Client/WebSocketClientNetwork.cs index d6f189f..ef0b78f 100644 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Protocol/WebSocket/Client/WebSocketClientNetwork.cs +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Protocol/WebSocket/Client/WebSocketClientNetwork.cs @@ -289,9 +289,9 @@ namespace Fantasy.Network.WebSocket #region Send - public override void Send(uint rpcId, long routeId, MemoryStreamBuffer memoryStream, IMessage message, Type messageType) + public override void Send(uint rpcId, long routeId, MemoryStreamBuffer memoryStream, IMessage message) { - _sendBuffers.Enqueue(_packetParser.Pack(ref rpcId, ref routeId, memoryStream, message, messageType)); + _sendBuffers.Enqueue(_packetParser.Pack(ref rpcId, ref routeId, memoryStream, message)); if (!_isSending) { diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Protocol/WebSocket/Client/WebSocketClientNetworkWebgl.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Protocol/WebSocket/Client/WebSocketClientNetworkWebgl.cs index c0c5713..36ef84d 100644 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Protocol/WebSocket/Client/WebSocketClientNetworkWebgl.cs +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Protocol/WebSocket/Client/WebSocketClientNetworkWebgl.cs @@ -140,14 +140,14 @@ namespace Fantasy.Network.WebSocket #region Send - public override void Send(uint rpcId, long routeId, MemoryStreamBuffer memoryStream, IMessage message, Type messageType) + public override void Send(uint rpcId, long routeId, MemoryStreamBuffer memoryStream, IMessage message) { if (IsDisposed) { return; } - - var buffer = _packetParser.Pack(ref rpcId, ref routeId, memoryStream, message, messageType); + + var buffer = _packetParser.Pack(ref rpcId, ref routeId, memoryStream, message); if (!_isConnected) { diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Protocol/WebSocket/Server/WebSocketServerNetworkChannel.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Protocol/WebSocket/Server/WebSocketServerNetworkChannel.cs index ae13ce9..040745f 100644 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Protocol/WebSocket/Server/WebSocketServerNetworkChannel.cs +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Protocol/WebSocket/Server/WebSocketServerNetworkChannel.cs @@ -230,9 +230,9 @@ public sealed class WebSocketServerNetworkChannel : ANetworkServerChannel #region Send - public override void Send(uint rpcId, long routeId, MemoryStreamBuffer memoryStream, IMessage message, Type messageType) + public override void Send(uint rpcId, long routeId, MemoryStreamBuffer memoryStream, IMessage message) { - _sendBuffers.Enqueue(_packetParser.Pack(ref rpcId, ref routeId, memoryStream, message, messageType)); + _sendBuffers.Enqueue(_packetParser.Pack(ref rpcId, ref routeId, memoryStream, message)); if (!_isSending) { diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Roaming/Component/SessionRoamingComponent.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Roaming/Component/SessionRoamingComponent.cs index d295b60..6834834 100644 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Roaming/Component/SessionRoamingComponent.cs +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Roaming/Component/SessionRoamingComponent.cs @@ -190,7 +190,7 @@ public sealed class SessionRoamingComponent : Entity /// 发送一个消息给漫游终 /// /// - public void Send(T message) where T : IRoamingMessage + public void Send(IRoamingMessage message) { Call(message.RouteType, message).Coroutine(); } @@ -200,7 +200,7 @@ public sealed class SessionRoamingComponent : Entity /// /// /// - public void Send(int roamingType, T message) where T : IRouteMessage + public void Send(int roamingType, IRouteMessage message) { Call(roamingType, message).Coroutine(); } @@ -210,7 +210,7 @@ public sealed class SessionRoamingComponent : Entity /// /// /// - public async FTask Call(T message) where T : IRoamingMessage + public async FTask Call(IRoamingMessage message) { return await Call(message.RouteType, message); } @@ -221,16 +221,17 @@ public sealed class SessionRoamingComponent : Entity /// /// /// - public async FTask Call(int roamingType, T message) where T : IRouteMessage + public async FTask Call(int roamingType, IRouteMessage message) { if (!_roaming.TryGetValue(roamingType, out var roaming)) { - return MessageDispatcherComponent.CreateResponse(message.OpCode(), InnerErrorCode.ErrNotFoundRoaming); + return MessageDispatcherComponent.CreateResponse(message.GetType(), InnerErrorCode.ErrNotFoundRoaming); } var failCount = 0; var runtimeId = RuntimeId; var routeId = roaming.TerminusId; + var requestType = message.GetType(); IResponse iRouteResponse = null; @@ -245,7 +246,7 @@ public sealed class SessionRoamingComponent : Entity if (routeId == 0) { - return MessageDispatcherComponent.CreateResponse(message.OpCode(), InnerErrorCode.ErrNotFoundRoaming); + return MessageDispatcherComponent.CreateResponse(requestType, InnerErrorCode.ErrNotFoundRoaming); } iRouteResponse = await NetworkMessagingComponent.CallInnerRoute(routeId, message); @@ -305,14 +306,14 @@ public sealed class SessionRoamingComponent : Entity { if (IsDisposed) { - return MessageDispatcherComponent.CreateResponse(packInfo.ProtocolCode, InnerErrorCode.ErrNotFoundRoaming); + return MessageDispatcherComponent.CreateResponse(requestType, InnerErrorCode.ErrNotFoundRoaming); } packInfo.IsDisposed = true; if (!_roaming.TryGetValue(roamingType, out var roaming)) { - return MessageDispatcherComponent.CreateResponse(packInfo.ProtocolCode, InnerErrorCode.ErrNotFoundRoaming); + return MessageDispatcherComponent.CreateResponse(requestType, InnerErrorCode.ErrNotFoundRoaming); } var failCount = 0; @@ -333,7 +334,7 @@ public sealed class SessionRoamingComponent : Entity if (routeId == 0) { - return MessageDispatcherComponent.CreateResponse(packInfo.ProtocolCode, InnerErrorCode.ErrNotFoundRoaming); + return MessageDispatcherComponent.CreateResponse(requestType, InnerErrorCode.ErrNotFoundRoaming); } iRouteResponse = await NetworkMessagingComponent.CallInnerRoute(routeId, requestType, packInfo); diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Roaming/Entity/Terminus.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Roaming/Entity/Terminus.cs index c60bf97..8d4448b 100644 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Roaming/Entity/Terminus.cs +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Roaming/Entity/Terminus.cs @@ -233,7 +233,7 @@ public sealed class Terminus : Entity /// 发送一个消息给客户端 /// /// - public void Send(T message) where T : IRouteMessage + public void Send(IRouteMessage message) { Scene.NetworkMessagingComponent.SendInnerRoute(ForwardSessionRouteId, message); } @@ -242,7 +242,7 @@ public sealed class Terminus : Entity /// /// /// - public void Send(int roamingType, T message) where T : IRoamingMessage + public void Send(int roamingType, IRoamingMessage message) { Call(roamingType, message).Coroutine(); } @@ -253,17 +253,17 @@ public sealed class Terminus : Entity /// /// /// - public async FTask Call(int roamingType, T request) where T : IRoamingMessage + public async FTask Call(int roamingType, IRoamingMessage request) { if (IsDisposed) { - return Scene.MessageDispatcherComponent.CreateResponse(request.OpCode(), InnerErrorCode.ErrNotFoundRoaming); + return Scene.MessageDispatcherComponent.CreateResponse(request.GetType(), InnerErrorCode.ErrNotFoundRoaming); } if (roamingType == RoamingType) { Log.Warning($"Does not support sending messages to the same scene as roamingType currentRoamingType:{RoamingType} roamingType:{roamingType}"); - return Scene.MessageDispatcherComponent.CreateResponse(request.OpCode(), InnerErrorCode.ErrNotFoundRoaming); + return Scene.MessageDispatcherComponent.CreateResponse(request.GetType(), InnerErrorCode.ErrNotFoundRoaming); } var failCount = 0; @@ -285,7 +285,7 @@ public sealed class Terminus : Entity } else { - return Scene.MessageDispatcherComponent.CreateResponse(request.OpCode(), InnerErrorCode.ErrNotFoundRoaming); + return Scene.MessageDispatcherComponent.CreateResponse(request.GetType(), InnerErrorCode.ErrNotFoundRoaming); } } diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Route/RouteComponent.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Route/RouteComponent.cs index 25bdfcc..1ce1fe8 100644 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Route/RouteComponent.cs +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Route/RouteComponent.cs @@ -65,7 +65,7 @@ public sealed class RouteComponent : Entity /// 路由类型。 /// 输出的路由ID。 /// 如果获取成功返回true,否则返回false。 - public bool TryGetRouteId(int routeType, out long routeId) + public bool TryGetRouteId(long routeType, out long routeId) { return RouteAddress.TryGetValue(routeType, out routeId); } diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Session/ProcessSession/ProcessScheduler.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Session/ProcessSession/ProcessScheduler.cs index cfec105..ec7c30c 100644 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Session/ProcessSession/ProcessScheduler.cs +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Session/ProcessSession/ProcessScheduler.cs @@ -46,7 +46,6 @@ internal static class ProcessScheduler throw new Exception($"not found scene routeId:{routeId}"); } - var protocolCode = packInfo.ProtocolCode; var message = packInfo.Deserialize(messageType); scene.ThreadSynchronizationContext.Post(() => @@ -59,7 +58,7 @@ internal static class ProcessScheduler return; } - sceneMessageDispatcherComponent.RouteMessageHandler(session, messageType, entity, message, rpcId, protocolCode).Coroutine(); + sceneMessageDispatcherComponent.RouteMessageHandler(session, messageType, entity, message, rpcId).Coroutine(); }); } @@ -76,7 +75,6 @@ internal static class ProcessScheduler throw new Exception($"not found scene routeId:{routeId}"); } - var protocolCode = packInfo.ProtocolCode; var message = packInfo.Deserialize(messageType); scene.ThreadSynchronizationContext.Post(() => @@ -86,11 +84,11 @@ internal static class ProcessScheduler if (entity == null || entity.IsDisposed) { - sceneMessageDispatcherComponent.FailRouteResponse(session, protocolCode, InnerErrorCode.ErrNotFoundRoute, rpcId); + sceneMessageDispatcherComponent.FailRouteResponse(session, messageType, InnerErrorCode.ErrNotFoundRoute, rpcId); return; } - sceneMessageDispatcherComponent.RouteMessageHandler(session, messageType, entity, message, rpcId, protocolCode).Coroutine(); + sceneMessageDispatcherComponent.RouteMessageHandler(session, messageType, entity, message, rpcId).Coroutine(); }); } @@ -111,8 +109,7 @@ internal static class ProcessScheduler { throw new NotSupportedException($"not found scene routeId = {routeId}"); } - - var protocolCode = packInfo.ProtocolCode; + var message = packInfo.Deserialize(messageType); scene.ThreadSynchronizationContext.Post(() => @@ -121,11 +118,11 @@ internal static class ProcessScheduler if (entity == null || entity.IsDisposed) { - scene.MessageDispatcherComponent.FailRouteResponse(session, protocolCode, InnerErrorCode.ErrNotFoundRoute, rpcId); + scene.MessageDispatcherComponent.FailRouteResponse(session, messageType, InnerErrorCode.ErrNotFoundRoute, rpcId); return; } - - scene.MessageDispatcherComponent.RouteMessageHandler(session, messageType, entity, message, rpcId, protocolCode).Coroutine(); + + scene.MessageDispatcherComponent.RouteMessageHandler(session, messageType, entity, message, rpcId).Coroutine(); }); } return; @@ -142,7 +139,7 @@ internal static class ProcessScheduler public static void Scheduler(this ProcessSession session, Type messageType, uint rpcId, long routeId, uint protocolCode, object message) { OpCodeIdStruct opCodeIdStruct = protocolCode; - + switch (opCodeIdStruct.Protocol) { case OpCodeType.InnerResponse: @@ -185,10 +182,8 @@ internal static class ProcessScheduler { return; } - - sceneMessageDispatcherComponent - .RouteMessageHandler(session, messageType, entity, messageObject, rpcId, protocolCode) - .Coroutine(); + + sceneMessageDispatcherComponent.RouteMessageHandler(session, messageType, entity, messageObject, rpcId).Coroutine(); }); return; @@ -213,13 +208,11 @@ internal static class ProcessScheduler if (entity == null || entity.IsDisposed) { - sceneMessageDispatcherComponent.FailRouteResponse(session, protocolCode, InnerErrorCode.ErrNotFoundRoute, rpcId); + sceneMessageDispatcherComponent.FailRouteResponse(session, message.GetType(), InnerErrorCode.ErrNotFoundRoute, rpcId); return; } - - sceneMessageDispatcherComponent - .RouteMessageHandler(session, messageType, entity, messageObject, rpcId, protocolCode) - .Coroutine(); + + sceneMessageDispatcherComponent.RouteMessageHandler(session, messageType, entity, messageObject, rpcId).Coroutine(); }); return; @@ -253,13 +246,12 @@ internal static class ProcessScheduler case Session gateSession: { // 这里如果是Session只可能是Gate的Session、如果是的话、肯定是转发Address消息 - gateSession.Send((IMessage)messageObject, messageType, rpcId); + gateSession.Send((IMessage)messageObject, rpcId); return; } default: { - scene.MessageDispatcherComponent.RouteMessageHandler(session, messageType, entity, - messageObject, rpcId, protocolCode).Coroutine(); + scene.MessageDispatcherComponent.RouteMessageHandler(session, messageType, entity, messageObject, rpcId).Coroutine(); return; } } diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Session/ProcessSession/ProcessSession.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Session/ProcessSession/ProcessSession.cs index 78ffb61..ecd0bd4 100644 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Session/ProcessSession/ProcessSession.cs +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Session/ProcessSession/ProcessSession.cs @@ -19,16 +19,6 @@ public sealed class ProcessSession : Session { private readonly MemoryStreamBufferPool _memoryStreamBufferPool = new MemoryStreamBufferPool(); private readonly Dictionary> _createInstances = new Dictionary>(); - - public override void Send(IMessage message, Type messageType, uint rpcId = 0, long routeId = 0) - { - if (IsDisposed) - { - return; - } - - this.Scheduler(messageType, rpcId, routeId, message.OpCode(), message); - } /// /// 发送消息到服务器内部。 @@ -36,14 +26,30 @@ public sealed class ProcessSession : Session /// 要发送的消息。 /// RPC 标识符。 /// 路由标识符。 - public override void Send(T message, uint rpcId = 0, long routeId = 0) + public override void Send(IMessage message, uint rpcId = 0, long routeId = 0) { if (IsDisposed) { return; } - this.Scheduler(typeof(T), rpcId, routeId, message.OpCode(), message); + this.Scheduler(message.GetType(), rpcId, routeId, message.OpCode(), message); + } + + /// + /// 发送路由消息到服务器内部。 + /// + /// 要发送的路由消息。 + /// RPC 标识符。 + /// 路由标识符。 + public override void Send(IRouteMessage routeMessage, uint rpcId = 0, long routeId = 0) + { + if (IsDisposed) + { + return; + } + + this.Scheduler(routeMessage.GetType(), rpcId, routeId, routeMessage.OpCode(), routeMessage); } public override void Send(uint rpcId, long routeId, Type messageType, APackInfo packInfo) @@ -66,7 +72,12 @@ public sealed class ProcessSession : Session throw new Exception("The use of this method is not supported"); } - public override FTask Call(T request, long routeId = 0) + public override FTask Call(IRouteRequest request, long routeId = 0) + { + throw new Exception("The use of this method is not supported"); + } + + public override FTask Call(IRequest request, long routeId = 0) { throw new Exception("The use of this method is not supported"); } @@ -74,7 +85,7 @@ public sealed class ProcessSession : Session public object Deserialize(Type messageType, object message, ref OpCodeIdStruct opCodeIdStruct) { var memoryStream = _memoryStreamBufferPool.RentMemoryStream(MemoryStreamBufferSource.None); - + try { if (SerializerManager.TryGetSerializer(opCodeIdStruct.OpCodeProtocolType, out var serializer)) diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Session/Session.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Session/Session.cs index d613de2..02789e7 100644 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Session/Session.cs +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Network/Session/Session.cs @@ -105,7 +105,7 @@ namespace Fantasy.Network return; } - Channel.Send(rpcId, routeId, packInfo.MemoryStream, null, messageType); + Channel.Send(rpcId, routeId, packInfo.MemoryStream, null); } /// @@ -123,7 +123,7 @@ namespace Fantasy.Network using (packInfo) { - Channel.Send(rpcId, routeId, packInfo.MemoryStream, null, packInfo.MessageType); + Channel.Send(rpcId, routeId, packInfo.MemoryStream, null); } } @@ -140,7 +140,7 @@ namespace Fantasy.Network return; } - Channel.Send(rpcId, routeId, memoryStream, null, null); + Channel.Send(rpcId, routeId, memoryStream, null); } #endif /// @@ -174,16 +174,6 @@ namespace Fantasy.Network RequestCallback.Clear(); OnDispose?.Invoke(); } - - public virtual void Send(IMessage message, Type messageType, uint rpcId = 0, long routeId = 0) - { - if (IsDisposed) - { - return; - } - - Channel.Send(rpcId, routeId, null, message, messageType); - } /// /// 发送一个消息 @@ -191,23 +181,39 @@ namespace Fantasy.Network /// 消息的实例 /// 如果是RPC消息需要传递一个RPCId /// routeId - public virtual void Send(T message, uint rpcId = 0, long routeId = 0) where T : IMessage + public virtual void Send(IMessage message, uint rpcId = 0, long routeId = 0) + { + if (IsDisposed) + { + return; + } + + Channel.Send(rpcId, routeId, null, message); + } + + /// + /// 发送一个消息 + /// + /// 消息的实例,不同的是这个是发送Route消息使用的 + /// 如果是RPC消息需要传递一个RPCId + /// routeId + public virtual void Send(IRouteMessage routeMessage, uint rpcId = 0, long routeId = 0) { if (IsDisposed) { return; } - Channel.Send(rpcId, routeId, null, message, typeof(T)); + Channel.Send(rpcId, routeId, null, routeMessage); } /// /// 发送一个RPC消息 /// - /// 请求消息的实例 + /// 请求Route消息的实例 /// routeId /// - public virtual FTask Call(T request, long routeId = 0) where T : IRequest + public virtual FTask Call(IRouteRequest request, long routeId = 0) { if (IsDisposed) { @@ -217,7 +223,27 @@ namespace Fantasy.Network var requestCallback = FTask.Create(); var rpcId = ++_rpcId; RequestCallback.Add(rpcId, requestCallback); - Send(request, rpcId, routeId); + Send(request, rpcId, routeId); + return requestCallback; + } + + /// + /// 发送一个RPC消息 + /// + /// 请求消息的实例 + /// routeId + /// + public virtual FTask Call(IRequest request, long routeId = 0) + { + if (IsDisposed) + { + return null; + } + + var requestCallback = FTask.Create(); + var rpcId = ++_rpcId; + RequestCallback.Add(rpcId, requestCallback); + Send(request, rpcId, routeId); return requestCallback; } diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Platform/Net/ConfigTable/SceneConfig.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Platform/Net/ConfigTable/SceneConfig.cs index 1106312..14f8a1a 100644 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Platform/Net/ConfigTable/SceneConfig.cs +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Platform/Net/ConfigTable/SceneConfig.cs @@ -209,7 +209,7 @@ namespace Fantasy.Platform.Net /// public void Initialize() { - RouteId = IdFactoryHelper.RuntimeId(false, 0, Id, (byte)WorldConfigId, 0); + RouteId = IdFactoryHelper.RuntimeId(0, Id, (byte)WorldConfigId, 0); } } } diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Platform/Net/Entry.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Platform/Net/Entry.cs index 4e5bfa9..dff5316 100644 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Platform/Net/Entry.cs +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Platform/Net/Entry.cs @@ -6,7 +6,6 @@ using Fantasy.Helper; using Fantasy.Network; using Fantasy.Serialize; // ReSharper disable FunctionNeverReturns -#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. namespace Fantasy.Platform.Net; @@ -17,13 +16,68 @@ namespace Fantasy.Platform.Net; /// 不支持的 ProcessType 类型异常。 public static class Entry { + /// + /// 框架初始化 + /// + /// 日志实例 + /// 注册的Assembly + /// + public static async FTask Initialize(ILog? log, params System.Reflection.Assembly[] assemblies) + { + // 注册日志模块到框架 + if (log != null) + { + Log.Register(log); + } + Log.Info($"Fantasy Version:{ProgramDefine.VERSION}"); + // 加载Fantasy.config配置文件 + await ConfigLoader.InitializeFromXml(Path.Combine(AppContext.BaseDirectory, "Fantasy.config")); + // 解析命令行参数 + Parser.Default.ParseArguments(Environment.GetCommandLineArgs()) + .WithNotParsed(error => throw new Exception("Command line format error!")) + .WithParsed(option => + { + ProgramDefine.ProcessId = option.ProcessId; + ProgramDefine.ProcessType = option.ProcessType; + ProgramDefine.RuntimeMode = Enum.Parse(option.RuntimeMode); + ProgramDefine.StartupGroup = option.StartupGroup; + }); + // 初始化Log系统 + Log.Initialize(); + // 检查启动参数,后期可能有机器人等不同的启动参数 + switch (ProgramDefine.ProcessType) + { + case "Game": + { + break; + } + default: + { + throw new NotSupportedException($"ProcessType is {ProgramDefine.ProcessType} Unrecognized!"); + } + } + // 初始化程序集管理系统 + await AssemblySystem.InnerInitialize(assemblies); + // 初始化序列化 + SerializerManager.Initialize(); + // 精度处理(只针对Windows下有作用、其他系统没有这个问题、一般也不会用Windows来做服务器的) + WinPeriod.Initialize(); + } + + /// + /// 框架初始化 + /// + /// 注册的Assembly + public static FTask Initialize(params System.Reflection.Assembly[] assemblies) + { + return Initialize(null, assemblies); + } + /// /// 启动Fantasy.Net /// - public static async FTask Start(ILog log = null) + public static async FTask Start() { - // 初始化 - await Initialize(log); // 启动Process StartProcess().Coroutine(); await FTask.CompletedTask; @@ -33,7 +87,17 @@ public static class Entry Thread.Sleep(1); } } - + + /// + /// 初始化并且启动框架 + /// + /// + public static async FTask Start(params System.Reflection.Assembly[] assemblies) + { + await Initialize(assemblies); + await Start(); + } + private static async FTask StartProcess() { if (ProgramDefine.StartupGroup != 0) @@ -65,52 +129,12 @@ public static class Entry } } - /// - /// 框架初始化 - /// - /// 日志实例 - /// - private static async FTask Initialize(ILog log = null) - { - // 初始化Log系统 - Log.Initialize(log); - Log.Info($"Fantasy Version:{ProgramDefine.VERSION}"); - // 加载Fantasy.config配置文件 - await ConfigLoader.InitializeFromXml(Path.Combine(AppContext.BaseDirectory, "Fantasy.config")); - // 解析命令行参数 - Parser.Default.ParseArguments(Environment.GetCommandLineArgs()) - .WithNotParsed(error => throw new Exception("Command line format error!")) - .WithParsed(option => - { - ProgramDefine.ProcessId = option.ProcessId; - ProgramDefine.ProcessType = option.ProcessType; - ProgramDefine.RuntimeMode = Enum.Parse(option.RuntimeMode); - ProgramDefine.StartupGroup = option.StartupGroup; - }); - // 检查启动参数,后期可能有机器人等不同的启动参数 - switch (ProgramDefine.ProcessType) - { - case "Game": - { - break; - } - default: - { - throw new NotSupportedException($"ProcessType is {ProgramDefine.ProcessType} Unrecognized!"); - } - } - // 初始化序列化 - await SerializerManager.Initialize(); - // 精度处理(只针对Windows下有作用、其他系统没有这个问题、一般也不会用Windows来做服务器的) - WinPeriod.Initialize(); - } - /// /// 关闭 Fantasy /// public static void Close() { - AssemblyManifest.Dispose().Coroutine(); + AssemblySystem.Dispose(); SerializerManager.Dispose(); } } diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Platform/Unity/Entry.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Platform/Unity/Entry.cs index 50e9275..f0fa9e6 100644 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Platform/Unity/Entry.cs +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Platform/Unity/Entry.cs @@ -43,19 +43,21 @@ namespace Fantasy.Platform.Unity /// /// 初始化框架 /// - public static async FTask Initialize() + /// + public static async FTask Initialize(params System.Reflection.Assembly[] assemblies) { if (_isInit) { Log.Error("Fantasy has already been initialized and does not need to be initialized again!"); return; } - Log.Initialize(new UnityLog()); FantasyObject.OnRuntimeMethodLoad(); + Log.Register(new UnityLog()); ProgramDefine.MaxMessageSize = ushort.MaxValue * 16; Log.Info($"Fantasy Version:{ProgramDefine.VERSION}"); + await AssemblySystem.InnerInitialize(assemblies); // 初始化序列化 - await SerializerManager.Initialize(); + SerializerManager.Initialize(); #if FANTASY_WEBGL ThreadSynchronizationContext.Initialize(); #endif @@ -94,8 +96,8 @@ namespace Fantasy.Platform.Unity private void OnDestroy() { + AssemblySystem.Dispose(); SerializerManager.Dispose(); - AssemblyManifest.Dispose().Coroutine(); if (Scene != null) { Scene?.Dispose(); diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Pool/Normal/PoolCore.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Pool/Normal/PoolCore.cs index c7983aa..94144f1 100644 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Pool/Normal/PoolCore.cs +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Pool/Normal/PoolCore.cs @@ -20,8 +20,8 @@ namespace Fantasy.Pool /// 池子里可用的数量 /// public int Count => _poolQueue.Count; - private readonly OneToManyQueue _poolQueue = new OneToManyQueue(); - private readonly Dictionary> _typeCheckCache = new Dictionary>(); + private readonly OneToManyQueue _poolQueue = new OneToManyQueue(); + private readonly Dictionary> _typeCheckCache = new Dictionary>(); /// /// 构造函数 @@ -39,7 +39,7 @@ namespace Fantasy.Pool /// public T Rent() where T : IPool, new() { - if (!_poolQueue.TryDequeue(typeof(T).TypeHandle, out var queue)) + if (!_poolQueue.TryDequeue(typeof(T), out var queue)) { queue = new T(); } @@ -57,10 +57,9 @@ namespace Fantasy.Pool /// public IPool Rent(Type type) { - var runtimeTypeHandle = type.TypeHandle; - if (!_poolQueue.TryDequeue(runtimeTypeHandle, out var queue)) + if (!_poolQueue.TryDequeue(type, out var queue)) { - if (!_typeCheckCache.TryGetValue(runtimeTypeHandle, out var createInstance)) + if (!_typeCheckCache.TryGetValue(type, out var createInstance)) { if (!typeof(IPool).IsAssignableFrom(type)) { @@ -69,7 +68,7 @@ namespace Fantasy.Pool else { createInstance = CreateInstance.CreateIPool(type); - _typeCheckCache[runtimeTypeHandle] = createInstance; + _typeCheckCache[type] = createInstance; } } @@ -107,7 +106,7 @@ namespace Fantasy.Pool _poolCount++; obj.SetIsPool(false); - _poolQueue.Enqueue(type.TypeHandle, obj); + _poolQueue.Enqueue(type, obj); } /// diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Pool/PoolHelper.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Pool/PoolHelper.cs index f2bf670..d9214fd 100644 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Pool/PoolHelper.cs +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Pool/PoolHelper.cs @@ -1,6 +1,5 @@ using System; using System.Reflection.Emit; -using Fantasy.Network.Interface; using Fantasy.Serialize; #pragma warning disable CS8604 // Possible null reference argument. diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Scene/Scene.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Scene/Scene.cs index f0a07e6..15ced7e 100644 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Scene/Scene.cs +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Scene/Scene.cs @@ -13,11 +13,10 @@ using Fantasy.Timer; #if FANTASY_NET using Fantasy.DataBase; using Fantasy.Platform.Net; -// using Fantasy.SingleCollection; +using Fantasy.SingleCollection; using System.Runtime.CompilerServices; using Fantasy.Network.Route; using Fantasy.Network.Roaming; -using Fantasy.SeparateTable; #endif // ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract #pragma warning disable CS8601 // Possible null reference assignment. @@ -87,7 +86,7 @@ namespace Fantasy /// 当前Scene的下创建的Entity /// private readonly Dictionary _entities = new Dictionary(); - internal readonly Dictionary> TypeInstance = new Dictionary>(); + internal readonly Dictionary> TypeInstance = new Dictionary>(); #endregion #region IdFactory @@ -141,7 +140,7 @@ namespace Fantasy /// /// Scene下的Entity分表组件 /// - public SeparateTableComponent SeparateTableComponent { get; internal set; } + public SingleCollectionComponent SingleCollectionComponent { get; internal set; } /// /// Scene下的内网消息发送组件 /// @@ -176,7 +175,7 @@ namespace Fantasy MessageDispatcherComponent = await Create(this, false, true).Initialize(); #if FANTASY_NET NetworkMessagingComponent = Create(this, false, true); - SeparateTableComponent = await Create(this, false, true).Initialize(); + SingleCollectionComponent = await Create(this, false, true).Initialize(); TerminusComponent = Create(this, false, true); RoamingComponent = Create(this, false, true).Initialize(); #endif @@ -263,7 +262,7 @@ namespace Fantasy Process = null; SceneType = 0; SceneConfigId = 0; - SeparateTableComponent = null; + SingleCollectionComponent = null; NetworkMessagingComponent = null; TerminusComponent = null; RoamingComponent = null; @@ -341,7 +340,7 @@ namespace Fantasy scene.EntityIdFactory = IdFactoryHelper.EntityIdFactory(sceneId, world); scene.RuntimeIdFactory = IdFactoryHelper.RuntimeIdFactory(0, sceneId, world); scene.Id = IdFactoryHelper.EntityId(0, sceneId, world, 0); - scene.RuntimeId = IdFactoryHelper.RuntimeId(false, 0, sceneId, world, 0); + scene.RuntimeId = IdFactoryHelper.RuntimeId(0, sceneId, world, 0); scene.AddEntity(scene); await SetScheduler(scene, sceneRuntimeMode); scene.ThreadSynchronizationContext.Post(() => @@ -369,9 +368,9 @@ namespace Fantasy scene.Process = process; scene.SceneRuntimeType = SceneRuntimeType.Root; scene.EntityIdFactory = IdFactoryHelper.EntityIdFactory(sceneConfigId, worldId); - scene.RuntimeIdFactory = IdFactoryHelper.RuntimeIdFactory(0, sceneConfigId, worldId); + scene.RuntimeIdFactory = IdFactoryHelper.RuntimeIdFactory(0,sceneConfigId, worldId); scene.Id = IdFactoryHelper.EntityId(0, sceneConfigId, worldId, 0); - scene.RuntimeId = IdFactoryHelper.RuntimeId(false, 0, sceneConfigId, worldId, 0); + scene.RuntimeId = IdFactoryHelper.RuntimeId(0, sceneConfigId, worldId, 0); scene.AddEntity(scene); return scene; } @@ -444,7 +443,7 @@ namespace Fantasy scene.EntityIdFactory = parentScene.EntityIdFactory; scene.RuntimeIdFactory = parentScene.RuntimeIdFactory; scene.Id = scene.EntityIdFactory.Create; - scene.RuntimeId = scene.RuntimeIdFactory.Create(false); + scene.RuntimeId = scene.RuntimeIdFactory.Create; scene.AddEntity(scene); scene.Initialize(parentScene); scene.ThreadSynchronizationContext.Post(() => OnEvent().Coroutine()); diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Scene/SubScene.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Scene/SubScene.cs index cfbf086..a8c2e73 100644 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Scene/SubScene.cs +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Scene/SubScene.cs @@ -40,7 +40,7 @@ namespace Fantasy MessageDispatcherComponent = rootScene.MessageDispatcherComponent; #if FANTASY_NET NetworkMessagingComponent = rootScene.NetworkMessagingComponent; - // SingleCollectionComponent = rootScene.SingleCollectionComponent; + SingleCollectionComponent = rootScene.SingleCollectionComponent; TerminusComponent = rootScene.TerminusComponent; #endif ThreadSynchronizationContext = rootScene.ThreadSynchronizationContext; diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Serialize/BsonPack/BsonPackHelperNet.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Serialize/BsonPack/BsonPackHelperNet.cs index 1c8aa16..cf97a8c 100644 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Serialize/BsonPack/BsonPackHelperNet.cs +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Serialize/BsonPack/BsonPackHelperNet.cs @@ -4,7 +4,6 @@ using System.Collections; using System.ComponentModel; using System.Reflection; using Fantasy.Assembly; -using Fantasy.Async; using Fantasy.Entitas; using MongoDB.Bson; using MongoDB.Bson.IO; @@ -20,7 +19,7 @@ namespace Fantasy.Serialize /// /// BSON帮助方法 /// - public class BsonPackHelper : ISerialize, IAssemblyLifecycle + public class BsonPackHelper : ISerialize { /// /// 序列化器的名字 @@ -32,56 +31,54 @@ namespace Fantasy.Serialize /// public BsonPackHelper() { + // 清除掉注册过的LookupClassMap。 + + var classMapRegistryField = typeof(BsonClassMap).GetField("__classMaps", BindingFlags.Static | BindingFlags.NonPublic); + + if (classMapRegistryField != null) + { + ((Dictionary)classMapRegistryField.GetValue(null)).Clear(); + } + + // 清除掉注册过的ConventionRegistry。 + + var registryField = typeof(ConventionRegistry).GetField("_lookup", BindingFlags.Static | BindingFlags.NonPublic); + + if (registryField != null) + { + var registry = registryField.GetValue(null); + var dictionaryField = registry.GetType().GetField("_conventions", BindingFlags.Instance | BindingFlags.NonPublic); + if (dictionaryField != null) + { + ((IDictionary)dictionaryField.GetValue(registry)).Clear(); + } + } + // 初始化ConventionRegistry、注册IgnoreExtraElements。 + ConventionRegistry.Register("IgnoreExtraElements", new ConventionPack { new IgnoreExtraElementsConvention(true) }, type => true); + // 注册一个自定义的序列化器。 + // BsonSerializer.TryRegisterSerializer(typeof(float2), new StructBsonSerialize()); // BsonSerializer.TryRegisterSerializer(typeof(float3), new StructBsonSerialize()); // BsonSerializer.TryRegisterSerializer(typeof(float4), new StructBsonSerialize()); // BsonSerializer.TryRegisterSerializer(typeof(quaternion), new StructBsonSerialize()); BsonSerializer.RegisterSerializer(new ObjectSerializer(x => true)); - } - #region AssemblyManifest + // 注册LookupClassMap。 - internal async FTask Initialize() - { - await AssemblyLifecycle.Add(this); - return this; - } - - /// - /// - /// - /// 程序集清单对象,包含程序集的元数据和注册器 - public async FTask OnLoad(AssemblyManifest assemblyManifest) - { - var entityTypes = assemblyManifest.EntityTypeCollectionRegistrar.GetEntityTypes(); - if (entityTypes.Any()) + foreach (var type in AssemblySystem.ForEach()) { - foreach (var entityType in entityTypes) + if (type.IsInterface || type.IsAbstract || type.IsGenericType || !typeof(Entity).IsAssignableFrom(type)) { - if (BsonClassMap.IsClassMapRegistered(entityType)) - { - continue; - } - BsonClassMap.LookupClassMap(entityType); + continue; } + + BsonClassMap.LookupClassMap(type); } - await FTask.CompletedTask; } - /// - /// - /// - /// 程序集清单对象,包含程序集的元数据和注册器 - public async FTask OnUnload(AssemblyManifest assemblyManifest) - { - await FTask.CompletedTask; - } - - #endregion - /// /// 反序列化 /// diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Serialize/Interface/ASerialize.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Serialize/Interface/ASerialize.cs index d28f2c7..7487b86 100644 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Serialize/Interface/ASerialize.cs +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Serialize/Interface/ASerialize.cs @@ -20,4 +20,41 @@ namespace Fantasy.Serialize public virtual void EndInit() { } public virtual void AfterDeserialization() => EndInit(); } + + public abstract class AMessage : ASerialize, IPool + { +#if FANTASY_NET || FANTASY_UNITY || FANTASY_CONSOLE + [BsonIgnore] + [JsonIgnore] + [IgnoreDataMember] + [ProtoIgnore] + private Scene _scene; + protected Scene GetScene() + { + return _scene; + } + + public void SetScene(Scene scene) + { + _scene = scene; + } +#endif +#if FANTASY_NET + [BsonIgnore] +#endif + [JsonIgnore] + [IgnoreDataMember] + [ProtoIgnore] + private bool _isPool; + + public bool IsPool() + { + return _isPool; + } + + public void SetIsPool(bool isPool) + { + _isPool = isPool; + } + } } \ No newline at end of file diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Serialize/ProtoBufPackHelper/IProto.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Serialize/ProtoBufPackHelper/IProto.cs new file mode 100644 index 0000000..2267dd2 --- /dev/null +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Serialize/ProtoBufPackHelper/IProto.cs @@ -0,0 +1,9 @@ +namespace Fantasy.Serialize +{ + /// + /// 代表是一个ProtoBuf协议 + /// + public interface IProto + { + } +} \ No newline at end of file diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Serialize/ProtoBufPackHelper/ProtoBufPackHelperNet.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Serialize/ProtoBufPackHelper/ProtoBufPackHelperNet.cs index 87af2ee..5e28808 100644 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Serialize/ProtoBufPackHelper/ProtoBufPackHelperNet.cs +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Serialize/ProtoBufPackHelper/ProtoBufPackHelperNet.cs @@ -1,23 +1,23 @@ #if FANTASY_NET || FANTASY_EXPORTER using System.Buffers; using Fantasy.Assembly; -using Fantasy.Async; using ProtoBuf.Meta; namespace Fantasy.Serialize { /// /// ProtoBufP帮助类,Net平台使用 /// - public sealed class ProtoBufPackHelper : ISerialize, IAssemblyLifecycle + public sealed class ProtoBufPackHelper : ISerialize { /// /// 序列化器的名字 /// public string SerializeName { get; } = "ProtoBuf"; - - #region AssemblyManifest - - internal async FTask Initialize() + + /// + /// 构造函数 + /// + public ProtoBufPackHelper () { #if FANTASY_NET RuntimeTypeModel.Default.AutoAddMissingTypes = true; @@ -26,40 +26,16 @@ namespace Fantasy.Serialize RuntimeTypeModel.Default.AutoCompile = true; RuntimeTypeModel.Default.UseImplicitZeroDefaults = true; RuntimeTypeModel.Default.InferTagFromNameDefault = true; -#endif - await AssemblyLifecycle.Add(this); - return this; - } - - /// - /// - /// - /// 程序集清单对象,包含程序集的元数据和注册器 - public async FTask OnLoad(AssemblyManifest assemblyManifest) - { - var protoBufTypes = assemblyManifest.NetworkProtocolRegistrar.GetNetworkProtocolTypes(); - if (protoBufTypes.Any()) + + foreach (var type in AssemblySystem.ForEach(typeof(IProto))) { - foreach (var protoBufType in protoBufTypes) - { - RuntimeTypeModel.Default.Add(protoBufType, true); - } - RuntimeTypeModel.Default.CompileInPlace(); + RuntimeTypeModel.Default.Add(type, true); } - await FTask.CompletedTask; - } - /// - /// - /// - /// 程序集清单对象,包含程序集的元数据和注册器 - public async FTask OnUnload(AssemblyManifest assemblyManifest) - { - await FTask.CompletedTask; + RuntimeTypeModel.Default.CompileInPlace(); +#endif } - #endregion - /// /// 使用ProtoBuf反序列化数据到实例 /// diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Serialize/ProtoBufPackHelper/ProtoBufPackHelperUnity.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Serialize/ProtoBufPackHelper/ProtoBufPackHelperUnity.cs index 6f70ded..0e386af 100644 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Serialize/ProtoBufPackHelper/ProtoBufPackHelperUnity.cs +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Serialize/ProtoBufPackHelper/ProtoBufPackHelperUnity.cs @@ -3,7 +3,6 @@ using System; using System.Buffers; using System.IO; using Fantasy.Assembly; -using Fantasy.Async; using ProtoBuf; using ProtoBuf.Meta; @@ -19,12 +18,6 @@ namespace Fantasy.Serialize /// public string SerializeName { get; } = "ProtoBuf"; - internal async FTask Initialize() - { - await FTask.CompletedTask; - return this; - } - /// /// 使用ProtoBuf反序列化数据到实例 /// diff --git a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Serialize/SerializerManager.cs b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Serialize/SerializerManager.cs index 6f402e0..1ff52aa 100644 --- a/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Serialize/SerializerManager.cs +++ b/Fantasy/Fantasy.Net/Fantasy.Net/Runtime/Core/Serialize/SerializerManager.cs @@ -1,6 +1,6 @@ using System; using System.Collections.Generic; -using Fantasy.Async; +using Fantasy.Assembly; using Fantasy.Helper; #if !FANTASY_EXPORTER using Fantasy.Network; @@ -40,7 +40,7 @@ namespace Fantasy.Serialize /// /// 初始化方法 /// - public static async FTask Initialize() + public static void Initialize() { if (_isInitialized) { @@ -50,16 +50,14 @@ namespace Fantasy.Serialize try { var sort = new SortedList(); - - var protoBufPackSerializer = await new ProtoBufPackHelper().Initialize(); - var protoBufPackSerializerHash64 = HashCodeHelper.ComputeHash64(protoBufPackSerializer.SerializeName); - sort.Add(protoBufPackSerializerHash64, protoBufPackSerializer); -#if FANTASY_NET - var bsonPackSerializer = await new BsonPackHelper().Initialize(); - var bsonPackSerializerSerializerHash64 = HashCodeHelper.ComputeHash64(bsonPackSerializer.SerializeName); - sort.Add(bsonPackSerializerSerializerHash64, bsonPackSerializer); -#endif + foreach (var serializerType in AssemblySystem.ForEach(typeof(ISerialize))) + { + var serializer = (ISerialize)Activator.CreateInstance(serializerType); + var computeHash64 = HashCodeHelper.ComputeHash64(serializer.SerializeName); + sort.Add(computeHash64, serializer); + } + var index = 1; _serializers = new ISerialize[sort.Count]; diff --git a/Fantasy/Fantasy.Net/Fantasy.SourceGenerator/Attributes/GeneratorAttributes.cs b/Fantasy/Fantasy.Net/Fantasy.SourceGenerator/Attributes/GeneratorAttributes.cs deleted file mode 100644 index 8c7e7a3..0000000 --- a/Fantasy/Fantasy.Net/Fantasy.SourceGenerator/Attributes/GeneratorAttributes.cs +++ /dev/null @@ -1,31 +0,0 @@ -namespace Fantasy.SourceGenerator.Attributes -{ - /// - /// 标记程序集启用 Source Generator 生成注册代码 - /// 添加到 AssemblyInfo.cs 或任何文件: - /// [assembly: Fantasy.SourceGenerator.Attributes.EnableSourceGenerator] - /// - [System.AttributeUsage(System.AttributeTargets.Assembly)] - public sealed class EnableSourceGeneratorAttribute : System.Attribute - { - /// - /// 是否生成 Entity System 注册器 - /// - public bool GenerateEntitySystem { get; set; } = true; - - /// - /// 是否生成 Event Handler 注册器 - /// - public bool GenerateEventHandler { get; set; } = true; - - /// - /// 是否生成 OpCode Mapper 注册器 - /// - public bool GenerateOpCodeMapper { get; set; } = true; - - /// - /// 是否生成 Message Handler 注册器 - /// - public bool GenerateMessageHandler { get; set; } = true; - } -} diff --git a/Fantasy/Fantasy.Net/Fantasy.SourceGenerator/Common/CompilationHelper.cs b/Fantasy/Fantasy.Net/Fantasy.SourceGenerator/Common/CompilationHelper.cs deleted file mode 100644 index fc90c17..0000000 --- a/Fantasy/Fantasy.Net/Fantasy.SourceGenerator/Common/CompilationHelper.cs +++ /dev/null @@ -1,101 +0,0 @@ -using System.Linq; -using Microsoft.CodeAnalysis; - -namespace Fantasy.SourceGenerator.Common -{ - /// - /// 编译环境检测帮助类 - /// 提供检测 Fantasy 框架、Unity 环境等公共方法 - /// - public static class CompilationHelper - { - /// - /// 检查是否定义了 Fantasy 框架的预编译符号 - /// 只有定义了 FANTASY_NET的项目才会生成代码 - /// - public static bool HasFantasyNETDefine(Compilation compilation) - { - // 遍历所有语法树的预处理符号 - foreach (var tree in compilation.SyntaxTrees) - { - var defines = tree.Options.PreprocessorSymbolNames; - if (defines.Contains("FANTASY_NET")) - { - return true; - } - } - - return false; - } - - /// - /// 检查是否定义了 Fantasy 框架的预编译符号 - /// 只有定义了 FANTASY_UNITY的项目才会生成代码 - /// - public static bool HasFantasyUNITYDefine(Compilation compilation) - { - // 遍历所有语法树的预处理符号 - foreach (var tree in compilation.SyntaxTrees) - { - var defines = tree.Options.PreprocessorSymbolNames; - if (defines.Contains("FANTASY_UNITY")) - { - return true; - } - } - - return false; - } - - /// - /// 检查是否定义了 Fantasy 框架的预编译符号 - /// 只有定义了 FANTASY_NET 或 FANTASY_UNITY 的项目才会生成代码 - /// - public static bool HasFantasyDefine(Compilation compilation) - { - // 遍历所有语法树的预处理符号 - foreach (var tree in compilation.SyntaxTrees) - { - var defines = tree.Options.PreprocessorSymbolNames; - if (defines.Contains("FANTASY_NET") || defines.Contains("FANTASY_UNITY")) - { - return true; - } - } - - return false; - } - - /// - /// 检测是否是 Unity 编译环境 - /// 优先检查是否引用了 UnityEngine 核心程序集或类型,这是最可靠的方式 - /// - public static bool IsUnityCompilation(Compilation compilation) - { - // 方法1: 检查是否引用了 UnityEngine.CoreModule 或 UnityEngine 程序集(最可靠) - foreach (var reference in compilation.References) - { - if (reference is PortableExecutableReference peRef) - { - var display = peRef.Display ?? ""; - // Unity 2017+ 使用模块化程序集 - if (display.Contains("UnityEngine.CoreModule.dll") || - display.Contains("UnityEngine.dll")) - { - return true; - } - } - } - - // 方法2: 检查是否能够找到 UnityEngine 命名空间的核心类型 - var unityMonoBehaviour = compilation.GetTypeByMetadataName("UnityEngine.MonoBehaviour"); - var unityGameObject = compilation.GetTypeByMetadataName("UnityEngine.GameObject"); - if (unityMonoBehaviour != null || unityGameObject != null) - { - return true; - } - - return false; - } - } -} diff --git a/Fantasy/Fantasy.Net/Fantasy.SourceGenerator/Common/GeneratorConstants.cs b/Fantasy/Fantasy.Net/Fantasy.SourceGenerator/Common/GeneratorConstants.cs deleted file mode 100644 index cfa05c6..0000000 --- a/Fantasy/Fantasy.Net/Fantasy.SourceGenerator/Common/GeneratorConstants.cs +++ /dev/null @@ -1,43 +0,0 @@ -namespace Fantasy.SourceGenerator.Common -{ - /// - /// Source Generator 使用的常量定义 - /// - internal static class GeneratorConstants - { - /// - /// 生成文件的命名空间 - /// - public const string GeneratedNamespace = "Fantasy.Generated"; - /// - /// 诊断 ID 前缀 - /// - public const string DiagnosticIdPrefix = "FANTASY"; - /// - /// 生成的代码文件注释头 - /// - public const string AutoGeneratedHeader = @"//------------------------------------------------------------------------------ -// -// This code was generated by Fantasy.SourceGenerator. -// Runtime Version: 1.0.0 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -#nullable enable -#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member -// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract -// ReSharper disable MergeIntoPattern -// ReSharper disable SuspiciousTypeConversion.Global -// ReSharper disable NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract -// ReSharper disable CheckNamespace -#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. -#pragma warning disable CS8603 // Possible null reference return. -#pragma warning disable CS8602 // Dereference of a possibly null reference. -#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type. -#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. -"; - } -} diff --git a/Fantasy/Fantasy.Net/Fantasy.SourceGenerator/Common/HashCodeHelper.cs b/Fantasy/Fantasy.Net/Fantasy.SourceGenerator/Common/HashCodeHelper.cs deleted file mode 100644 index 74abed1..0000000 --- a/Fantasy/Fantasy.Net/Fantasy.SourceGenerator/Common/HashCodeHelper.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; - -namespace Fantasy.SourceGenerator.Common -{ - /// - /// HashCode 算法帮助类 (Source Generator 版本) - /// 用于在编译时计算类型哈希值 - /// 注意:此实现必须与 Fantasy.Helper.HashCodeHelper.ComputeHash64 保持完全一致 - /// - internal static class HashCodeHelper - { - /// - /// 使用 MurmurHash3 算法生成一个 long 的值 - /// - /// 输入字符串 - /// 64位哈希值 - public static long ComputeHash64(string str) - { - const ulong seed = 0xc58f1a7bc58f1a7bUL; // 64-bit seed - var hash = seed; - var c1 = 0x87c37b91114253d5UL; - var c2 = 0x4cf5ad432745937fUL; - - for (var i = 0; i < str.Length; i++) - { - var k1 = (ulong)str[i]; - k1 *= c1; - k1 = (k1 << 31) | (k1 >> (64 - 31)); - k1 *= c2; - - hash ^= k1; - hash = (hash << 27) | (hash >> (64 - 27)); - hash = hash * 5 + 0x52dce729; - } - - hash ^= (ulong)str.Length; - hash ^= hash >> 33; - hash *= 0xff51afd7ed558ccdUL; - hash ^= hash >> 33; - hash *= 0xc4ceb9fe1a85ec53UL; - hash ^= hash >> 33; - return (long)hash; - } - } -} diff --git a/Fantasy/Fantasy.Net/Fantasy.SourceGenerator/Common/IsExternalInit.cs b/Fantasy/Fantasy.Net/Fantasy.SourceGenerator/Common/IsExternalInit.cs deleted file mode 100644 index 12d5df1..0000000 --- a/Fantasy/Fantasy.Net/Fantasy.SourceGenerator/Common/IsExternalInit.cs +++ /dev/null @@ -1,33 +0,0 @@ -// ReSharper disable CheckNamespace -//------------------------------------------------------------------------------ -// 这个 IsExternalInit 类是一个 polyfill(兼容性填充) -// 用于在 .NET Standard 2.0 或较低版本的框架中启用 C# 9.0 的 init 访问器和 record 类型功能。 -// 为什么需要它? -// C# 9.0 引入了 init 访问器(只在初始化时可设置的属性) -// 编译器在编译 init 属性时,会查找 IsExternalInit 类型 -// 示例: -// public class Person -// { -// public string Name { get; init; } // 需要 IsExternalInit -// public int Age { get; init; } -// } -// 使用 -// var person = new Person { Name = "Alice", Age = 30 }; -// person.Name = "Bob"; // ❌ 编译错误:init 属性只能在对象初始化时设置 -// 不定义会怎样? -// 如果目标框架是 netstandard2.0 但没定义 IsExternalInit,编译器会报错: -// error CS0518: Predefined type 'System.Runtime.CompilerServices.IsExternalInit' -// is not defined or imported -// 实际应用场景 -// 在 IncrementalGenerator 中,你可能会生成或使用带 init 的代码 -//------------------------------------------------------------------------------ -#if NETSTANDARD2_0 || NETFRAMEWORK -namespace System.Runtime.CompilerServices -{ - /// - /// Polyfill for C# 9.0 record types in netstandard2.0 - /// - [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] - internal static class IsExternalInit { } -} -#endif diff --git a/Fantasy/Fantasy.Net/Fantasy.SourceGenerator/Common/RoslynExtensions.cs b/Fantasy/Fantasy.Net/Fantasy.SourceGenerator/Common/RoslynExtensions.cs deleted file mode 100644 index 5941b36..0000000 --- a/Fantasy/Fantasy.Net/Fantasy.SourceGenerator/Common/RoslynExtensions.cs +++ /dev/null @@ -1,134 +0,0 @@ -using Microsoft.CodeAnalysis; -using System.Collections.Generic; -using System.Linq; - -namespace Fantasy.SourceGenerator.Common -{ - /// - /// Roslyn 相关的扩展方法 - /// - internal static class RoslynExtensions - { - /// - /// 检查类型是否实现了指定的接口(通过完全限定名) - /// - public static bool ImplementsInterface(this INamedTypeSymbol typeSymbol, string interfaceFullName) - { - return typeSymbol.AllInterfaces.Any(i => i.ToDisplayString() == interfaceFullName); - } - - /// - /// 检查类型是否继承自指定的基类(通过完全限定名) - /// - public static bool InheritsFrom(this INamedTypeSymbol typeSymbol, string baseTypeFullName) - { - var current = typeSymbol.BaseType; - while (current != null) - { - if (current.ToDisplayString() == baseTypeFullName) - { - return true; - } - current = current.BaseType; - } - return false; - } - - /// - /// 获取类型的完全限定名(包括命名空间) - /// - public static string GetFullName(this ITypeSymbol typeSymbol, bool includeGlobal = true) - { - var displayString = typeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); - return includeGlobal ? displayString : displayString.Replace("global::", ""); - } - - /// - /// 检查类型是否可以被实例化(非抽象、非接口、非泛型定义) - /// - public static bool IsInstantiable(this INamedTypeSymbol typeSymbol) - { - // 允许:具体的类(非抽象、非静态) - // 排除:抽象类、静态类、接口、开放泛型类型(如 MyClass where T 未指定) - return typeSymbol is { IsAbstract: false, IsStatic: false, TypeKind: TypeKind.Class }; - } - - /// - /// 获取所有实现指定接口的类型 - /// - public static IEnumerable GetTypesImplementingInterface( - this Compilation compilation, - string interfaceFullName) - { - var visitor = new InterfaceImplementationVisitor(interfaceFullName); - visitor.Visit(compilation.GlobalNamespace); - return visitor.ImplementingTypes; - } - - /// - /// 转换为驼峰命名 - /// - public static string ToCamelCase(this string name) - { - if (string.IsNullOrEmpty(name) || char.IsLower(name[0])) - { - return name; - } - - return char.ToLowerInvariant(name[0]) + name.Substring(1); - } - - /// - /// 访问器:查找实现特定接口的所有类型 - /// - private class InterfaceImplementationVisitor : SymbolVisitor - { - private readonly string _interfaceFullName; - private readonly List _implementingTypes = new List(); - - public IReadOnlyList ImplementingTypes => _implementingTypes; - - public InterfaceImplementationVisitor(string interfaceFullName) - { - _interfaceFullName = interfaceFullName; - } - - public override void VisitNamespace(INamespaceSymbol symbol) - { - foreach (var member in symbol.GetMembers()) - { - member.Accept(this); - } - } - - public override void VisitNamedType(INamedTypeSymbol symbol) - { - if (symbol.IsInstantiable() && symbol.ImplementsInterface(_interfaceFullName)) - { - _implementingTypes.Add(symbol); - } - - // 递归访问嵌套类型 - foreach (var nestedType in symbol.GetTypeMembers()) - { - nestedType.Accept(this); - } - } - } - - /// - /// 尝试获取泛型接口的类型参数 - /// 例如:IAwakeSystem<PlayerEntity> 返回 PlayerEntity - /// - public static ITypeSymbol? GetGenericInterfaceTypeArgument( - this INamedTypeSymbol typeSymbol, - string genericInterfaceName) - { - var matchingInterface = typeSymbol.AllInterfaces.FirstOrDefault(i => - i.IsGenericType && - i.ConstructedFrom.ToDisplayString() == genericInterfaceName); - - return matchingInterface?.TypeArguments.FirstOrDefault(); - } - } -} diff --git a/Fantasy/Fantasy.Net/Fantasy.SourceGenerator/Common/SourceCodeBuilder.cs b/Fantasy/Fantasy.Net/Fantasy.SourceGenerator/Common/SourceCodeBuilder.cs deleted file mode 100644 index 57498d3..0000000 --- a/Fantasy/Fantasy.Net/Fantasy.SourceGenerator/Common/SourceCodeBuilder.cs +++ /dev/null @@ -1,211 +0,0 @@ -using System.Text; - -namespace Fantasy.SourceGenerator.Common -{ - /// - /// 辅助构建生成代码的工具类 - /// - internal sealed class SourceCodeBuilder - { - private readonly StringBuilder _builder; - private int _indentLevel; - private const string IndentString = " "; // 4 空格缩进 - - public SourceCodeBuilder(int indentLevel = 0) - { - _builder = new StringBuilder(); - _indentLevel = indentLevel; - } - - /// - /// 增加缩进级别 - /// - public SourceCodeBuilder Indent(int indentLevel = 1) - { - _indentLevel += indentLevel; - return this; - } - - /// - /// 减少缩进级别 - /// - public SourceCodeBuilder Unindent() - { - if (_indentLevel > 0) - { - _indentLevel--; - } - return this; - } - - public void Append(string code = "") - { - _builder.Append(code); - } - - /// - /// 添加一行代码(自动处理缩进) - /// - public SourceCodeBuilder AppendLine(string code = "", bool indent = true) - { - if (string.IsNullOrEmpty(code)) - { - _builder.AppendLine(); - } - else - { - if (indent) - { - for (int i = 0; i < _indentLevel; i++) - { - _builder.Append(IndentString); - } - } - - _builder.AppendLine(code); - } - - return this; - } - - /// - /// 添加代码块开始 { - /// - public SourceCodeBuilder OpenBrace() - { - AppendLine("{"); - Indent(); - return this; - } - - /// - /// 添加代码块结束 } - /// - public SourceCodeBuilder CloseBrace(bool semicolon = false) - { - Unindent(); - AppendLine(semicolon ? "};" : "}"); - return this; - } - - /// - /// 添加 using 语句 - /// - public SourceCodeBuilder AddUsing(string @namespace) - { - AppendLine($"using {@namespace};"); - return this; - } - - /// - /// 添加多个 using 语句 - /// - public SourceCodeBuilder AddUsings(params string[] namespaces) - { - foreach (var ns in namespaces) - { - AddUsing(ns); - } - return this; - } - - /// - /// 开始命名空间 - /// - public SourceCodeBuilder BeginNamespace(string @namespace) - { - AppendLine($"namespace {@namespace}"); - OpenBrace(); - return this; - } - - /// - /// 结束命名空间 - /// - public SourceCodeBuilder EndNamespace() - { - CloseBrace(); - return this; - } - - /// - /// 开始类定义 - /// - public SourceCodeBuilder BeginClass(string className, string? modifiers = "internal static", string? baseTypes = null) - { - var classDeclaration = $"{modifiers} class {className}"; - if (!string.IsNullOrEmpty(baseTypes)) - { - classDeclaration += $" : {baseTypes}"; - } - AppendLine(classDeclaration); - OpenBrace(); - return this; - } - - /// - /// 结束类定义 - /// - public SourceCodeBuilder EndClass() - { - CloseBrace(); - return this; - } - - /// - /// 开始方法定义 - /// - public SourceCodeBuilder BeginMethod(string signature) - { - AppendLine(signature); - OpenBrace(); - return this; - } - - /// - /// 结束方法定义 - /// - public SourceCodeBuilder EndMethod() - { - CloseBrace(); - return this; - } - - /// - /// 添加注释 - /// - public SourceCodeBuilder AddComment(string comment) - { - AppendLine($"// {comment}"); - return this; - } - - /// - /// 添加 XML 文档注释 - /// - public SourceCodeBuilder AddXmlComment(string summary) - { - AppendLine("/// "); - AppendLine($"/// {summary}"); - AppendLine("/// "); - return this; - } - - /// - /// 构建最终代码 - /// - public override string ToString() - { - return _builder.ToString(); - } - - /// - /// 清空构建器 - /// - public void Clear() - { - _builder.Clear(); - _indentLevel = 0; - } - } -} diff --git a/Fantasy/Fantasy.Net/Fantasy.SourceGenerator/Fantasy.SourceGenerator.csproj b/Fantasy/Fantasy.Net/Fantasy.SourceGenerator/Fantasy.SourceGenerator.csproj deleted file mode 100644 index 7d49485..0000000 --- a/Fantasy/Fantasy.Net/Fantasy.SourceGenerator/Fantasy.SourceGenerator.csproj +++ /dev/null @@ -1,40 +0,0 @@ - - - - netstandard2.0 - latest - enable - enable - true - true - false - - - true - false - - - - - $(DefineConstants);UNITY_COMPATIBLE - - - - - - - - - - - - - - - - - - - - - diff --git a/Fantasy/Fantasy.Net/Fantasy.SourceGenerator/Generators/AssemblyInitializerGenerator.cs b/Fantasy/Fantasy.Net/Fantasy.SourceGenerator/Generators/AssemblyInitializerGenerator.cs deleted file mode 100644 index 779671c..0000000 --- a/Fantasy/Fantasy.Net/Fantasy.SourceGenerator/Generators/AssemblyInitializerGenerator.cs +++ /dev/null @@ -1,259 +0,0 @@ -using Fantasy.SourceGenerator.Common; -using Microsoft.CodeAnalysis; - -namespace Fantasy.SourceGenerator.Generators -{ - /// - /// 程序集初始化器生成器 - /// 为每个 Fantasy 项目生成 ModuleInitializer,在程序集加载时自动注册到框架 - /// 支持 Native AOT,无需运行时反射 - /// - [Generator] - public class AssemblyInitializerGenerator : IIncrementalGenerator - { - public void Initialize(IncrementalGeneratorInitializationContext context) - { - // 获取编译信息 - var compilationProvider = context.CompilationProvider; - // 注册源代码输出 - context.RegisterSourceOutput(compilationProvider, (spc, compilation) => - { - GenerateModuleInitializer(spc, compilation); - }); - } - - private static void GenerateModuleInitializer( - SourceProductionContext context, - Compilation compilation) - { - // 检查是否定义了 FANTASY_NET 或 FANTASY_UNITY 预编译符号 - if (!CompilationHelper.HasFantasyDefine(compilation)) - { - // 不是 Fantasy 框架项目,跳过代码生成 - return; - } - - var assemblyName = compilation.AssemblyName ?? "Unknown"; - // 检测是否是 Unity 环境 - var isUnity = CompilationHelper.IsUnityCompilation(compilation); - var builder = new SourceCodeBuilder(); - // 添加文件头 - builder.AppendLine(GeneratorConstants.AutoGeneratedHeader); - // 添加 using - if (isUnity) - { - builder.AddUsings( - "System", - "UnityEngine" - ); - } - else - { - builder.AddUsings( - "System", - "System.Runtime.CompilerServices" - ); - } - builder.AppendLine(); - // 开始命名空间 - builder.BeginNamespace(GeneratorConstants.GeneratedNamespace); - // 添加类注释 - builder.AddXmlComment($"Auto-generated assembly initializer for {assemblyName}"); - builder.AddXmlComment("This class is automatically invoked when the assembly is loaded via ModuleInitializer"); - // 开始类定义 - builder.BeginClass("AssemblyInitializer", "internal static"); - // 添加字段 - builder.AppendLine("private static bool _initialized;"); - builder.AppendLine("private static long _assemblyManifestId;"); - builder.AppendLine(); - - // 生成 ModuleInitializer 方法 - GenerateInitializeMethod(builder, assemblyName, isUnity); - - // 生成卸载方法 - GenerateUnloadMethod(builder, isUnity); - - // 结束 AssemblyInitializer 类 - builder.EndClass(); - - builder.AppendLine(); - - // 生成 AssemblyMarker 类(用于强制加载程序集) - // 类名包含程序集名称以避免冲突,将特殊字符替换为下划线 - var markerClassName = assemblyName.Replace("-", "_").Replace(".", "_") + "_AssemblyMarker"; - builder.AddXmlComment($"Public marker class for forcing {assemblyName} assembly load"); - builder.AddXmlComment("Access this type to ensure the assembly is loaded and ModuleInitializer executes"); - builder.BeginClass(markerClassName, "public static"); - builder.AppendLine("/// "); - builder.AppendLine("/// Call this method to ensure the assembly is loaded"); - builder.AppendLine("/// This is useful when loading assemblies dynamically via reflection"); - builder.AppendLine("/// "); - builder.BeginMethod("public static void EnsureLoaded()"); - builder.AppendLine("// Accessing this method ensures the assembly is loaded"); - builder.AppendLine("// ModuleInitializer will execute automatically when assembly loads"); - builder.EndMethod(); - builder.EndClass(); - - // 结束命名空间 - builder.EndNamespace(); - // 输出源代码 - context.AddSource("AssemblyInitializer.g.cs", builder.ToString()); - } - - private static void GenerateInitializeMethod(SourceCodeBuilder builder, string assemblyName, bool isUnity) - { - if (isUnity) - { - builder.AddXmlComment( - "Unity runtime initializer - automatically called when entering play mode or on app start"); - builder.AppendLine("[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterAssembliesLoaded)]"); - builder.BeginMethod("internal static void Initialize()"); - } - else - { - builder.AddXmlComment("Module initializer - automatically called when assembly is loaded"); - builder.AppendLine("[ModuleInitializer]"); - builder.BeginMethod("internal static void Initialize()"); - } - - // 防止重复初始化 - builder.AppendLine("if (_initialized)"); - builder.OpenBrace(); - builder.AppendLine("return;"); - builder.CloseBrace(); - builder.AppendLine(); - builder.AppendLine("_initialized = true;"); - builder.AppendLine(); - - // 获取程序集并计算唯一标识 - builder.AddComment("Get assembly and calculate manifest ID"); - builder.AppendLine("var assembly = typeof(AssemblyInitializer).Assembly;"); - builder.AppendLine( - $"_assemblyManifestId = Fantasy.Helper.HashCodeHelper.ComputeHash64(assembly.GetName().Name ?? \"{assemblyName}\");"); - builder.AppendLine(); - - // 注册卸载事件(用于热更新支持) - builder.AddComment("Register auto-unload for collectible AssemblyLoadContext (hot-reload support)"); - if (isUnity) - { - builder.AppendLine( - "#if !UNITY_EDITOR && !UNITY_STANDALONE && !UNITY_ANDROID && !UNITY_IOS && !UNITY_WEBGL"); - } - - builder.AppendLine("var loadContext = System.Runtime.Loader.AssemblyLoadContext.GetLoadContext(assembly);"); - builder.AppendLine( - "if (loadContext != null && loadContext != System.Runtime.Loader.AssemblyLoadContext.Default)"); - builder.OpenBrace(); - builder.AppendLine("loadContext.Unloading += OnAssemblyUnloading;"); - builder.CloseBrace(); - if (isUnity) - { - builder.AppendLine("#endif"); - } - - builder.AppendLine(); - - // 声明所有可能的注册器变量 - builder.AddComment("Declare registrar variables"); - builder.AppendLine("Fantasy.Assembly.INetworkProtocolRegistrar? networkProtocolRegistrar = null;"); - builder.AppendLine("Fantasy.Assembly.IEventSystemRegistrar? eventSystemRegistrar = null;"); - builder.AppendLine("Fantasy.Assembly.IEntitySystemRegistrar? entitySystemRegistrar = null;"); - builder.AppendLine("Fantasy.Assembly.IMessageHandlerResolver? messageHandlerResolverRegistrar = null;"); - builder.AppendLine( - "Fantasy.Assembly.IEntityTypeCollectionRegistrar? entityTypeCollectionRegistrar = null;"); - builder.AppendLine( - "Fantasy.Assembly.INetworkProtocolOpCodeResolver? networkProtocolOpCodeResolverRegistrar = null;"); - builder.AppendLine( - "Fantasy.Assembly.INetworkProtocolResponseTypeResolver? networkProtocolResponseTypeResolverRegistrar = null;"); - builder.AppendLine("#if FANTASY_NET", false); - builder.AppendLine( - "Fantasy.Assembly.ISeparateTableRegistrar? separateTableRegistrar = null;"); - builder.AppendLine("#endif", false); - builder.AppendLine(); - - // 尝试创建各个注册器(如果存在) - builder.AddComment("Try to create registrars if they were generated in this assembly"); - GenerateTryCreateRegistrar(builder, "NetworkProtocol", "networkProtocolRegistrar"); - GenerateTryCreateRegistrar(builder, "EventSystem", "eventSystemRegistrar"); - GenerateTryCreateRegistrar(builder, "EntitySystem", "entitySystemRegistrar"); - GenerateTryCreateRegistrar(builder, "MessageHandlerResolver", "messageHandlerResolverRegistrar"); - GenerateTryCreateRegistrar(builder, "EntityTypeCollection", "entityTypeCollectionRegistrar"); - GenerateTryCreateRegistrar(builder, "NetworkProtocolOpCodeResolver", "networkProtocolOpCodeResolverRegistrar"); - GenerateTryCreateRegistrar(builder, "NetworkProtocolResponseTypeResolver", "networkProtocolResponseTypeResolverRegistrar"); - builder.AppendLine("#if FANTASY_NET", false); - GenerateTryCreateRegistrar(builder, "SeparateTable", "separateTableRegistrar"); - builder.AppendLine("#endif", false); - - builder.AppendLine(); - - // 注册到框架 - builder.AddComment("Register complete AssemblyManifest to the framework"); - builder.AppendLine("#if FANTASY_NET", false); - builder.AppendLine("Fantasy.Assembly.AssemblyManifest.Register("); - builder.Indent(); - builder.AppendLine("_assemblyManifestId,"); - builder.AppendLine("assembly,"); - builder.AppendLine("networkProtocolRegistrar,"); - builder.AppendLine("eventSystemRegistrar,"); - builder.AppendLine("entitySystemRegistrar,"); - builder.AppendLine("messageHandlerResolverRegistrar,"); - builder.AppendLine("entityTypeCollectionRegistrar,"); - builder.AppendLine("separateTableRegistrar,"); - builder.AppendLine("networkProtocolOpCodeResolverRegistrar,"); - builder.AppendLine("networkProtocolResponseTypeResolverRegistrar);"); - builder.Unindent(); - builder.AppendLine("#endif", false); - builder.AppendLine("#if FANTASY_UNITY", false); - builder.AppendLine("Fantasy.Assembly.AssemblyManifest.Register("); - builder.Indent(); - builder.AppendLine("_assemblyManifestId,"); - builder.AppendLine("assembly,"); - builder.AppendLine("networkProtocolRegistrar,"); - builder.AppendLine("eventSystemRegistrar,"); - builder.AppendLine("entitySystemRegistrar,"); - builder.AppendLine("messageHandlerResolverRegistrar,"); - builder.AppendLine("entityTypeCollectionRegistrar,"); - builder.AppendLine("networkProtocolOpCodeResolverRegistrar,"); - builder.AppendLine("networkProtocolResponseTypeResolverRegistrar);"); - builder.Unindent(); - builder.AppendLine("#endif", false); - builder.EndMethod(); - } - - private static void GenerateUnloadMethod(SourceCodeBuilder builder, bool isUnity) - { - builder.AppendLine(); - builder.AddXmlComment("Called when AssemblyLoadContext is unloading (for hot-reload support)"); - - // Unity 环境下,AssemblyLoadContext 仅在非编辑器/非独立平台可用 - if (isUnity) - { - builder.AppendLine("#if !UNITY_EDITOR && !UNITY_STANDALONE && !UNITY_ANDROID && !UNITY_IOS && !UNITY_WEBGL"); - } - - builder.BeginMethod("private static void OnAssemblyUnloading(System.Runtime.Loader.AssemblyLoadContext context)"); - - builder.AddComment("Unregister from framework"); - builder.AppendLine("if (_assemblyManifestId != 0)"); - builder.OpenBrace(); - builder.AppendLine("Fantasy.Assembly.AssemblyManifest.Unregister(_assemblyManifestId);"); - builder.CloseBrace(); - - builder.EndMethod(); - - if (isUnity) - { - builder.AppendLine("#endif"); - } - } - - private static void GenerateTryCreateRegistrar( - SourceCodeBuilder builder, - string registrarName, - string variableName) - { - var typeName = $"Fantasy.Generated.{registrarName}Registrar"; - builder.AppendLine($"{variableName} = new {typeName}();"); - } - } -} diff --git a/Fantasy/Fantasy.Net/Fantasy.SourceGenerator/Generators/EntitySystemGenerator.cs b/Fantasy/Fantasy.Net/Fantasy.SourceGenerator/Generators/EntitySystemGenerator.cs deleted file mode 100644 index b2e0f51..0000000 --- a/Fantasy/Fantasy.Net/Fantasy.SourceGenerator/Generators/EntitySystemGenerator.cs +++ /dev/null @@ -1,338 +0,0 @@ -using Fantasy.SourceGenerator.Common; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using System.Collections.Generic; -using System.Linq; -using System.Text; -#pragma warning disable CS8602 // Dereference of a possibly null reference. - -namespace Fantasy.SourceGenerator.Generators -{ - /// - /// Entity System 注册代码生成器 - /// 自动生成 EntityComponent 所需的 System 注册代码,替代运行时反射 - /// - [Generator] - public partial class EntitySystemGenerator : IIncrementalGenerator - { - public void Initialize(IncrementalGeneratorInitializationContext context) - { - // 查找所有实现了 IEntitySystem 相关接口的类 - var systemTypes = context.SyntaxProvider - .CreateSyntaxProvider( - predicate: static (node, _) => IsSystemClass(node), - transform: static (ctx, _) => GetSystemTypeInfo(ctx)) - .Where(static info => info != null) - .Collect(); - // 组合编译信息和找到的类型 - var compilationAndTypes = context.CompilationProvider.Combine(systemTypes); - // 注册源代码输出 - context.RegisterSourceOutput(compilationAndTypes, static (spc, source) => - { - // 检查1: 是否定义了 FANTASY_NET 或 FANTASY_UNITY 预编译符号 - if (!CompilationHelper.HasFantasyDefine(source.Left)) - { - return; - } - - // 检查2: 是否引用了 Fantasy 框架的核心类型 - if (source.Left.GetTypeByMetadataName("Fantasy.Assembly.IEntitySystemRegistrar") == null) - { - return; - } - - GenerateRegistrationCode(spc, source.Left, source.Right!); - }); - } - - private static EntitySystemTypeInfo? GetSystemTypeInfo(GeneratorSyntaxContext context) - { - var classDecl = (ClassDeclarationSyntax)context.Node; - - if (context.SemanticModel.GetDeclaredSymbol(classDecl) is not INamedTypeSymbol symbol || !symbol.IsInstantiable()) - { - return null; - } - - var baseType = symbol.BaseType; - - if (!baseType.IsGenericType) - { - return null; - } - - return baseType.Name switch - { - "AwakeSystem" => EntitySystemTypeInfo.Create(EntitySystemType.AwakeSystem, symbol), - "UpdateSystem" => EntitySystemTypeInfo.Create(EntitySystemType.UpdateSystem, symbol), - "DestroySystem" => EntitySystemTypeInfo.Create(EntitySystemType.DestroySystem, symbol), - "DeserializeSystem" => EntitySystemTypeInfo.Create(EntitySystemType.DeserializeSystem, symbol), - "LateUpdateSystem" => EntitySystemTypeInfo.Create(EntitySystemType.LateUpdateSystem, symbol), - _ => null - }; - } - - private static void GenerateRegistrationCode( - SourceProductionContext context, - Compilation compilation, - IEnumerable systemTypes) - { - var entitySystemTypeInfos = systemTypes.ToList(); - // 获取当前程序集名称(仅用于注释) - var assemblyName = compilation.AssemblyName ?? "Unknown"; - // 生成代码文件 - var builder = new SourceCodeBuilder(); - // 添加文件头 - builder.AppendLine(GeneratorConstants.AutoGeneratedHeader); - // 添加 using - builder.AddUsings( - "System", - "System.Collections.Generic", - "Fantasy.Assembly", - "Fantasy.Entitas", - "Fantasy.Entitas.Interface" - ); - builder.AppendLine(); - // 开始命名空间(固定使用 Fantasy.Generated) - builder.BeginNamespace("Fantasy.Generated"); - // 开始类定义(实现 IEntitySystemRegistrar 接口) - builder.AddXmlComment($"Auto-generated Entity System registration class for {assemblyName}"); - builder.BeginClass("EntitySystemRegistrar", "internal sealed", "IEntitySystemRegistrar"); - // 生成字段用于存储 System 实例 - GenerateFields(builder, entitySystemTypeInfos); - // 生成注册方法 - GenerateRegisterMethod(builder, entitySystemTypeInfos); - // 生成反注册方法 - GenerateUnRegisterMethod(builder, entitySystemTypeInfos); - // 结束类和命名空间 - builder.EndClass(); - builder.EndNamespace(); - // 输出源代码 - context.AddSource("EntitySystemRegistrar.g.cs", builder.ToString()); - } - - private static void GenerateFields(SourceCodeBuilder builder, List entitySystemTypeInfos) - { - builder.AddComment("Store registered entity system for UnRegister"); - - if (!entitySystemTypeInfos.Any()) - { - return; - } - - foreach (var eventSystemTypeInfo in entitySystemTypeInfos) - { - var fieldName = $"_{eventSystemTypeInfo.TypeName.ToCamelCase()}"; - builder.AppendLine($"private {eventSystemTypeInfo.GlobalTypeFullName} {fieldName} = new {eventSystemTypeInfo.GlobalTypeFullName}();"); - builder.AppendLine($"private const long _typeHashCode{fieldName} = {eventSystemTypeInfo.EntityTypeHashCode};"); - } - - builder.AppendLine(); - } - - private static void GenerateRegisterMethod(SourceCodeBuilder builder, List entitySystemTypeInfos) - { - builder.AddXmlComment("Register all Entity Systems to the dictionaries"); - builder.AppendLine("#if FANTASY_NET", false); - builder.BeginMethod( - "public void RegisterSystems(" + - "Dictionary> awakeSystems, " + - "Dictionary> updateSystems, " + - "Dictionary> destroySystems, " + - "Dictionary> deserializeSystems)"); - builder.AppendLine("#endif", false); - builder.Unindent(); - builder.AppendLine("#if FANTASY_UNITY", false); - builder.BeginMethod( - "public void RegisterSystems(" + - "Dictionary> awakeSystems, " + - "Dictionary> updateSystems, " + - "Dictionary> destroySystems, " + - "Dictionary> deserializeSystems, " + - "Dictionary> lateUpdateSystems)"); - builder.AppendLine("#endif", false); - - if (entitySystemTypeInfos.Any()) - { - foreach (var systemTypeInfo in entitySystemTypeInfos) - { - var fieldName = $"_{systemTypeInfo.TypeName.ToCamelCase()}"; - - switch (systemTypeInfo.EntitySystemType) - { - case EntitySystemType.AwakeSystem: - { - builder.AppendLine($"awakeSystems.Add(_typeHashCode{fieldName}, {fieldName}.Invoke);"); - continue; - } - case EntitySystemType.UpdateSystem: - { - builder.AppendLine($"updateSystems.Add(_typeHashCode{fieldName}, {fieldName}.Invoke);"); - continue; - } - case EntitySystemType.DestroySystem: - { - builder.AppendLine($"destroySystems.Add(_typeHashCode{fieldName}, {fieldName}.Invoke);"); - continue; - } - case EntitySystemType.DeserializeSystem: - { - builder.AppendLine($"deserializeSystems.Add(_typeHashCode{fieldName}, {fieldName}.Invoke);"); - continue; - } - case EntitySystemType.LateUpdateSystem: - { - builder.AppendLine("#if FANTASY_UNITY", false); - builder.AppendLine($"awakeSystems.Add(_typeHashCode{fieldName}, {fieldName}.Invoke);"); - builder.AppendLine("#endif", false); - continue; - } - } - } - } - builder.EndMethod(); - builder.AppendLine(); - } - - private static void GenerateUnRegisterMethod(SourceCodeBuilder builder, List entitySystemTypeInfos) - { - builder.AddXmlComment("Unregister all Entity Systems from the dictionaries"); - builder.AppendLine("#if FANTASY_NET", false); - builder.BeginMethod( - "public void UnRegisterSystems(" + - "Dictionary> awakeSystems, " + - "Dictionary> updateSystems, " + - "Dictionary> destroySystems, " + - "Dictionary> deserializeSystems)"); - builder.AppendLine("#endif", false); - builder.Unindent(); - builder.AppendLine("#if FANTASY_UNITY", false); - builder.BeginMethod( - "public void UnRegisterSystems(" + - "Dictionary> awakeSystems, " + - "Dictionary> updateSystems, " + - "Dictionary> destroySystems, " + - "Dictionary> deserializeSystems, " + - "Dictionary> lateUpdateSystems)"); - builder.AppendLine("#endif", false); - - if (entitySystemTypeInfos.Any()) - { - foreach (var systemTypeInfo in entitySystemTypeInfos) - { - var fieldName = $"_{systemTypeInfo.TypeName.ToCamelCase()}"; - - switch (systemTypeInfo.EntitySystemType) - { - case EntitySystemType.AwakeSystem: - { - builder.AppendLine($"awakeSystems.Remove(_typeHashCode{fieldName});"); - continue; - } - case EntitySystemType.UpdateSystem: - { - builder.AppendLine($"updateSystems.Remove(_typeHashCode{fieldName});"); - continue; - } - case EntitySystemType.DestroySystem: - { - builder.AppendLine($"destroySystems.Remove(_typeHashCode{fieldName});"); - continue; - } - case EntitySystemType.DeserializeSystem: - { - builder.AppendLine($"deserializeSystems.Remove(_typeHashCode{fieldName});"); - continue; - } - case EntitySystemType.LateUpdateSystem: - { - builder.AppendLine("#if FANTASY_UNITY", false); - builder.AppendLine($"lateUpdateSystem.Remove(_typeHashCode{fieldName});"); - builder.AppendLine("#endif", false); - continue; - } - } - } - } - - builder.EndMethod(); - builder.AppendLine(); - } - - private static bool IsSystemClass(SyntaxNode node) - { - if (node is not ClassDeclarationSyntax classDecl) - { - return false; - } - - // 必须有基类型列表(继承抽象类) - if (classDecl.BaseList == null || classDecl.BaseList.Types.Count == 0) - { - return false; - } - - // 快速检查是否包含可能的 EntitySystem 基类名称 - var baseListText = classDecl.BaseList.ToString(); - return baseListText.Contains("AwakeSystem") || - baseListText.Contains("UpdateSystem") || - baseListText.Contains("DestroySystem") || - baseListText.Contains("DeserializeSystem") || - baseListText.Contains("LateUpdateSystem"); - } - - private enum EntitySystemType - { - None, - AwakeSystem, - UpdateSystem, - DestroySystem, - DeserializeSystem, - LateUpdateSystem - } - - private sealed class EntitySystemTypeInfo - { - public readonly EntitySystemType EntitySystemType; - public readonly string GlobalTypeFullName; - public readonly string TypeFullName; - public readonly string TypeName; - public readonly long EntityTypeHashCode; // 预计算的实体类型哈希值 - public readonly string EntityTypeFullName; // 实体类型的完整名称(用于注释) - - private EntitySystemTypeInfo( - EntitySystemType entitySystemType, - string globalTypeFullName, - string typeFullName, - string typeName, - long entityTypeHashCode, - string entityTypeFullName) - { - EntitySystemType = entitySystemType; - GlobalTypeFullName = globalTypeFullName; - TypeFullName = typeFullName; - TypeName = typeName; - EntityTypeHashCode = entityTypeHashCode; - EntityTypeFullName = entityTypeFullName; - } - - public static EntitySystemTypeInfo Create(EntitySystemType entitySystemType, INamedTypeSymbol symbol) - { - // 获取泛型参数 T (例如:AwakeSystem 中的 T) - var entityType = symbol.BaseType?.TypeArguments.FirstOrDefault(); - var entityTypeFullName = entityType?.GetFullName(false) ?? "Unknown"; - - // 使用与运行时相同的算法计算哈希值 - var entityTypeHashCode = HashCodeHelper.ComputeHash64(entityTypeFullName); - - return new EntitySystemTypeInfo( - entitySystemType, - symbol.GetFullName(), - symbol.GetFullName(false), - symbol.Name, - entityTypeHashCode, - entityTypeFullName); - } - } - } -} diff --git a/Fantasy/Fantasy.Net/Fantasy.SourceGenerator/Generators/EntityTypeCollectionGenerate.cs b/Fantasy/Fantasy.Net/Fantasy.SourceGenerator/Generators/EntityTypeCollectionGenerate.cs deleted file mode 100644 index f13aa8a..0000000 --- a/Fantasy/Fantasy.Net/Fantasy.SourceGenerator/Generators/EntityTypeCollectionGenerate.cs +++ /dev/null @@ -1,180 +0,0 @@ -using Fantasy.SourceGenerator.Common; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp.Syntax; - -namespace Fantasy.SourceGenerator.Generators -{ - [Generator] - public partial class EntityTypeCollectionGenerate : IIncrementalGenerator - { - public void Initialize(IncrementalGeneratorInitializationContext context) - { - // 查找所有实现了 EventSystem 相关抽象类的类 - var protoBufTypes = context.SyntaxProvider - .CreateSyntaxProvider( - predicate: static (node, _) => IsEntityClass(node), - transform: static (ctx, _) => GetEntityTypeInfo(ctx)) - .Where(static info => info != null) - .Collect(); - // 组合编译信息和找到的类型 - var compilationAndTypes = context.CompilationProvider.Combine(protoBufTypes); - // 注册源代码输出 - context.RegisterSourceOutput(compilationAndTypes, static (spc, source) => - { - // 检查1: 是否定义了 FANTASY_NET 或 FANTASY_UNITY 预编译符号 - if (!CompilationHelper.HasFantasyDefine(source.Left)) - { - return; - } - - // 检查2: 是否引用了 Fantasy 框架的核心类型 - if (source.Left.GetTypeByMetadataName("Fantasy.Assembly.IEntityTypeCollectionRegistrar") == null) - { - return; - } - - GenerateRegistrationCode(spc, source.Left, source.Right!); - }); - } - - /// - /// 生成注册代码 - /// - private static void GenerateRegistrationCode( - SourceProductionContext context, - Compilation compilation, - IEnumerable entityTypeInfos) - { - var entityTypeList = entityTypeInfos.ToList(); - // 获取当前程序集名称(仅用于注释) - var assemblyName = compilation.AssemblyName ?? "Unknown"; - var builder = new SourceCodeBuilder(); - // 添加文件头 - builder.AppendLine(GeneratorConstants.AutoGeneratedHeader); - // 添加 using - builder.AddUsings( - "System", - "System.Collections.Generic", - "Fantasy.Assembly", - "Fantasy.DataStructure.Collection", - "Fantasy.Event" - ); - builder.AppendLine(); - // 开始命名空间(固定使用 Fantasy.Generated) - builder.BeginNamespace("Fantasy.Generated"); - // 开始类定义(实现 IEntityTypeCollectionRegistrar 接口) - builder.AddXmlComment($"Automatically generated Entity Type collection class for {assemblyName}"); - builder.BeginClass("EntityTypeCollectionRegistrar", "internal sealed", "IEntityTypeCollectionRegistrar"); - // 生成 GetEntityTypes 方法 - GetEntityTypesMethod(builder, entityTypeList); - builder.AppendLine(); - // 结束类和命名空间 - builder.EndClass(); - builder.EndNamespace(); - // 输出源代码 - context.AddSource("EntityTypeCollectionRegistrar.g.cs", builder.ToString()); - } - - private static void GetEntityTypesMethod(SourceCodeBuilder builder, List entityTypeInfos) - { - builder.AddXmlComment("All Entity Types"); - builder.BeginMethod("public List GetEntityTypes()"); - - if (entityTypeInfos.Any()) - { - builder.AppendLine($"return new List({entityTypeInfos.Count})"); - builder.AppendLine("{"); - builder.Indent(); - foreach (var system in entityTypeInfos) - { - builder.AppendLine($"typeof({system.EntityTypeFullName}),"); - } - builder.Unindent(); - builder.AppendLine("};"); - builder.AppendLine(); - } - else - { - builder.AppendLine($"return new List();"); - } - - builder.EndMethod(); - } - - private static bool IsEntityClass(SyntaxNode node) - { - if (node is not ClassDeclarationSyntax classDecl) - { - return false; - } - // 必须有基类型列表(实现接口) - return classDecl.BaseList != null && classDecl.BaseList.Types.Any(); - } - - private static EntityTypeInfo? GetEntityTypeInfo(GeneratorSyntaxContext context) - { - var classDecl = (ClassDeclarationSyntax)context.Node; - var semanticModel = context.SemanticModel; - - // 检查是否继承自 Entity 或 Entity - if (!InheritsFromEntity(classDecl, semanticModel)) - { - return null; - } - - var symbol = context.SemanticModel.GetDeclaredSymbol(classDecl) as INamedTypeSymbol; - - if (symbol == null || !symbol.IsInstantiable()) - { - return null; - } - - return new EntityTypeInfo( - symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), - symbol.Name - ); - } - - private static bool InheritsFromEntity(ClassDeclarationSyntax classDecl, SemanticModel semanticModel) - { - if (classDecl.BaseList == null) - { - return false; - } - - foreach (var baseType in classDecl.BaseList.Types) - { - var typeInfo = semanticModel.GetTypeInfo(baseType.Type); - var baseTypeSymbol = typeInfo.Type as INamedTypeSymbol; - - if (baseTypeSymbol == null) - { - continue; - } - - // 检查是否是 Entity(非泛型) - if (baseTypeSymbol.Name == "Entity" && baseTypeSymbol.Arity == 0) - { - return true; - } - - // 检查是否是 Entity(泛型) - if (baseTypeSymbol.IsGenericType) - { - var originalDef = baseTypeSymbol.OriginalDefinition; - if (originalDef.Name == "Entity" && originalDef.Arity == 1) - { - return true; - } - } - } - - return false; - } - - private sealed record EntityTypeInfo( - string EntityTypeFullName, - string EntityTypeName - ); - } -} \ No newline at end of file diff --git a/Fantasy/Fantasy.Net/Fantasy.SourceGenerator/Generators/EventSystemGenerator.cs b/Fantasy/Fantasy.Net/Fantasy.SourceGenerator/Generators/EventSystemGenerator.cs deleted file mode 100644 index fb5947b..0000000 --- a/Fantasy/Fantasy.Net/Fantasy.SourceGenerator/Generators/EventSystemGenerator.cs +++ /dev/null @@ -1,305 +0,0 @@ -using Fantasy.SourceGenerator.Common; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using System.Collections.Generic; -using System.Linq; -using System.Text; -// ReSharper disable AssignNullToNotNullAttribute - -namespace Fantasy.SourceGenerator.Generators -{ - /// - /// Event System 注册代码生成器 - /// 自动生成 EventComponent 所需的 Event System 注册代码,替代运行时反射 - /// - [Generator] - public partial class EventSystemGenerator : IIncrementalGenerator - { - public void Initialize(IncrementalGeneratorInitializationContext context) - { - // 查找所有实现了 EventSystem 相关抽象类的类 - var eventSystemTypes = context.SyntaxProvider - .CreateSyntaxProvider( - predicate: static (node, _) => IsEventSystemClass(node), - transform: static (ctx, _) => GetEventSystemTypeInfo(ctx)) - .Where(static info => info != null) - .Collect(); - // 组合编译信息和找到的类型 - var compilationAndTypes = context.CompilationProvider.Combine(eventSystemTypes); - // 注册源代码输出 - context.RegisterSourceOutput(compilationAndTypes, static (spc, source) => - { - // 检查1: 是否定义了 FANTASY_NET 或 FANTASY_UNITY 预编译符号 - if (!CompilationHelper.HasFantasyDefine(source.Left)) - { - return; - } - - // 检查2: 是否引用了 Fantasy 框架的核心类型 - if (source.Left.GetTypeByMetadataName("Fantasy.Assembly.IEventSystemRegistrar") == null) - { - return; - } - - GenerateRegistrationCode(spc, source.Left, source.Right!); - }); - } - - /// - /// 提取 EventSystem 类型信息 - /// - private static EventSystemTypeInfo? GetEventSystemTypeInfo(GeneratorSyntaxContext context) - { - var classDecl = (ClassDeclarationSyntax)context.Node; - - if (context.SemanticModel.GetDeclaredSymbol(classDecl) is not INamedTypeSymbol symbol || !symbol.IsInstantiable()) - { - return null; - } - - if (symbol.BaseType == null) - { - return null; - } - - var baseType = symbol.BaseType; - - if (!baseType.IsGenericType) - { - return null; - } - - return baseType.Name switch - { - "EventSystem" => EventSystemTypeInfo.Create(EventSystemType.EventSystem, symbol), - "AsyncEventSystem" => EventSystemTypeInfo.Create(EventSystemType.AsyncEventSystem, symbol), - "SphereEventSystem" => EventSystemTypeInfo.Create(EventSystemType.SphereEventSystem, symbol), - _ => null - }; - } - - /// - /// 生成注册代码 - /// - private static void GenerateRegistrationCode( - SourceProductionContext context, - Compilation compilation, - IEnumerable eventSystemTypes) - { - var eventSystems = eventSystemTypes.ToList(); - // 获取当前程序集名称(仅用于注释) - var assemblyName = compilation.AssemblyName ?? "Unknown"; - // 生成代码文件 - var builder = new SourceCodeBuilder(); - // 添加文件头 - builder.AppendLine(GeneratorConstants.AutoGeneratedHeader); - // 添加 using - builder.AddUsings( - "System", - "System.Collections.Generic", - "Fantasy.Assembly", - "Fantasy.DataStructure.Collection", - "Fantasy.Event" - ); - builder.AppendLine(); - // 开始命名空间(固定使用 Fantasy.Generated) - builder.BeginNamespace("Fantasy.Generated"); - // 开始类定义(实现 IEventSystemRegistrar 接口) - builder.AddXmlComment($"Auto-generated Event System registration class for {assemblyName}"); - builder.BeginClass("EventSystemRegistrar", "internal sealed", "IEventSystemRegistrar"); - // 生成字段用于存储已注册的事件处理器(用于 UnRegister) - GenerateFields(builder, eventSystems); - // 生成 RegisterSystems 方法 - GenerateRegisterMethod(builder, eventSystems); - // 生成 UnRegisterSystems 方法 - GenerateUnRegisterMethod(builder, eventSystems); - // 生成 Dispose 方法 - GenerateDisposeMethod(builder, eventSystems); - // 结束类和命名空间 - builder.EndClass(); - builder.EndNamespace(); - // 输出源代码 - context.AddSource("EventSystemRegistrar.g.cs", builder.ToString()); - } - - /// - /// 生成字段,用于存储已注册的事件处理器实例 - /// - private static void GenerateFields(SourceCodeBuilder builder, List eventSystems) - { - builder.AddComment("Store registered event handlers for UnRegister"); - - if (!eventSystems.Any()) - { - return; - } - - foreach (var eventSystemTypeInfo in eventSystems) - { - var fieldName = $"_{eventSystemTypeInfo.TypeName.ToCamelCase()}"; - builder.AppendLine($"private {eventSystemTypeInfo.TypeFullName} {fieldName} = new {eventSystemTypeInfo.TypeFullName}();"); - } - builder.AppendLine(); - } - - /// - /// 生成 RegisterSystems 方法 - /// - private static void GenerateRegisterMethod(SourceCodeBuilder builder, List eventSystems) - { - builder.AddXmlComment("Register all Event Systems to the containers"); - builder.BeginMethod( - "public void RegisterSystems(" + - "OneToManyList events, " + - "OneToManyList asyncEvents, " + - "OneToManyList sphereEvents)"); - - if (eventSystems.Any()) - { - foreach (var eventSystemTypeInfo in eventSystems) - { - var fieldName = $"_{eventSystemTypeInfo.TypeName.ToCamelCase()}"; - - switch (eventSystemTypeInfo.EventSystemType) - { - case EventSystemType.EventSystem: - { - builder.AppendLine($"events.Add({fieldName}.EventType().TypeHandle, {fieldName});"); - continue; - } - case EventSystemType.AsyncEventSystem: - { - builder.AppendLine($"asyncEvents.Add({fieldName}.EventType().TypeHandle, {fieldName});"); - continue; - } - case EventSystemType.SphereEventSystem: - { - builder.AppendLine($"sphereEvents.Add({fieldName}.EventType().TypeHandle, {fieldName});"); - continue; - } - } - } - } - - builder.EndMethod(); - builder.AppendLine(); - } - - /// - /// 生成 UnRegisterSystems 方法 - /// - private static void GenerateUnRegisterMethod(SourceCodeBuilder builder, List eventSystems) - { - builder.AddXmlComment("Unregister all Event Systems from the containers (called on hot reload)"); - builder.BeginMethod( - "public void UnRegisterSystems(" + - "OneToManyList events, " + - "OneToManyList asyncEvents, " + - "OneToManyList sphereEvents)"); - - if (eventSystems.Any()) - { - foreach (var eventSystemTypeInfo in eventSystems) - { - var fieldName = $"_{eventSystemTypeInfo.TypeName.ToCamelCase()}"; - - switch (eventSystemTypeInfo.EventSystemType) - { - case EventSystemType.EventSystem: - { - builder.AppendLine($"events.RemoveValue({fieldName}.EventType().TypeHandle, {fieldName});"); - continue; - } - case EventSystemType.AsyncEventSystem: - { - builder.AppendLine($"asyncEvents.RemoveValue({fieldName}.EventType().TypeHandle, {fieldName});"); - continue; - } - case EventSystemType.SphereEventSystem: - { - builder.AppendLine($"sphereEvents.RemoveValue({fieldName}.EventType().TypeHandle, {fieldName});"); - continue; - } - } - } - } - - builder.EndMethod(); - builder.AppendLine(); - } - - /// - /// 生成 Dispose 方法 - /// - private static void GenerateDisposeMethod(SourceCodeBuilder builder, List eventSystems) - { - builder.AddXmlComment("Dispose all resources"); - builder.BeginMethod("public void Dispose()"); - builder.AddComment("Clear all references"); - - if (eventSystems.Any()) - { - foreach (var eventSystemTypeInfo in eventSystems) - { - var fieldName = $"_{eventSystemTypeInfo.TypeName.ToCamelCase()}"; - builder.AppendLine($"{fieldName} = null;"); - } - } - - builder.EndMethod(); - } - - /// - /// 快速判断语法节点是否可能是 EventSystem 类 - /// - private static bool IsEventSystemClass(SyntaxNode node) - { - if (node is not ClassDeclarationSyntax classDecl) - { - return false; - } - - // 必须有基类型列表(继承抽象类) - if (classDecl.BaseList == null || classDecl.BaseList.Types.Count == 0) - { - return false; - } - - // 快速检查是否包含可能的 EventSystem 基类名称 - var baseListText = classDecl.BaseList.ToString(); - return baseListText.Contains("AwakeSystem") || - baseListText.Contains("AsyncEventSystem") || - baseListText.Contains("SphereEventSystem"); - } - - private enum EventSystemType - { - None, - EventSystem, // EventSystem 事件类 - AsyncEventSystem, // AsyncEventSystem 事件类 - SphereEventSystem, // SphereEventSystem 事件类 - } - - private sealed class EventSystemTypeInfo - { - public readonly EventSystemType EventSystemType; - public readonly string TypeFullName; - public readonly string TypeName; - - private EventSystemTypeInfo(EventSystemType eventSystemType, string typeFullName, string typeName) - { - EventSystemType = eventSystemType; - TypeFullName = typeFullName; - TypeName = typeName; - } - - public static EventSystemTypeInfo Create(EventSystemType eventSystemType, INamedTypeSymbol symbol) - { - return new EventSystemTypeInfo( - eventSystemType, - symbol.GetFullName(), - symbol.Name); - } - } - } -} diff --git a/Fantasy/Fantasy.Net/Fantasy.SourceGenerator/Generators/MessageHandlerGenerator.cs b/Fantasy/Fantasy.Net/Fantasy.SourceGenerator/Generators/MessageHandlerGenerator.cs deleted file mode 100644 index dfd8840..0000000 --- a/Fantasy/Fantasy.Net/Fantasy.SourceGenerator/Generators/MessageHandlerGenerator.cs +++ /dev/null @@ -1,379 +0,0 @@ -using Fantasy.SourceGenerator.Common; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp.Syntax; - -namespace Fantasy.SourceGenerator.Generators -{ - [Generator] - public sealed class MessageHandlerGenerator : IIncrementalGenerator - { - public void Initialize(IncrementalGeneratorInitializationContext context) - { - // 查找所有实现了消息相关接口的类 - var messageTypes = context.SyntaxProvider - .CreateSyntaxProvider( - predicate: static (node, _) => IsMessageHandlerClass(node), - transform: static (ctx, _) => GetMessageTypeInfo(ctx)) - .Where(static info => info != null) - .Collect(); - // 组合编译信息和找到的类型 - var compilationAndTypes = context.CompilationProvider.Combine(messageTypes); - // 注册源代码输出 - context.RegisterSourceOutput(compilationAndTypes, static (spc, source) => - { - // 检查1: 是否定义了 FANTASY_NET 或 FANTASY_UNITY 预编译符号 - if (!CompilationHelper.HasFantasyDefine(source.Left)) - { - return; - } - - // 检查2: 是否引用了 Fantasy 框架的核心类型 - if (source.Left.GetTypeByMetadataName("Fantasy.Assembly.INetworkProtocolRegistrar") == null) - { - return; - } - - GenerateRegistrationCode(spc, source.Left, source.Right!); - }); - } - - private static void GenerateRegistrationCode( - SourceProductionContext context, - Compilation compilation, - IEnumerable messageHandlerInfos) - { - var messageHandlers = new List(); - var routeMessageHandlers = new List(); - - foreach (var messageHandlerInfo in messageHandlerInfos) - { - switch (messageHandlerInfo.HandlerType) - { - case HandlerType.MessageHandler: - { - messageHandlers.Add(messageHandlerInfo); - break; - } - case HandlerType.RouteMessageHandler: - { - routeMessageHandlers.Add(messageHandlerInfo); - break; - } - } - } - - var assemblyName = compilation.AssemblyName ?? "Unknown"; - var builder = new SourceCodeBuilder(); - builder.AppendLine(GeneratorConstants.AutoGeneratedHeader); - builder.AddUsings( - "System", - "System.Collections.Generic", - "Fantasy.Assembly", - "Fantasy.DataStructure.Dictionary", - "Fantasy.Network.Interface", - "Fantasy.Network", - "Fantasy.Entitas", - "Fantasy.Async", - "System.Runtime.CompilerServices" - ); - builder.AppendLine(); - builder.BeginNamespace("Fantasy.Generated"); - builder.AddXmlComment($"Auto-generated message handler registration class for {assemblyName}"); - builder.BeginClass("MessageHandlerResolverRegistrar", "internal sealed", "IMessageHandlerResolver"); - // 生成字段用于存储已注册的实例(用于 UnRegister) - GenerateFields(builder, messageHandlers, routeMessageHandlers); - // 生成 Register 方法 - GenerateRegistrationCode(builder, messageHandlers, routeMessageHandlers); - // 结束类和命名空间 - builder.EndClass(); - builder.EndNamespace(); - // 输出源代码 - context.AddSource("MessageHandlerResolverRegistrar.g.cs", builder.ToString()); - } - - private static void GenerateFields(SourceCodeBuilder builder, List messageHandlers, List routeMessageHandlers) - { - foreach (var messageHandlerInfo in messageHandlers) - { - builder.AppendLine($"private Func message_{messageHandlerInfo.TypeName} = new {messageHandlerInfo.TypeFullName}().Handle;"); - } - - foreach (var messageHandlerInfo in routeMessageHandlers) - { - builder.AppendLine($"private Func routeMessage_{messageHandlerInfo.TypeName} = new {messageHandlerInfo.TypeFullName}().Handle;"); - } - - builder.AppendLine(); - } - - private static void GenerateRegistrationCode(SourceCodeBuilder builder, List messageHandlers, List routeMessageHandlers) - { - builder.AppendLine("[MethodImpl(MethodImplOptions.AggressiveInlining)]"); - builder.BeginMethod("public int GetMessageHandlerCount()"); - builder.AppendLine($"return {messageHandlers.Count};"); - builder.EndMethod(); - builder.AppendLine("[MethodImpl(MethodImplOptions.AggressiveInlining)]"); - builder.BeginMethod("public int GetRouteMessageHandlerCount()"); - builder.AppendLine($"return {routeMessageHandlers.Count};"); - builder.EndMethod(); - builder.AppendLine("[MethodImpl(MethodImplOptions.AggressiveInlining)]"); - builder.BeginMethod("public bool MessageHandler(Session session, uint rpcId, uint protocolCode, object message)"); - if (messageHandlers.Any()) - { - builder.AppendLine("switch (protocolCode)"); - builder.AppendLine("{"); - builder.Indent(); - foreach (var messageHandlerInfo in messageHandlers) - { - builder.AppendLine($"case {messageHandlerInfo.OpCode}:"); - builder.AppendLine("{"); - builder.Indent(); - builder.AppendLine($"message_{messageHandlerInfo.TypeName}(session, rpcId, protocolCode, message).Coroutine();"); - builder.AppendLine($"return true;"); - builder.Unindent(); - builder.AppendLine("}"); - } - builder.AppendLine("default:"); - builder.AppendLine("{"); - builder.Indent(); - builder.AppendLine($"return false;"); - builder.Unindent(); - builder.AppendLine("}"); - builder.Unindent(); - builder.AppendLine("}"); - } - else - { - builder.AppendLine($"return false;"); - } - builder.EndMethod(); - - builder.AppendLine("#if FANTASY_NET", false); - builder.AppendLine("[MethodImpl(MethodImplOptions.AggressiveInlining)]"); - builder.BeginMethod("public async FTask RouteMessageHandler(Session session, Entity entity, uint rpcId, uint protocolCode, object message)"); - if (routeMessageHandlers.Any()) - { - builder.AppendLine("switch (protocolCode)"); - builder.AppendLine("{"); - builder.Indent(); - foreach (var routeMessageHandler in routeMessageHandlers) - { - builder.AppendLine($"case {routeMessageHandler.OpCode}:"); - builder.AppendLine("{"); - builder.Indent(); - builder.AppendLine($"await routeMessage_{routeMessageHandler.TypeName}(session, entity, rpcId, message);"); - builder.AppendLine($"return true;"); - builder.Unindent(); - builder.AppendLine("}"); - } - builder.AppendLine("default:"); - builder.AppendLine("{"); - builder.Indent(); - builder.AppendLine($"await FTask.CompletedTask;"); - builder.AppendLine($"return false;"); - builder.Unindent(); - builder.AppendLine("}"); - builder.Unindent(); - builder.AppendLine("}"); - } - else - { - builder.AppendLine($"await FTask.CompletedTask;"); - builder.AppendLine($"return false;"); - } - builder.EndMethod(); - builder.AppendLine("#endif", false); - } - - private static bool IsMessageHandlerClass(SyntaxNode node) - { - if (node is not ClassDeclarationSyntax classDecl) - { - return false; - } - - if (classDecl.BaseList == null || !classDecl.BaseList.Types.Any()) - { - return false; - } - - foreach (var baseType in classDecl.BaseList.Types) - { - var typeName = baseType.Type.ToString(); - - if (typeName.Contains("IMessageHandler") || - typeName.Contains("IRouteMessageHandler") || - typeName.Contains("Message<") || - typeName.Contains("MessageRPC<") || - typeName.Contains("Route<") || - typeName.Contains("RouteRPC<") || - typeName.Contains("Addressable<") || - typeName.Contains("AddressableRPC<") || - typeName.Contains("Roaming<") || - typeName.Contains("RoamingRPC<")) - { - return true; - } - } - - return false; - } - - private static MessageHandlerInfo? GetMessageTypeInfo(GeneratorSyntaxContext context) - { - var classDecl = (ClassDeclarationSyntax)context.Node; - - if (context.SemanticModel.GetDeclaredSymbol(classDecl) is not INamedTypeSymbol symbol || - !symbol.IsInstantiable()) - { - return null; - } - - var baseType = symbol.BaseType; - - if (baseType is not { IsGenericType: true } || baseType.TypeArguments.Length <= 0) - { - return null; - } - - var baseTypeName = baseType.OriginalDefinition.ToDisplayString(); - - switch (baseTypeName) - { - case "Fantasy.Network.Interface.Message": - case "Fantasy.Network.Interface.MessageRPC": - { - return new MessageHandlerInfo( - HandlerType.MessageHandler, - symbol.GetFullName(), - symbol.Name, - GetOpCode(context, baseType, 0)); - } - case "Fantasy.Network.Interface.Route": - case "Fantasy.Network.Interface.RouteRPC": - case "Fantasy.Network.Interface.Addressable": - case "Fantasy.Network.Interface.AddressableRPC": - case "Fantasy.Network.Interface.Roaming": - case "Fantasy.Network.Interface.RoamingRPC": - { - return new MessageHandlerInfo( - HandlerType.RouteMessageHandler, - symbol.GetFullName(), - symbol.Name, - GetOpCode(context, baseType, 1)); - } - } - - return null; - } - - private static uint? GetOpCode(GeneratorSyntaxContext context, INamedTypeSymbol baseType, int index) - { - if (baseType.TypeArguments.Length <= index) - { - return null; - } - - var messageType = (INamedTypeSymbol)baseType.TypeArguments[index]; - var messageName = messageType.Name; - var compilation = context.SemanticModel.Compilation; - - // 策略1:从消息类型所在程序集中搜索 OpCode 类 - var messageAssembly = messageType.ContainingAssembly; - var namespaceName = messageType.ContainingNamespace.ToDisplayString(); - - // 遍历程序集中的所有类型,查找 OuterOpcode 或 InnerOpcode - var opCodeTypeNames = new[] { "OuterOpcode", "InnerOpcode" }; - foreach (var opCodeTypeName in opCodeTypeNames) - { - var opCodeType = FindTypeInAssembly(messageAssembly.GlobalNamespace, namespaceName, opCodeTypeName); - if (opCodeType != null) - { - var opCodeField = opCodeType.GetMembers(messageName).OfType().FirstOrDefault(); - if (opCodeField != null && opCodeField.IsConst && opCodeField.ConstantValue is uint constValue) - { - return constValue; - } - } - } - - // 策略2:如果策略1失败,尝试从 OpCode() 方法的语法树中解析(仅适用于同项目中的消息) - var opCodeMethod = messageType.GetMembers("OpCode").OfType().FirstOrDefault(); - if (opCodeMethod != null) - { - var opCodeSyntax = opCodeMethod.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax() as MethodDeclarationSyntax; - if (opCodeSyntax?.Body != null) - { - var returnStatement = opCodeSyntax.Body.DescendantNodes() - .OfType() - .FirstOrDefault(); - - if (returnStatement?.Expression != null) - { - var syntaxTree = opCodeSyntax.SyntaxTree; - - if (compilation.ContainsSyntaxTree(syntaxTree)) - { - var semanticModel = compilation.GetSemanticModel(syntaxTree); - - // 尝试符号解析 - var symbolInfo = semanticModel.GetSymbolInfo(returnStatement.Expression); - if (symbolInfo.Symbol is IFieldSymbol fieldSymbol && fieldSymbol.IsConst && fieldSymbol.ConstantValue is uint constValue2) - { - return constValue2; - } - - // 尝试常量值解析 - var constantValue = semanticModel.GetConstantValue(returnStatement.Expression); - if (constantValue.HasValue && constantValue.Value is uint uintValue) - { - return uintValue; - } - } - } - } - } - - return null; - } - - // 辅助方法:在程序集的命名空间中递归查找指定类型 - private static INamedTypeSymbol? FindTypeInAssembly(INamespaceSymbol namespaceSymbol, string targetNamespace, string typeName) - { - // 如果当前命名空间匹配目标命名空间,查找类型 - if (namespaceSymbol.ToDisplayString() == targetNamespace) - { - var type = namespaceSymbol.GetTypeMembers(typeName).FirstOrDefault(); - if (type != null) - { - return type; - } - } - - // 递归搜索子命名空间 - foreach (var childNamespace in namespaceSymbol.GetNamespaceMembers()) - { - var result = FindTypeInAssembly(childNamespace, targetNamespace, typeName); - if (result != null) - { - return result; - } - } - - return null; - } - - private enum HandlerType - { - None, - MessageHandler, - RouteMessageHandler - } - - private sealed record MessageHandlerInfo( - HandlerType HandlerType, - string TypeFullName, - string TypeName, - uint? OpCode); - } -} \ No newline at end of file diff --git a/Fantasy/Fantasy.Net/Fantasy.SourceGenerator/Generators/NetworkProtocolGenerator.cs b/Fantasy/Fantasy.Net/Fantasy.SourceGenerator/Generators/NetworkProtocolGenerator.cs deleted file mode 100644 index b6cb164..0000000 --- a/Fantasy/Fantasy.Net/Fantasy.SourceGenerator/Generators/NetworkProtocolGenerator.cs +++ /dev/null @@ -1,398 +0,0 @@ -using System.Linq; -using System.Text; -using Fantasy.SourceGenerator.Common; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp.Syntax; -#pragma warning disable CS0649 // Field is never assigned to, and will always have its default value -#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. - -namespace Fantasy.SourceGenerator.Generators; -[Generator] -internal partial class NetworkProtocolGenerator : IIncrementalGenerator -{ - public void Initialize(IncrementalGeneratorInitializationContext context) - { - var networkProtocols = context.SyntaxProvider - .CreateSyntaxProvider( - predicate: static (node, _) => IsNetworkProtocolClass(node), - transform: static (ctx, _) => GetNetworkProtocolInfo(ctx)) - .Where(static info => info != null) - .Collect(); - - var compilationAndTypes = context.CompilationProvider.Combine(networkProtocols); - - context.RegisterSourceOutput(compilationAndTypes, static (spc, source) => - { - if (!CompilationHelper.HasFantasyDefine(source.Left)) - { - return; - } - - if (source.Left.GetTypeByMetadataName("Fantasy.Assembly.INetworkProtocolRegistrar") == null) - { - return; - } - - GenerateCode(spc, source.Left, source.Right!); - }); - } - - #region GenerateCode - - private static void GenerateCode( - SourceProductionContext context, - Compilation compilation, - IEnumerable networkProtocolTypeInfos) - { - var networkProtocolTypeInfoList = networkProtocolTypeInfos.ToList(); - // 获取当前程序集名称(仅用于注释) - var assemblyName = compilation.AssemblyName ?? "Unknown"; - // 生成网络网络协议类型注册的类。 - GenerateNetworkProtocolTypesCode(context, assemblyName, networkProtocolTypeInfoList); - // 生成OpCode辅助方法。 - GenerateNetworkProtocolOpCodeResolverCode(context, assemblyName, networkProtocolTypeInfoList); - // 生成Request消息的ResponseType辅助方法。 - GenerateNetworkProtocolResponseTypesResolverCode(context, assemblyName, networkProtocolTypeInfoList); - } - - private static void GenerateNetworkProtocolTypesCode( - SourceProductionContext context, - string assemblyName, - List networkProtocolTypeInfoList) - { - var builder = new SourceCodeBuilder(); - // 添加文件头 - builder.AppendLine(GeneratorConstants.AutoGeneratedHeader); - // 添加 using - builder.AddUsings( - "System", - "System.Collections.Generic", - "Fantasy.Assembly", - "Fantasy.DataStructure.Collection" - ); - // 开始命名空间(固定使用 Fantasy.Generated) - builder.BeginNamespace("Fantasy.Generated"); - // 开始类定义(实现 IEventSystemRegistrar 接口) - builder.AddXmlComment($"Auto-generated NetworkProtocol registration class for {assemblyName}"); - builder.BeginClass("NetworkProtocolRegistrar", "internal sealed", "INetworkProtocolRegistrar"); - builder.BeginMethod("public List GetNetworkProtocolTypes()"); - - if (networkProtocolTypeInfoList.Any()) - { - builder.AppendLine($"return new List({networkProtocolTypeInfoList.Count})"); - builder.AppendLine("{"); - builder.Indent(); - foreach (var system in networkProtocolTypeInfoList) - { - builder.AppendLine($"typeof({system.FullName}),"); - } - builder.Unindent(); - builder.AppendLine("};"); - builder.AppendLine(); - } - else - { - builder.AppendLine($"return new List();"); - } - - builder.EndMethod(); - builder.AppendLine(); - // 结束类和命名空间 - builder.EndClass(); - builder.EndNamespace(); - // 输出源代码 - context.AddSource("NetworkProtocolRegistrar.g.cs", builder.ToString()); - } - - private static void GenerateNetworkProtocolOpCodeResolverCode( - SourceProductionContext context, - string assemblyName, - List networkProtocolTypeInfoList) - { - var routeTypeInfos = networkProtocolTypeInfoList.Where(d => d.RouteType.HasValue).ToList(); - var builder = new SourceCodeBuilder(); - // 添加文件头 - builder.AppendLine(GeneratorConstants.AutoGeneratedHeader); - // 添加 using - builder.AddUsings( - "System", - "System.Collections.Generic", - "Fantasy.Assembly", - "Fantasy.DataStructure.Collection", - "System.Runtime.CompilerServices" - ); - builder.AppendLine(); - // 开始命名空间(固定使用 Fantasy.Generated) - builder.BeginNamespace("Fantasy.Generated"); - // 开始类定义(实现 INetworkProtocolOpCodeResolver 接口) - builder.AddXmlComment($"Auto-generated NetworkProtocolOpCodeResolverRegistrar class for {assemblyName}"); - builder.BeginClass("NetworkProtocolOpCodeResolverRegistrar", "internal sealed", "INetworkProtocolOpCodeResolver"); - builder.AddXmlComment($"GetOpCodeCount"); - builder.AppendLine("[MethodImpl(MethodImplOptions.AggressiveInlining)]"); - builder.BeginMethod("public int GetOpCodeCount()"); - builder.AppendLine($"return {networkProtocolTypeInfoList.Count};"); - builder.EndMethod(); - builder.AddXmlComment($"GetCustomRouteTypeCount"); - builder.AppendLine("[MethodImpl(MethodImplOptions.AggressiveInlining)]"); - builder.BeginMethod("public int GetCustomRouteTypeCount()"); - builder.AppendLine($"return {routeTypeInfos.Count};"); - builder.EndMethod(); - // 开始定义GetOpCodeType方法 - builder.AddXmlComment($"GetOpCodeType"); - builder.AppendLine("[MethodImpl(MethodImplOptions.AggressiveInlining)]"); - builder.BeginMethod("public Type GetOpCodeType(uint opCode)"); - - if (networkProtocolTypeInfoList.Any()) - { - builder.AppendLine("switch (opCode)"); - builder.AppendLine("{"); - builder.Indent(); - foreach (var networkProtocolTypeInfo in networkProtocolTypeInfoList) - { - builder.AppendLine($"case {networkProtocolTypeInfo.OpCode}:"); - builder.AppendLine("{"); - builder.Indent(); - builder.AppendLine($"return typeof({networkProtocolTypeInfo.FullName});"); - builder.Unindent(); - builder.AppendLine("}"); - } - builder.AppendLine("default:"); - builder.AppendLine("{"); - builder.Indent(); - builder.AppendLine($"return null!;"); - builder.Unindent(); - builder.AppendLine("}"); - builder.Unindent(); - builder.AppendLine("}"); - builder.AppendLine(); - } - else - { - builder.AppendLine($"return null!;"); - } - - builder.EndMethod(); - - // 开始定义GetRouteType方法 - builder.AddXmlComment($"CustomRouteType"); - builder.AppendLine("[MethodImpl(MethodImplOptions.AggressiveInlining)]"); - builder.BeginMethod("public int? GetCustomRouteType(uint opCode)"); - - if (routeTypeInfos.Any()) - { - builder.AppendLine("switch (opCode)"); - builder.AppendLine("{"); - builder.Indent(); - foreach (var routeTypeInfo in routeTypeInfos) - { - builder.AppendLine($"case {routeTypeInfo.OpCode}:"); - builder.AppendLine("{"); - builder.Indent(); - builder.AppendLine($"return {routeTypeInfo.RouteType};"); - builder.Unindent(); - builder.AppendLine("}"); - } - builder.AppendLine("default:"); - builder.AppendLine("{"); - builder.Indent(); - builder.AppendLine($"return null;"); - builder.Unindent(); - builder.AppendLine("}"); - builder.Unindent(); - builder.AppendLine("}"); - builder.AppendLine(); - } - else - { - builder.AppendLine($"return null;"); - } - - builder.EndMethod(); - builder.AppendLine(); - // 结束类和命名空间 - builder.EndClass(); - builder.EndNamespace(); - // 输出源代码 - context.AddSource("NetworkProtocolOpCodeResolverRegistrar.g.cs", builder.ToString()); - } - - private static void GenerateNetworkProtocolResponseTypesResolverCode( - SourceProductionContext context, - string assemblyName, - List networkProtocolTypeInfoList) - { - var requestList = networkProtocolTypeInfoList.Where(d => d.ResponseType != null).ToList(); - var builder = new SourceCodeBuilder(); - // 添加文件头 - builder.AppendLine(GeneratorConstants.AutoGeneratedHeader); - // 添加 using - builder.AddUsings( - "System", - "System.Collections.Generic", - "Fantasy.Assembly", - "Fantasy.DataStructure.Collection", - "System.Runtime.CompilerServices" - ); - builder.AppendLine(); - // 开始命名空间(固定使用 Fantasy.Generated) - builder.BeginNamespace("Fantasy.Generated"); - // 开始类定义(实现 IEventSystemRegistrar 接口) - builder.AddXmlComment($"Auto-generated NetworkProtocolResponseTypeResolverRegistrar class for {assemblyName}"); - builder.BeginClass("NetworkProtocolResponseTypeResolverRegistrar", "internal sealed", "INetworkProtocolResponseTypeResolver"); - builder.AddXmlComment($"GetOpCodeCount"); - builder.AppendLine("[MethodImpl(MethodImplOptions.AggressiveInlining)]"); - builder.BeginMethod("public int GetRequestCount()"); - builder.AppendLine($"return {requestList.Count};"); - builder.EndMethod(); - // 开始定义GetOpCodeType方法 - builder.AddXmlComment($"GetOpCodeType"); - builder.AppendLine("[MethodImpl(MethodImplOptions.AggressiveInlining)]"); - builder.BeginMethod("public Type GetResponseType(uint opCode)"); - - if (requestList.Any()) - { - builder.AppendLine("switch (opCode)"); - builder.AppendLine("{"); - builder.Indent(); - foreach (var request in requestList) - { - builder.AppendLine($"case {request.OpCode}:"); - builder.AppendLine("{"); - builder.Indent(); - builder.AppendLine($"return typeof({request.ResponseType});"); - builder.Unindent(); - builder.AppendLine("}"); - } - builder.AppendLine("default:"); - builder.AppendLine("{"); - builder.Indent(); - builder.AppendLine($"return null!;"); - builder.Unindent(); - builder.AppendLine("}"); - builder.Unindent(); - builder.AppendLine("}"); - builder.AppendLine(); - } - else - { - builder.AppendLine($"return null!;"); - } - - builder.EndMethod(); - builder.AppendLine(); - // 结束类和命名空间 - builder.EndClass(); - builder.EndNamespace(); - // 输出源代码 - context.AddSource("NetworkProtocolResponseTypeResolverRegistrar.g.cs", builder.ToString()); - } - - #endregion - - private static NetworkProtocolTypeInfo? GetNetworkProtocolInfo(GeneratorSyntaxContext context) - { - var classDecl = (ClassDeclarationSyntax)context.Node; - var symbol = context.SemanticModel.GetDeclaredSymbol(classDecl) as INamedTypeSymbol; - - if (symbol == null || !symbol.IsInstantiable()) - { - return null; - } - - var baseType = symbol.BaseType; - - if (baseType == null) - { - return null; - } - - if (baseType.ToDisplayString() != "Fantasy.Network.Interface.AMessage") - { - return null; - } - - // 获取 OpCode 方法的值 - uint? opCodeValue = null; - var opCodeMethod = symbol.GetMembers("OpCode").OfType().FirstOrDefault(); - var opCodeSyntax = opCodeMethod?.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax() as MethodDeclarationSyntax; - - if (opCodeSyntax?.Body != null) - { - var returnStatement = opCodeSyntax.Body.DescendantNodes() - .OfType() - .FirstOrDefault(); - - if (returnStatement?.Expression != null) - { - var constantValue = context.SemanticModel.GetConstantValue(returnStatement.Expression); - if (constantValue.HasValue && constantValue.Value is uint uintValue) - { - opCodeValue = uintValue; - } - } - } - - if (!opCodeValue.HasValue) - { - return null; - } - - // 获取 ResponseType 属性及其类型 - string? responseTypeName = null; - var responseTypeProperty = symbol.GetMembers("ResponseType").OfType().FirstOrDefault(); - if (responseTypeProperty != null) - { - // 获取 ResponseType 属性的类型(例如 G2C_TestResponse) - responseTypeName = responseTypeProperty.Type.GetFullName(); - } - - // 获取 RouteType 属性的值 - int? routeTypeValue = null; - var routeTypeProperty = symbol.GetMembers("RouteType").OfType().FirstOrDefault(); - var routeTypeSyntax = routeTypeProperty?.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax() as PropertyDeclarationSyntax; - if (routeTypeSyntax?.ExpressionBody != null) - { - var constantValue = context.SemanticModel.GetConstantValue(routeTypeSyntax.ExpressionBody.Expression); - if (constantValue.HasValue && constantValue.Value is int intValue) - { - routeTypeValue = intValue; - } - } - - return new NetworkProtocolTypeInfo( - symbol.GetFullName(), - opCodeValue.Value, - responseTypeName, - routeTypeValue - ); - } - - private static bool IsNetworkProtocolClass(SyntaxNode node) - { - if (node is not ClassDeclarationSyntax classDecl) - { - return false; - } - - if (classDecl.BaseList == null) - { - return false; - } - - foreach (var baseTypeSyntax in classDecl.BaseList.Types) - { - if (baseTypeSyntax.Type.ToString().Contains("AMessage")) - { - return true; - } - } - - return false; - } - - private sealed record NetworkProtocolTypeInfo( - string FullName, - uint OpCode, - string? ResponseType, - int? RouteType); -} \ No newline at end of file diff --git a/Fantasy/Fantasy.Net/Fantasy.SourceGenerator/Generators/SeparateTableGenerator.cs b/Fantasy/Fantasy.Net/Fantasy.SourceGenerator/Generators/SeparateTableGenerator.cs deleted file mode 100644 index 3a5389c..0000000 --- a/Fantasy/Fantasy.Net/Fantasy.SourceGenerator/Generators/SeparateTableGenerator.cs +++ /dev/null @@ -1,267 +0,0 @@ -using Fantasy.SourceGenerator.Common; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using System.Collections.Generic; -using System.Linq; -using System.Text; -// ReSharper disable MemberHidesStaticFromOuterClass - -namespace Fantasy.SourceGenerator.Generators -{ - /// - /// SeparateTable 接口生成器 - /// 自动生成 SeparateTable 所需的注册代码,替代运行时反射 - /// - [Generator] - public partial class SeparateTableGenerator : IIncrementalGenerator - { - private static readonly SourceCodeBuilder SeparateTableInfo = new SourceCodeBuilder(); - - public void Initialize(IncrementalGeneratorInitializationContext context) - { - // 查找所有标记了 SeparateTableAttribute 的类 - var attributedClasses = context.SyntaxProvider - .CreateSyntaxProvider( - predicate: static (node, _) => IsSeparateTableClass(node), - transform: static (ctx, _) => GetSeparateTableTypeInfo(ctx)) - .Where(static info => info != null) - .Collect(); - // 组合编译信息和找到的类型 - var compilationAndTypes = context.CompilationProvider.Combine(attributedClasses); - // 注册源代码输出 - context.RegisterSourceOutput(compilationAndTypes, static (spc, source) => - { - // 检查1: 是否定义了 FANTASY_NET 预编译符号 - if (!CompilationHelper.HasFantasyNETDefine(source.Left)) - { - return; - } - - // 检查2: 是否引用了 Fantasy 框架的核心类型 - if (source.Left.GetTypeByMetadataName("Fantasy.Entitas.Interface.ISupportedSeparateTable") == null) - { - return; - } - - GenerateRegistrationCode(spc, source.Left, source.Right!); - }); - } - - private static SeparateTableTypeInfo? GetSeparateTableTypeInfo(GeneratorSyntaxContext context) - { - var classDecl = (ClassDeclarationSyntax)context.Node; - - if (context.SemanticModel.GetDeclaredSymbol(classDecl) is not INamedTypeSymbol symbol) - { - return null; - } - - if (!symbol.InheritsFrom("Fantasy.Entitas.Entity")) - { - // 不是 Entity,不处理 - return null; - } - - // 检查是否标记了 SeparateTableAttribute - var separateTableAttr = symbol.GetAttributes() - .Where(attr => attr.AttributeClass?.ToDisplayString() == "Fantasy.SeparateTable.SeparateTableAttribute").ToList(); - - if (!separateTableAttr.Any()) - { - return null; - } - - var separateTableInfo = new Dictionary(); - - foreach (var attributeData in separateTableAttr) - { - if (attributeData.ConstructorArguments.Length < 2) - { - return null; - } - - var rootTypeSymbol = attributeData.ConstructorArguments[0].Value as INamedTypeSymbol; - var collectionName = attributeData.ConstructorArguments[1].Value?.ToString(); - - if (rootTypeSymbol == null || collectionName == null) - { - return null; - } - - separateTableInfo[rootTypeSymbol.GetFullName()] = collectionName; - } - - return separateTableInfo.Any() ? SeparateTableTypeInfo.Create(symbol, separateTableInfo) : null; - } - - private static void GenerateRegistrationCode( - SourceProductionContext context, - Compilation compilation, - IEnumerable separateTableTypeInfos) - { - SeparateTableInfo.Clear(); - var separateTableTypeInfoList = separateTableTypeInfos.ToList(); - // 获取当前程序集名称(仅用于注释) - var assemblyName = compilation.AssemblyName ?? "Unknown"; - // 生成代码文件 - var builder = new SourceCodeBuilder(); - // 添加 using - builder.AddUsings( - "System", - "System.Collections.Generic", - "Fantasy.Assembly", - "Fantasy.Entitas", - "Fantasy.Entitas.Interface", - "Fantasy.Async" - ); - builder.AppendLine(); - // 开始命名空间(固定使用 Fantasy.Generated) - builder.BeginNamespace("Fantasy.Generated"); - // 开始类定义(实现 IEntitySystemRegistrar 接口) - builder.AddXmlComment($"Auto-generated Entity System registration class for {assemblyName}"); - builder.BeginClass("SeparateTableRegistrar", "internal sealed", "ISeparateTableRegistrar"); - // 生成字段用于存储已注册(用于 UnRegister) - GenerateFields(builder, separateTableTypeInfoList); - // 生成注册方法 - GenerateRegisterMethod(builder, separateTableTypeInfoList); - // 生成反注册方法 - GenerateUnRegisterMethod(builder, separateTableTypeInfoList); - // 结束类 - builder.EndClass(); - // 生成数据库帮助类 - builder.Append(GenerateGenerateSeparateTableGeneratedExtensions().ToString()); - // 结束命名空间 - builder.EndNamespace(); - // 输出源代码 - context.AddSource("SeparateTableRegistrar.g.cs", builder.ToString()); - } - - private static SourceCodeBuilder GenerateGenerateSeparateTableGeneratedExtensions() - { - var builder = new SourceCodeBuilder(); - builder.AppendLine(); - builder.Indent(1); - builder.AddXmlComment("分表组件扩展方法。"); - builder.AppendLine("public static class SeparateTableGeneratedExtensions"); - builder.AppendLine("{"); - builder.Indent(1); - builder.AddXmlComment("从数据库加载指定实体的所有分表数据,并自动建立父子关系。"); - builder.BeginMethod("public static FTask LoadWithSeparateTables(this T entity) where T : Entity, new()"); - builder.AppendLine("return entity.Scene.SeparateTableComponent.LoadWithSeparateTables(entity);"); - builder.EndMethod(); - builder.AddXmlComment("将实体及其所有分表组件保存到数据库中。"); - builder.BeginMethod("public static FTask PersistAggregate(this T entity) where T : Entity, new()"); - builder.AppendLine("return entity.Scene.SeparateTableComponent.PersistAggregate(entity);"); - builder.EndMethod(); - builder.Unindent(); - builder.AppendLine("}"); - return builder; - } - - private static void GenerateFields(SourceCodeBuilder builder, List separateTableTypeInfoList) - { - SeparateTableInfo.AppendLine("private readonly List _separateTableInfos = new List()"); - SeparateTableInfo.Indent(2); - SeparateTableInfo.AppendLine("{"); - SeparateTableInfo.Indent(1); - - if (separateTableTypeInfoList.Any()) - { - foreach (var separateTableTypeInfo in separateTableTypeInfoList) - { - foreach (var separateTableInfo in separateTableTypeInfo.SeparateTableInfo) - { - SeparateTableInfo.AppendLine( - $"new ISeparateTableRegistrar.SeparateTableInfo(typeof({separateTableInfo.Key}) ,typeof({separateTableTypeInfo.TypeFullName}) ,\"{separateTableInfo.Value}\"),"); - } - } - } - - SeparateTableInfo.Unindent(); - SeparateTableInfo.AppendLine("};"); - builder.AppendLine(SeparateTableInfo.ToString()); - } - - private static void GenerateRegisterMethod(SourceCodeBuilder builder, List separateTableTypeInfos) - { - builder.BeginMethod("public List Register()"); - builder.AppendLine("return _separateTableInfos;"); - builder.EndMethod(); - builder.AppendLine(); - } - - private static void GenerateUnRegisterMethod(SourceCodeBuilder builder, List separateTableTypeInfos) - { - builder.BeginMethod("public List UnRegister()"); - builder.AppendLine("return _separateTableInfos;"); - builder.EndMethod(); - builder.AppendLine(); - } - - private static bool IsSeparateTableClass(SyntaxNode node) - { - if (node is not ClassDeclarationSyntax classDecl) - { - return false; - } - - // 快速检查是否有任何 Attribute - if (classDecl.AttributeLists.Count == 0) - { - return false; - } - - // 检查是否标记了 SeparateTableAttribute - // 这里只做简单的语法级别检查,精确的语义检查在 GetSeparateTableTypeInfo 中进行 - foreach (var attributeList in classDecl.AttributeLists) - { - foreach (var attribute in attributeList.Attributes) - { - var attributeName = attribute.Name.ToString(); - - // 匹配以下情况: - // 1. [SeparateTable(...)] - // 2. [SeparateTableAttribute(...)] - // 3. [Fantasy.Entitas.Interface.SeparateTable(...)] - // 4. [Fantasy.Entitas.Interface.SeparateTableAttribute(...)] - // 5. [global::Fantasy.Entitas.Interface.SeparateTable(...)] - if (attributeName == "SeparateTable" || - attributeName == "SeparateTableAttribute" || - attributeName == "Fantasy.Entitas.Interface.SeparateTable" || - attributeName == "Fantasy.Entitas.Interface.SeparateTableAttribute" || - attributeName == "global::Fantasy.Entitas.Interface.SeparateTable" || - attributeName == "global::Fantasy.Entitas.Interface.SeparateTableAttribute") - { - return true; - } - } - } - - return false; - } - - private sealed class SeparateTableTypeInfo - { - public readonly Dictionary SeparateTableInfo; - public readonly string TypeFullName; - public readonly string TypeName; - - private SeparateTableTypeInfo(string typeFullName, string typeName, - Dictionary separateTableInfo) - { - TypeFullName = typeFullName; - TypeName = typeName; - SeparateTableInfo = separateTableInfo; - } - - public static SeparateTableTypeInfo Create(INamedTypeSymbol symbol, - Dictionary separateTableInfo) - { - return new SeparateTableTypeInfo( - symbol.GetFullName(), - symbol.Name, - separateTableInfo); - } - } - } -} diff --git a/Fantasy/Fantasy.Net/Fantasy.SourceGenerator/README.md b/Fantasy/Fantasy.Net/Fantasy.SourceGenerator/README.md deleted file mode 100644 index d0ac960..0000000 --- a/Fantasy/Fantasy.Net/Fantasy.SourceGenerator/README.md +++ /dev/null @@ -1,373 +0,0 @@ -# Fantasy.SourceGenerator - -Fantasy 框架的增量源代码生成器(Incremental Source Generator),在编译时自动生成注册代码,消除反射开销,提升性能并支持 Native AOT。 - -## 功能特性 - -### ✅ 已实现的生成器 - -- **AssemblyInitializerGenerator**: 自动生成程序集初始化器(ModuleInitializer),在程序集加载时自动注册到框架 -- **EntitySystemGenerator**: 自动生成 Entity System(Awake/Update/Destroy/Deserialize/LateUpdate)注册代码 -- **EventSystemGenerator**: 自动生成 Event System(EventSystem/AsyncEventSystem/SphereEventSystem)注册代码 -- **MessageDispatcherGenerator**: 自动生成消息调度器(网络协议、消息处理器、路由处理器)注册代码 -- **EntityTypeCollectionGenerator**: 自动生成 Entity 类型集合,用于框架类型管理 -- **ProtoBufGenerator**: 自动生成 ProtoBuf 类型注册代码,用于序列化系统 - -## 使用方法 - -### 自动集成(推荐) - -Framework 已经完全集成了 Source Generator,开发者**无需手动配置**。 - -#### 工作流程 - -1. **编译时自动生成**: 当你构建项目时,Source Generator 会自动扫描代码并生成注册器 -2. **自动注册**: `AssemblyInitializerGenerator` 生成的 `ModuleInitializer` 会在程序集加载时自动注册所有生成器到框架 -3. **透明运行**: 框架内部自动使用生成的注册器,无需任何手动调用 - -#### 示例:添加一个 Entity System - -```csharp -// 只需按照 Fantasy 框架的规范编写代码 -public class MyEntityAwakeSystem : AwakeSystem -{ - protected override void Awake(MyEntity self) - { - // 你的逻辑 - } -} -``` - -编译后,Source Generator 会自动: -- 生成 `EntitySystemRegistrar.g.cs` 包含此 System -- 在程序集加载时自动注册到框架 -- 无需任何额外步骤 - -### 手动集成(高级用途) - -如果你需要在自己的项目中引用 Source Generator: - -```xml - - - -``` - -确保项目定义了 `FANTASY_NET` 或 `FANTASY_UNITY` 预编译符号: - -```xml - - FANTASY_NET - -``` - -## 生成的代码示例 - -Source Generator 会在 `obj/.../generated/Fantasy.SourceGenerator/` 目录下自动生成多个注册器类: - -### AssemblyInitializer.g.cs(核心) - -```csharp -// 程序集初始化器 - 在程序集加载时自动执行 -namespace Fantasy.Generated -{ - internal static class AssemblyInitializer - { - [ModuleInitializer] // .NET 使用 ModuleInitializer - // [RuntimeInitializeOnLoadMethod] // Unity 使用此属性 - internal static void Initialize() - { - var assembly = typeof(AssemblyInitializer).Assembly; - var assemblyManifestId = HashCodeHelper.ComputeHash64(assembly.GetName().Name); - - // 创建所有生成的注册器 - var protoBufRegistrar = new Fantasy.Generated.ProtoBufRegistrar(); - var eventSystemRegistrar = new Fantasy.Generated.EventSystemRegistrar(); - var entitySystemRegistrar = new Fantasy.Generated.EntitySystemRegistrar(); - var messageDispatcherRegistrar = new Fantasy.Generated.MessageDispatcherRegistrar(); - var entityTypeCollectionRegistrar = new Fantasy.Generated.EntityTypeCollectionRegistrar(); - - // 一次性注册到框架 - Fantasy.Assembly.AssemblyManifest.Register( - assemblyManifestId, - assembly, - protoBufRegistrar, - eventSystemRegistrar, - entitySystemRegistrar, - messageDispatcherRegistrar, - entityTypeCollectionRegistrar); - } - } - - // 用于强制加载程序集的标记类 - public static class MyAssembly_AssemblyMarker - { - public static void EnsureLoaded() { } - } -} -``` - -### EntitySystemRegistrar.g.cs - -```csharp -namespace Fantasy.Generated -{ - internal sealed class EntitySystemRegistrar : IEntitySystemRegistrar - { - public void RegisterSystems( - Dictionary awakeSystems, - Dictionary updateSystems, - // ... 其他系统类型 - ) - { - awakeSystems.Add(typeof(MyEntity), new MyEntityAwakeSystem()); - updateSystems.Add(typeof(MyEntity), new MyEntityUpdateSystem()); - // ... 自动注册所有发现的 System - } - } -} -``` - -### EventSystemRegistrar.g.cs - -```csharp -namespace Fantasy.Generated -{ - internal sealed class EventSystemRegistrar : IEventSystemRegistrar - { - private MyEventHandler _myEventHandler = new MyEventHandler(); - - public void RegisterSystems( - OneToManyList events, - OneToManyList asyncEvents, - OneToManyList sphereEvents) - { - events.Add(_myEventHandler.EventType(), _myEventHandler); - } - } -} -``` - -### MessageDispatcherRegistrar.g.cs - -```csharp -namespace Fantasy.Generated -{ - internal sealed class MessageDispatcherRegistrar : IMessageDispatcherRegistrar - { - public void RegisterSystems( - DoubleMapDictionary networkProtocols, - Dictionary responseTypes, - Dictionary messageHandlers, - // ... - ) - { - var c2GLoginRequest = new C2G_LoginRequest(); - networkProtocols.Add(c2GLoginRequest.OpCode(), typeof(C2G_LoginRequest)); - responseTypes.Add(typeof(C2G_LoginRequest), typeof(G2C_LoginResponse)); - } - } -} -``` - -## 性能对比 - -| 场景 | 反射方式 | Source Generator | 性能提升 | -|------|---------|------------------|---------| -| 程序集加载时注册 | ~50ms(100个类型) | ~1ms | **50x** | -| 启动时间 | 受反射影响 | 几乎无影响 | **显著** | -| 运行时实例化 | ~150ns/个 | ~3ns/个 | **50x** | -| 内存分配 | 反射元数据开销 | 无额外开销 | **更优** | -| Native AOT 兼容 | ❌ 不支持 | ✅ 完全支持 | - | -| IL2CPP (Unity) | ⚠️ 性能差 | ✅ 完美支持 | **必需** | - -## 调试生成的代码 - -### 查看生成的代码 - -生成的代码位于: -``` -<项目目录>/obj/<配置>/<目标框架>/generated/Fantasy.SourceGenerator/ -``` - -例如: -``` -obj/Debug/net8.0/generated/Fantasy.SourceGenerator/Fantasy.SourceGenerator.Generators.AssemblyInitializerGenerator/AssemblyInitializer.g.cs -obj/Debug/net8.0/generated/Fantasy.SourceGenerator/Fantasy.SourceGenerator.Generators.EntitySystemGenerator/EntitySystemRegistrar.g.cs -``` - -### IDE 支持 - -- **Visual Studio**: Dependencies → Analyzers → Fantasy.SourceGenerator → 展开查看生成的文件 -- **JetBrains Rider**: Dependencies → Source Generators → Fantasy.SourceGenerator -- **VS Code**: 需要手动浏览 `obj/` 目录 - -### 启用详细日志 - -在 `.csproj` 中添加: -```xml - - true - $(BaseIntermediateOutputPath)/GeneratedFiles - -``` - -这会将生成的文件输出到 `obj/GeneratedFiles/` 目录,方便查看和调试。 - -## 兼容性 - -### 平台支持 - -| 平台 | 状态 | 说明 | -|------|------|------| -| .NET 8.0+ | ✅ 完全支持 | 使用 Roslyn 4.8.0 | -| .NET Framework 4.x | ❌ 不支持 | Source Generator 需要 .NET Standard 2.0+ | -| Unity 2020.2+ | ✅ 完全支持 | 使用 Roslyn 4.0.1(兼容版本) | -| Native AOT | ✅ 完全支持 | 无反射,完美兼容 AOT | -| IL2CPP | ✅ 完全支持 | Unity IL2CPP 必需 | - -### 运行模式 - -- **开发环境**: Source Generator 自动生成,支持热重载(重新编译) -- **生产环境**: Source Generator 自动生成,零反射开销 -- **热更新程序集**: 框架会检测可收集的 AssemblyLoadContext,支持程序集卸载和重载 - -## 技术特性 - -### 增量编译支持 - -所有生成器都实现了 `IIncrementalGenerator` 接口: -- ✅ 仅在相关代码变化时重新生成 -- ✅ 大幅提升编译速度 -- ✅ 支持部分代码更新 - -### 条件编译 - -生成器会根据预编译符号调整生成代码: -- `FANTASY_NET`: .NET 平台特定功能 -- `FANTASY_UNITY`: Unity 平台特定功能 -- 自动检测平台并生成对应代码 - -### 自动清理 - -- 程序集卸载时自动反注册(支持热更新) -- 实现 `IDisposable` 接口,支持资源释放 -- 无内存泄漏风险 - -## 多版本构建支持 - -### Unity 版本 vs .NET 版本 - -此项目支持两种构建配置,使用不同版本的 Roslyn 编译器: - -#### Unity 配置(用于 Unity Package) - -```bash -dotnet build --configuration Unity -``` - -- **Roslyn 版本**: 4.0.1 -- **用途**: Unity 2020.2+ 项目 -- **兼容性**: Unity 2020.2 - Unity 2023.x -- **输出**: `bin/Unity/netstandard2.0/Fantasy.SourceGenerator.dll` - -#### Release/Debug 配置(用于 .NET 项目) - -```bash -dotnet build --configuration Release -``` - -- **Roslyn 版本**: 4.8.0 -- **用途**: .NET 8/9 项目 -- **兼容性**: .NET 8.0+ -- **输出**: `bin/Release/netstandard2.0/Fantasy.SourceGenerator.dll` - -### 为什么需要两个版本? - -Unity 使用的 Roslyn 编译器版本较旧,Source Generator 引用的 Roslyn 版本必须**小于或等于**运行时编译器版本: - -| 平台 | Roslyn 版本 | Source Generator 配置 | -|------|------------|---------------------| -| Unity 2020.2-2021.x | 3.8 - 4.0 | Unity (4.0.1) | -| Unity 2022.x | 4.3 | Unity (4.0.1) | -| Unity 2023.x | 4.6+ | Unity (4.0.1) | -| .NET 8.0 | 4.8+ | Release (4.8.0) | -| .NET 9.0 | 4.10+ | Release (4.8.0) | - -### 自动化更新脚本 - -**更新 Unity Package:** -```bash -# macOS/Linux -./update-unity-source-generator.sh - -# Windows -update-unity-source-generator.bat -``` - -这些脚本会自动使用 Unity 配置构建并更新到 Fantasy.Unity package。在项目的Tools/Update-Unity-Source-Generator下。 - -### 技术实现 - -在 `.csproj` 中使用条件 PackageReference: - -```xml - - - - - - - - - - - -``` - -### 故障排查 - -**CS9057 警告/错误:** -``` -warning CS9057: The analyzer assembly references version 'X' of the compiler, -which is newer than the currently running version 'Y'. -``` - -**解决方案**: 确保使用 Unity 配置构建用于 Unity 的版本。 - -## 常见问题(FAQ) - -### Q: 为什么看不到生成的代码? -A: 生成的代码在 `obj/` 目录下,需要编译项目后才能看到。可以在 IDE 的 Analyzers/Source Generators 节点中查看。 - -### Q: 可以禁用某个生成器吗? -A: 生成器会自动检测代码特征,如果你的项目中没有相关类型,对应的生成器不会生成代码。无需手动禁用。 - -### Q: Source Generator 会影响编译速度吗? -A: 首次编译会扫描所有代码,后续使用增量编译,仅在相关代码变化时重新生成,对编译速度影响很小。 - -### Q: Unity 项目如何使用? -A: Fantasy.Unity package 已经包含了兼容版本的 Source Generator(Roslyn 4.0.1),无需额外配置。 - -### Q: 支持热重载吗? -A: 支持。框架会检测 AssemblyLoadContext 的卸载事件,自动反注册。重新编译后自动注册新版本。 - -### Q: Native AOT 部署需要注意什么? -A: 无需特别注意,Source Generator 生成的代码不使用反射,完全兼容 Native AOT。 - -## 贡献指南 - -欢迎贡献代码!如果你想添加新的生成器: - -1. 在 `Generators/` 目录下创建新的生成器类 -2. 实现 `IIncrementalGenerator` 接口 -3. 在 `AssemblyInitializerGenerator` 中添加对应的注册器接口 -4. 更新本 README 文档 - ---- - -**维护者**: 初见 -**更新日期**: 2025-10-19 -**版本**: 1.0.0 diff --git a/Fantasy/Fantasy.Net/Fantasy.SourceGenerator/README_DEBUG.md b/Fantasy/Fantasy.Net/Fantasy.SourceGenerator/README_DEBUG.md deleted file mode 100644 index 5d6b226..0000000 --- a/Fantasy/Fantasy.Net/Fantasy.SourceGenerator/README_DEBUG.md +++ /dev/null @@ -1,42 +0,0 @@ -# Source Generator 调试指南 - -## 当前状态 - -生成器已编译成功,但没有生成任何代码文件。 - -## 可能的原因 - -1. **类型匹配问题**:生成器的 `IsSystemClass` 或 `GetSystemType` 方法无法正确识别 System 类 -2. **Incremental Generator Pipeline 问题**:Pipeline 可能过滤掉了所有候选类 -3. **命名空间不匹配**:生成器可能在寻找错误的命名空间 - -## 调试步骤 - -### 方法 1:简化生成器逻辑 - -将 `IsSystemClass` 改为返回 `true` 以查看是否有任何类被处理: - -```csharp -private static bool IsSystemClass(SyntaxNode node) -{ - return node is ClassDeclarationSyntax; // 处理所有类 -} -``` - -### 方法 2:查看生成器输出 - -```bash -# 启用详细日志 -dotnet build /p:EmitCompilerGeneratedFiles=true /p:CompilerGeneratedFilesOutputPath=Generated - -# 查看生成的文件 -ls -la Generated/ -``` - -### 方法 3:使用 LinqPad/RoslynPad 测试 - -创建独立的测试项目来验证 Roslyn API 调用。 - -## 下一步 - -建议先尝试方法 2,查看 MSBuild 是否能输出生成的文件。 diff --git a/Fantasy/Fantasy.Packages/Fantasy.ConfigTable/Net/ConfigTableHelper.cs b/Fantasy/Fantasy.Packages/Fantasy.ConfigTable/Net/ConfigTableHelper.cs index 1a5cc11..e2d5ea9 100644 --- a/Fantasy/Fantasy.Packages/Fantasy.ConfigTable/Net/ConfigTableHelper.cs +++ b/Fantasy/Fantasy.Packages/Fantasy.ConfigTable/Net/ConfigTableHelper.cs @@ -113,5 +113,82 @@ namespace Fantasy.ConfigTable return types; } + + // private static string _configTableBinaryDirectory; + // private static readonly object Lock = new object(); + // // 配置表数据缓存字典 + // private static readonly Dictionary ConfigDic = new (); + // /// + // /// 初始化ConfigTableHelper + // /// + // /// + // public static void Initialize(string configTableBinaryDirectory) + // { + // _configTableBinaryDirectory = configTableBinaryDirectory; + // } + // /// + // /// 加载配置表数据 + // /// + // /// 配置表类型 + // /// 配置表数据 + // public static T Load() where T : ASerialize + // { + // lock (Lock) + // { + // try + // { + // var dataConfig = typeof(T).Name; + // + // if (ConfigDic.TryGetValue(dataConfig, out var aProto)) + // { + // return (T)aProto; + // } + // + // var configFile = GetConfigPath(dataConfig); + // var bytes = File.ReadAllBytes(configFile); + // // Log.Debug($"dataConfig:{dataConfig} {bytes.Length}"); + // var data = SerializerManager.GetSerializer(FantasySerializerType.ProtoBuf).Deserialize(bytes); + // ConfigDic[dataConfig] = data; + // return data; + // } + // catch (Exception ex) + // { + // throw new Exception($"ConfigTableManage:{typeof(T).Name} 数据表加载之后反序列化时出错:{ex}"); + // } + // } + // } + // + // /// + // /// 获取配置表文件路径 + // /// + // /// 配置表名称 + // /// 配置表文件路径 + // private static string GetConfigPath(string name) + // { + // var configFile = Path.Combine(_configTableBinaryDirectory, $"{name}.bytes"); + // + // if (File.Exists(configFile)) + // { + // return configFile; + // } + // + // throw new FileNotFoundException($"{name}.byte not found: {configFile}"); + // } + // + // /// + // /// 重新加载配置表数据 + // /// + // public static void ReLoadConfigTable() + // { + // lock (Lock) + // { + // foreach (var (_, aProto) in ConfigDic) + // { + // ((IDisposable) aProto).Dispose(); + // } + // + // ConfigDic.Clear(); + // } + // } } } \ No newline at end of file diff --git a/Fantasy/Fantasy.Packages/Fantasy.ConfigTable/Net/Dictionary/IntDictionaryConfig.cs b/Fantasy/Fantasy.Packages/Fantasy.ConfigTable/Net/Dictionary/IntDictionaryConfig.cs deleted file mode 100644 index a9b13b3..0000000 --- a/Fantasy/Fantasy.Packages/Fantasy.ConfigTable/Net/Dictionary/IntDictionaryConfig.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Collections.Generic; -using ProtoBuf; -#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member - -// ReSharper disable CheckNamespace -#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. - -namespace Fantasy.ConfigTable -{ - [ProtoContract] - public partial class IntDictionaryConfig - { - [ProtoMember(1)] - public Dictionary Dic; - public int this[int key] => GetValue(key); - public bool TryGetValue(int key, out int value) - { - value = default; - - if (!Dic.ContainsKey(key)) - { - return false; - } - - value = Dic[key]; - return true; - } - private int GetValue(int key) - { - return Dic.TryGetValue(key, out var value) ? value : 0; - } - } -} \ No newline at end of file diff --git a/Fantasy/Fantasy.Packages/Fantasy.ConfigTable/Net/Dictionary/StringDictionaryConfig.cs b/Fantasy/Fantasy.Packages/Fantasy.ConfigTable/Net/Dictionary/StringDictionaryConfig.cs deleted file mode 100644 index 708ccd7..0000000 --- a/Fantasy/Fantasy.Packages/Fantasy.ConfigTable/Net/Dictionary/StringDictionaryConfig.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Collections.Generic; -using ProtoBuf; -#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member - -// ReSharper disable CheckNamespace -// ReSharper disable CollectionNeverUpdated.Global -#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. -#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. -#pragma warning disable CS8603 // Possible null reference return. -namespace Fantasy.ConfigTable -{ - [ProtoContract] - public sealed partial class StringDictionaryConfig - { - [ProtoMember(1)] - public Dictionary Dic; - public string this[int key] => GetValue(key); - public bool TryGetValue(int key, out string value) - { - value = default; - - if (!Dic.ContainsKey(key)) - { - return false; - } - - value = Dic[key]; - return true; - } - private string GetValue(int key) - { - return Dic.TryGetValue(key, out var value) ? value : null; - } - } -} \ No newline at end of file diff --git a/Fantasy/Fantasy.Packages/Fantasy.ConfigTable/Net/Fantasy.ConfigTable.csproj b/Fantasy/Fantasy.Packages/Fantasy.ConfigTable/Net/Fantasy.ConfigTable.csproj index f519b5b..5353c0c 100644 --- a/Fantasy/Fantasy.Packages/Fantasy.ConfigTable/Net/Fantasy.ConfigTable.csproj +++ b/Fantasy/Fantasy.Packages/Fantasy.ConfigTable/Net/Fantasy.ConfigTable.csproj @@ -24,7 +24,7 @@ - + diff --git a/Fantasy/Fantays.Console/Fantasy.Console.csproj b/Fantasy/Fantays.Console/Fantasy.Console.csproj new file mode 100644 index 0000000..ad2a092 --- /dev/null +++ b/Fantasy/Fantays.Console/Fantasy.Console.csproj @@ -0,0 +1,31 @@ + + + + enable + enable + net8.0;net9.0 + default + + + + TRACE;FANTASY_CONSOLE + true + true + + + + TRACE;FANTASY_CONSOLE + true + + + + + + + + + + + + + diff --git a/Fantasy/Fantays.Console/Runtime/Core/Assembly/AssemblyInfo.cs b/Fantasy/Fantays.Console/Runtime/Core/Assembly/AssemblyInfo.cs new file mode 100644 index 0000000..3957539 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Assembly/AssemblyInfo.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Fantasy.DataStructure.Collection; + +// ReSharper disable CollectionNeverQueried.Global +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. + +namespace Fantasy.Assembly +{ + /// + /// AssemblyInfo提供有关程序集和类型的信息 + /// + public sealed class AssemblyInfo + { + /// + /// 唯一标识 + /// + public readonly long AssemblyIdentity; + /// + /// 获取或设置与此程序集相关联的 实例。 + /// + public System.Reflection.Assembly Assembly { get; private set; } + /// + /// 程序集类型集合,获取一个列表,包含从程序集加载的所有类型。 + /// + public readonly List AssemblyTypeList = new List(); + /// + /// 程序集类型分组集合,获取一个分组列表,将接口类型映射到实现这些接口的类型。 + /// + public readonly OneToManyList AssemblyTypeGroupList = new OneToManyList(); + + /// + /// 初始化 类的新实例。 + /// + /// + public AssemblyInfo(long assemblyIdentity) + { + AssemblyIdentity = assemblyIdentity; + } + + /// + /// 从指定的程序集加载类型信息并进行分类。 + /// + /// 要加载信息的程序集。 + public void Load(System.Reflection.Assembly assembly) + { + Assembly = assembly; + var assemblyTypes = assembly.GetTypes().ToList(); + + foreach (var type in assemblyTypes) + { + if (type.IsAbstract || type.IsInterface) + { + continue; + } + + var interfaces = type.GetInterfaces(); + + foreach (var interfaceType in interfaces) + { + AssemblyTypeGroupList.Add(interfaceType, type); + } + } + + AssemblyTypeList.AddRange(assemblyTypes); + } + + /// + /// 重新加载程序集的类型信息。 + /// + /// + public void ReLoad(System.Reflection.Assembly assembly) + { + Unload(); + Load(assembly); + } + + /// + /// 卸载程序集的类型信息。 + /// + public void Unload() + { + AssemblyTypeList.Clear(); + AssemblyTypeGroupList.Clear(); + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Assembly/AssemblySystem.cs b/Fantasy/Fantays.Console/Runtime/Core/Assembly/AssemblySystem.cs new file mode 100644 index 0000000..deb9da6 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Assembly/AssemblySystem.cs @@ -0,0 +1,285 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Security.Cryptography; +using System.Text; +using Fantasy.Async; +using Fantasy.Helper; +#pragma warning disable CS8604 // Possible null reference argument. +#pragma warning disable CS8602 // Dereference of a possibly null reference. +#pragma warning disable CS8603 +#pragma warning disable CS8618 +namespace Fantasy.Assembly +{ + /// + /// 管理程序集加载和卸载的帮助类。 + /// + public static class AssemblySystem + { +#if FANTASY_WEBGL + private static readonly List AssemblySystems = new List(); + private static readonly Dictionary AssemblyList = new Dictionary(); +#else + private static readonly ConcurrentQueue AssemblySystems = new ConcurrentQueue(); + private static readonly ConcurrentDictionary AssemblyList = new ConcurrentDictionary(); +#endif + /// + /// 初始化 AssemblySystem。(仅限内部) + /// + /// + internal static async FTask InnerInitialize(params System.Reflection.Assembly[] assemblies) + { + await LoadAssembly(typeof(AssemblySystem).Assembly); + foreach (var assembly in assemblies) + { + await LoadAssembly(assembly); + } + } + + /// + /// 加载指定的程序集,并触发相应的事件。 + /// + /// 要加载的程序集。 + /// 如果当前Domain中已经存在同名的Assembly,使用Domain中的程序集。 + public static async FTask LoadAssembly(System.Reflection.Assembly assembly, bool isCurrentDomain = true) + { + if (isCurrentDomain) + { + var currentDomainAssemblies = System.AppDomain.CurrentDomain.GetAssemblies(); + var currentAssembly = currentDomainAssemblies.FirstOrDefault(d => d.GetName().Name == assembly.GetName().Name); + if (currentAssembly != null) + { + assembly = currentAssembly; + } + } + + var assemblyIdentity = AssemblyIdentity(assembly); + + if (AssemblyList.TryGetValue(assemblyIdentity, out var assemblyInfo)) + { + assemblyInfo.ReLoad(assembly); + foreach (var assemblySystem in AssemblySystems) + { + await assemblySystem.ReLoad(assemblyIdentity); + } + } + else + { + assemblyInfo = new AssemblyInfo(assemblyIdentity); + assemblyInfo.Load(assembly); + AssemblyList.TryAdd(assemblyIdentity, assemblyInfo); + foreach (var assemblySystem in AssemblySystems) + { + await assemblySystem.Load(assemblyIdentity); + } + } + } + + /// + /// 卸载程序集 + /// + /// + public static async FTask UnLoadAssembly(System.Reflection.Assembly assembly) + { + var assemblyIdentity = AssemblyIdentity(assembly); + + if (!AssemblyList.Remove(assemblyIdentity, out var assemblyInfo)) + { + return; + } + + assemblyInfo.Unload(); + foreach (var assemblySystem in AssemblySystems) + { + await assemblySystem.OnUnLoad(assemblyIdentity); + } + } + + /// + /// 将AssemblySystem接口的object注册到程序集管理中心 + /// + /// + public static async FTask Register(object obj) + { + if (obj is not IAssembly assemblySystem) + { + return; + } +#if FANTASY_WEBGL + AssemblySystems.Add(assemblySystem); +#else + AssemblySystems.Enqueue(assemblySystem); +#endif + foreach (var (assemblyIdentity, _) in AssemblyList) + { + await assemblySystem.Load(assemblyIdentity); + } + } + + /// + /// 程序集管理中心卸载注册的Load、ReLoad、UnLoad的接口 + /// + /// + public static void UnRegister(object obj) + { + if (obj is not IAssembly assemblySystem) + { + return; + } +#if FANTASY_WEBGL + AssemblySystems.Remove(assemblySystem); +#else + var count = AssemblySystems.Count; + + for (var i = 0; i < count; i++) + { + if (!AssemblySystems.TryDequeue(out var removeAssemblySystem)) + { + continue; + } + + if (removeAssemblySystem == assemblySystem) + { + break; + } + + AssemblySystems.Enqueue(removeAssemblySystem); + } +#endif + } + + /// + /// 获取所有已加载程序集中的所有类型。 + /// + /// 所有已加载程序集中的类型。 + public static IEnumerable ForEach() + { + foreach (var (_, assemblyInfo) in AssemblyList) + { + foreach (var type in assemblyInfo.AssemblyTypeList) + { + yield return type; + } + } + } + + /// + /// 获取指定程序集中的所有类型。 + /// + /// 程序集唯一标识。 + /// 指定程序集中的类型。 + public static IEnumerable ForEach(long assemblyIdentity) + { + if (!AssemblyList.TryGetValue(assemblyIdentity, out var assemblyInfo)) + { + yield break; + } + + foreach (var type in assemblyInfo.AssemblyTypeList) + { + yield return type; + } + } + + /// + /// 获取所有已加载程序集中实现指定类型的所有类型。 + /// + /// 要查找的基类或接口类型。 + /// 所有已加载程序集中实现指定类型的类型。 + public static IEnumerable ForEach(Type findType) + { + foreach (var (_, assemblyInfo) in AssemblyList) + { + if (!assemblyInfo.AssemblyTypeGroupList.TryGetValue(findType, out var assemblyLoad)) + { + continue; + } + + foreach (var type in assemblyLoad) + { + yield return type; + } + } + } + + /// + /// 获取指定程序集中实现指定类型的所有类型。 + /// + /// 程序集唯一标识。 + /// 要查找的基类或接口类型。 + /// 指定程序集中实现指定类型的类型。 + public static IEnumerable ForEach(long assemblyIdentity, Type findType) + { + if (!AssemblyList.TryGetValue(assemblyIdentity, out var assemblyInfo)) + { + yield break; + } + + if (!assemblyInfo.AssemblyTypeGroupList.TryGetValue(findType, out var assemblyLoad)) + { + yield break; + } + + foreach (var type in assemblyLoad) + { + yield return type; + } + } + + /// + /// 获取指定程序集的实例。 + /// + /// 程序集名称。 + /// 指定程序集的实例,如果未加载则返回 null。 + public static System.Reflection.Assembly GetAssembly(long assemblyIdentity) + { + return !AssemblyList.TryGetValue(assemblyIdentity, out var assemblyInfo) ? null : assemblyInfo.Assembly; + } + + /// + /// 获取当前框架注册的Assembly + /// + /// + public static IEnumerable ForEachAssembly + { + get + { + foreach (var (_, assemblyInfo) in AssemblyList) + { + yield return assemblyInfo.Assembly; + } + } + } + + /// + /// 根据Assembly的强命名计算唯一标识。 + /// + /// + /// + private static long AssemblyIdentity(System.Reflection.Assembly assembly) + { + return HashCodeHelper.ComputeHash64(assembly.GetName().Name); + } + + /// + /// 释放资源,卸载所有加载的程序集。 + /// + public static void Dispose() + { + DisposeAsync().Coroutine(); + } + + private static async FTask DisposeAsync() + { + foreach (var (_, assemblyInfo) in AssemblyList.ToArray()) + { + await UnLoadAssembly(assemblyInfo.Assembly); + } + + AssemblyList.Clear(); + AssemblySystems.Clear(); + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Assembly/IAssembly.cs b/Fantasy/Fantays.Console/Runtime/Core/Assembly/IAssembly.cs new file mode 100644 index 0000000..185e037 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Assembly/IAssembly.cs @@ -0,0 +1,27 @@ +using System; +using Fantasy.Async; + +namespace Fantasy.Assembly +{ + /// + /// 实现这个接口、会再程序集首次加载、卸载、重载的时候调用 + /// + public interface IAssembly : IDisposable + { + /// + /// 程序集加载时调用 + /// + /// 程序集标识 + public FTask Load(long assemblyIdentity); + /// + /// 程序集重新加载的时候调用 + /// + /// 程序集标识 + public FTask ReLoad(long assemblyIdentity); + /// + /// 卸载的时候调用 + /// + /// 程序集标识 + public FTask OnUnLoad(long assemblyIdentity); + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Benchmark/Handler/BenchmarkRequestHandler.cs b/Fantasy/Fantays.Console/Runtime/Core/Benchmark/Handler/BenchmarkRequestHandler.cs new file mode 100644 index 0000000..8532547 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Benchmark/Handler/BenchmarkRequestHandler.cs @@ -0,0 +1,25 @@ +using Fantasy.Async; +using Fantasy.InnerMessage; +using Fantasy.Network.Interface; + +#if FANTASY_NET +namespace Fantasy.Network.Benchmark.Handler; + +/// +/// BenchmarkRequestHandler +/// +public sealed class BenchmarkRequestHandler : MessageRPC +{ + /// + /// Run方法 + /// + /// + /// + /// + /// + protected override async FTask Run(Session session, BenchmarkRequest request, BenchmarkResponse response, Action reply) + { + await FTask.CompletedTask; + } +} +#endif diff --git a/Fantasy/Fantays.Console/Runtime/Core/DataBase/DataBaseType.cs b/Fantasy/Fantays.Console/Runtime/Core/DataBase/DataBaseType.cs new file mode 100644 index 0000000..2fb03ec --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/DataBase/DataBaseType.cs @@ -0,0 +1,20 @@ +// ReSharper disable CheckNamespace +// ReSharper disable InconsistentNaming +#if FANTASY_NET +namespace Fantasy.DataBase; + +/// +/// 数据库类型 +/// +public enum DataBaseType +{ + /// + /// 默认 + /// + None = 0, + /// + /// MongoDB + /// + MongoDB = 1 +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/DataBase/IDataBase.cs b/Fantasy/Fantays.Console/Runtime/Core/DataBase/IDataBase.cs new file mode 100644 index 0000000..6a2fe8b --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/DataBase/IDataBase.cs @@ -0,0 +1,210 @@ +#if FANTASY_NET +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using Fantasy.Async; +using Fantasy.Entitas; +using MongoDB.Driver; +// ReSharper disable InconsistentNaming +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. + +#pragma warning disable CS8625 + +namespace Fantasy.DataBase +{ + /// + /// 数据库设置助手 + /// + public static class DataBaseSetting + { + /// + /// 初始化自定义委托,当设置了这个委托后,就不会自动创建MongoClient,需要自己在委托里创建MongoClient。 + /// + public static Func? MongoDBCustomInitialize; + } + + /// + /// MongoDB自定义连接参数 + /// + public sealed class DataBaseCustomConfig + { + /// + /// 当前Scene + /// + public Scene Scene; + /// + /// 连接字符串 + /// + public string ConnectionString; + /// + /// 数据库名字 + /// + public string DBName; + } + + /// + /// 表示用于执行各种数据库操作的数据库接口。 + /// + public interface IDataBase : IDisposable + { + /// + /// 获得当前数据的类型 + /// + public DataBaseType GetDataBaseType { get;} + /// + /// 获得对应数据的操作实例 + /// + /// 如MongoDB就是IMongoDatabase + public object GetDataBaseInstance { get;} + /// + /// 初始化数据库连接。 + /// + IDataBase Initialize(Scene scene, string connectionString, string dbName); + /// + /// 在指定的集合中检索类型 的实体数量。 + /// + FTask Count(string collection = null) where T : Entity; + /// + /// 在指定的集合中检索满足给定筛选条件的类型 的实体数量。 + /// + FTask Count(Expression> filter, string collection = null) where T : Entity; + /// + /// 检查指定集合中是否存在类型 的实体。 + /// + FTask Exist(string collection = null) where T : Entity; + /// + /// 检查指定集合中是否存在满足给定筛选条件的类型 的实体。 + /// + FTask Exist(Expression> filter, string collection = null) where T : Entity; + /// + /// 从指定集合中检索指定 ID 的类型 的实体,不锁定。 + /// + FTask QueryNotLock(long id, bool isDeserialize = false, string collection = null) where T : Entity; + /// + /// 从指定集合中检索指定 ID 的类型 的实体。 + /// + FTask Query(long id, bool isDeserialize = false, string collection = null) where T : Entity; + /// + /// 按页查询满足给定筛选条件的类型 的实体数量和日期。 + /// + FTask<(int count, List dates)> QueryCountAndDatesByPage(Expression> filter, int pageIndex, int pageSize, bool isDeserialize = false, string collection = null) where T : Entity; + /// + /// 按页查询满足给定筛选条件的类型 的实体数量和日期。 + /// + FTask<(int count, List dates)> QueryCountAndDatesByPage(Expression> filter, int pageIndex, int pageSize, string[] cols, bool isDeserialize = false, string collection = null) where T : Entity; + /// + /// 分页查询指定集合中满足给定筛选条件的类型 的实体列表。 + /// + FTask> QueryByPage(Expression> filter, int pageIndex, int pageSize, bool isDeserialize = false, string collection = null) where T : Entity; + /// + /// 分页查询指定集合中满足给定筛选条件的类型 的实体列表,仅返回指定列的数据。 + /// + FTask> QueryByPage(Expression> filter, int pageIndex, int pageSize, string[] cols, bool isDeserialize = false, string collection = null) where T : Entity; + /// + /// 从指定集合中按页查询满足给定筛选条件的类型 的实体列表,按指定字段排序。 + /// + FTask> QueryByPageOrderBy(Expression> filter, int pageIndex, int pageSize, Expression> orderByExpression, bool isAsc = true, bool isDeserialize = false, string collection = null) where T : Entity; + /// + /// 检索满足给定筛选条件的类型 的第一个实体,从指定集合中。 + /// + FTask First(Expression> filter, bool isDeserialize = false, string collection = null) where T : Entity; + /// + /// 查询指定集合中满足给定 JSON 查询字符串的类型 的第一个实体,仅返回指定列的数据。 + /// + FTask First(string json, string[] cols, bool isDeserialize = false, string collection = null) where T : Entity; + /// + /// 从指定集合中按页查询满足给定筛选条件的类型 的实体列表,按指定字段排序。 + /// + FTask> QueryOrderBy(Expression> filter, Expression> orderByExpression, bool isAsc = true, bool isDeserialize = false, string collection = null) where T : Entity; + /// + /// 从指定集合中按页查询满足给定筛选条件的类型 的实体列表。 + /// + FTask> Query(Expression> filter, bool isDeserialize = false, string collection = null) where T : Entity; + /// + /// 查询指定集合中满足给定筛选条件的类型 实体列表,仅返回指定字段的数据。 + /// + FTask> Query(Expression> filter, Expression>[] cols, bool isDeserialize = false, string collection = null) where T : Entity; + /// + /// 查询指定 ID 的多个集合,将结果存储在给定的实体列表中。 + /// + FTask Query(long id, List collectionNames, List result, bool isDeserialize = false); + /// + /// 根据给定的 JSON 查询字符串查询指定集合中的类型 实体列表。 + /// + FTask> QueryJson(string json, bool isDeserialize = false, string collection = null) where T : Entity; + /// + /// 根据给定的 JSON 查询字符串查询指定集合中的类型 实体列表,仅返回指定列的数据。 + /// + FTask> QueryJson(string json, string[] cols, bool isDeserialize = false, string collection = null) where T : Entity; + /// + /// 根据给定的 JSON 查询字符串查询指定集合中的类型 实体列表,通过指定的任务 ID 进行标识。 + /// + FTask> QueryJson(long taskId, string json, bool isDeserialize = false, string collection = null) where T : Entity; + /// + /// 查询指定集合中满足给定筛选条件的类型 实体列表,仅返回指定列的数据。 + /// + FTask> Query(Expression> filter, string[] cols, bool isDeserialize = false, string collection = null) where T : Entity; + /// + /// 保存类型 实体到指定集合中,如果集合不存在将自动创建。 + /// + FTask Save(T entity, string collection = null) where T : Entity, new(); + /// + /// 保存一组实体到数据库中,根据实体列表的 ID 进行区分和存储。 + /// + FTask Save(long id, List entities); + /// + /// 通过事务会话将类型 实体保存到指定集合中,如果集合不存在将自动创建。 + /// + FTask Save(object transactionSession, T entity, string collection = null) where T : Entity; + /// + /// 向指定集合中插入一个类型 实体,如果集合不存在将自动创建。 + /// + FTask Insert(T entity, string collection = null) where T : Entity, new(); + /// + /// 批量插入一组类型 实体到指定集合中,如果集合不存在将自动创建。 + /// + FTask InsertBatch(IEnumerable list, string collection = null) where T : Entity, new(); + /// + /// 通过事务会话,批量插入一组类型 实体到指定集合中,如果集合不存在将自动创建。 + /// + FTask InsertBatch(object transactionSession, IEnumerable list, string collection = null) where T : Entity, new(); + /// + /// 通过事务会话,根据指定的 ID 从数据库中删除指定类型 实体。 + /// + FTask Remove(object transactionSession, long id, string collection = null) where T : Entity, new(); + /// + /// 根据指定的 ID 从数据库中删除指定类型 实体。 + /// + FTask Remove(long id, string collection = null) where T : Entity, new(); + /// + /// 通过事务会话,根据给定的筛选条件从数据库中删除指定类型 实体。 + /// + FTask Remove(long coroutineLockQueueKey, object transactionSession, Expression> filter, string collection = null) where T : Entity, new(); + /// + /// 根据给定的筛选条件从数据库中删除指定类型 实体。 + /// + FTask Remove(long coroutineLockQueueKey, Expression> filter, string collection = null) where T : Entity, new(); + /// + /// 根据给定的筛选条件计算指定集合中类型 实体某个属性的总和。 + /// + FTask Sum(Expression> filter, Expression> sumExpression, string collection = null) where T : Entity; + /// + /// 在指定的集合中创建索引,以提高类型 实体的查询性能。 + /// + FTask CreateIndex(string collection, params object[] keys) where T : Entity; + /// + /// 在默认集合中创建索引,以提高类型 实体的查询性能。 + /// + FTask CreateIndex(params object[] keys) where T : Entity; + /// + /// 创建指定类型 的数据库,用于存储实体。 + /// + FTask CreateDB() where T : Entity; + /// + /// 根据指定类型创建数据库,用于存储实体。 + /// + FTask CreateDB(Type type); + } +} + +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/DataBase/MongoDataBase.cs b/Fantasy/Fantays.Console/Runtime/Core/DataBase/MongoDataBase.cs new file mode 100644 index 0000000..49948f4 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/DataBase/MongoDataBase.cs @@ -0,0 +1,1081 @@ +#if FANTASY_NET +using System.Linq.Expressions; +using Fantasy.Async; +using Fantasy.DataStructure.Collection; +using Fantasy.Entitas; +using Fantasy.Helper; +using Fantasy.Serialize; +using MongoDB.Bson; +using MongoDB.Driver; +#pragma warning disable CS8602 // Dereference of a possibly null reference. +#pragma warning disable CS8603 // Possible null reference return. +#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. + +namespace Fantasy.DataBase +{ + /// + /// 使用 MongoDB 数据库的实现。 + /// + public sealed class MongoDataBase : IDataBase + { + private const int DefaultTaskSize = 1024; + private Scene _scene; + private MongoClient _mongoClient; + private ISerialize _serializer; + private IMongoDatabase _mongoDatabase; + private CoroutineLock _dataBaseLock; + private readonly HashSet _collections = new HashSet(); + /// + /// 获得当前数据的类型 + /// + public DataBaseType GetDataBaseType { get; } = DataBaseType.MongoDB; + /// + /// 获得对应数据的操作实例 + /// + public object GetDataBaseInstance => _mongoDatabase; + /// + /// 初始化 MongoDB 数据库连接并记录所有集合名。 + /// + /// 场景对象。 + /// 数据库连接字符串。 + /// 数据库名称。 + /// 初始化后的数据库实例。 + public IDataBase Initialize(Scene scene, string connectionString, string dbName) + { + _scene = scene; + _mongoClient = DataBaseSetting.MongoDBCustomInitialize != null + ? DataBaseSetting.MongoDBCustomInitialize(new DataBaseCustomConfig() + { + Scene = scene, ConnectionString = connectionString, DBName = dbName + }) + : new MongoClient(connectionString); + _mongoDatabase = _mongoClient.GetDatabase(dbName); + _dataBaseLock = scene.CoroutineLockComponent.Create(GetType().TypeHandle.Value.ToInt64()); + // 记录所有集合名 + _collections.UnionWith(_mongoDatabase.ListCollectionNames().ToList()); + _serializer = SerializerManager.GetSerializer(FantasySerializerType.Bson); + return this; + } + + /// + /// 销毁释放资源。 + /// + public void Dispose() + { + // 优先释放协程锁。 + _dataBaseLock.Dispose(); + // 清理资源。 + _scene = null; + _serializer = null; + _mongoDatabase = null; + _dataBaseLock = null; + _collections.Clear(); + _mongoClient.Dispose(); + } + + #region Other + + /// + /// 对满足条件的文档中的某个数值字段进行求和操作。 + /// + /// 实体类型。 + /// 用于筛选文档的表达式。 + /// 要对其进行求和的字段表达式。 + /// 集合名称,可选。如果未指定,将使用实体类型的名称。 + /// 满足条件的文档中指定字段的求和结果。 + public async FTask Sum(Expression> filter, Expression> sumExpression, string collection = null) where T : Entity + { + var member = (MemberExpression)((UnaryExpression)sumExpression.Body).Operand; + var projection = new BsonDocument("_id", "null").Add("Result", new BsonDocument("$sum", $"${member.Member.Name}")); + var data = await GetCollection(collection).Aggregate().Match(filter).Group(projection).FirstOrDefaultAsync(); + return data == null ? 0 : Convert.ToInt64(data["Result"]); + } + + #endregion + + #region GetCollection + + /// + /// 获取指定集合中的 MongoDB 文档的 IMongoCollection 对象。 + /// + /// 实体类型。 + /// 集合名称,可选。如果未指定,将使用实体类型的名称。 + /// IMongoCollection 对象。 + private IMongoCollection GetCollection(string collection = null) + { + return _mongoDatabase.GetCollection(collection ?? typeof(T).Name); + } + + /// + /// 获取指定集合中的 MongoDB 文档的 IMongoCollection 对象,其中实体类型为 Entity。 + /// + /// 集合名称。 + /// IMongoCollection 对象。 + private IMongoCollection GetCollection(string name) + { + return _mongoDatabase.GetCollection(name); + } + + #endregion + + #region Count + + /// + /// 统计指定集合中满足条件的文档数量。 + /// + /// 实体类型。 + /// 集合名称,可选。如果未指定,将使用实体类型的名称。 + /// 满足条件的文档数量。 + public async FTask Count(string collection = null) where T : Entity + { + return await GetCollection(collection).CountDocumentsAsync(d => true); + } + + /// + /// 统计指定集合中满足条件的文档数量。 + /// + /// 实体类型。 + /// 用于筛选文档的表达式。 + /// 集合名称,可选。如果未指定,将使用实体类型的名称。 + /// 满足条件的文档数量。 + public async FTask Count(Expression> filter, string collection = null) where T : Entity + { + return await GetCollection(collection).CountDocumentsAsync(filter); + } + + #endregion + + #region Exist + + /// + /// 判断指定集合中是否存在文档。 + /// + /// 实体类型。 + /// 集合名称,可选。如果未指定,将使用实体类型的名称。 + /// 如果存在文档则返回 true,否则返回 false。 + public async FTask Exist(string collection = null) where T : Entity + { + return await Count(collection) > 0; + } + + /// + /// 判断指定集合中是否存在满足条件的文档。 + /// + /// 实体类型。 + /// 用于筛选文档的表达式。 + /// 集合名称,可选。如果未指定,将使用实体类型的名称。 + /// 如果存在满足条件的文档则返回 true,否则返回 false。 + public async FTask Exist(Expression> filter, string collection = null) where T : Entity + { + return await Count(filter, collection) > 0; + } + + #endregion + + #region Query + + /// + /// 在不加数据库锁定的情况下,查询指定 ID 的文档。 + /// + /// 文档实体类型。 + /// 要查询的文档 ID。 + /// 是否在查询后反序列化,执行反序列化后会自动将实体注册到框架系统中,并且能正常使用组件相关功能。 + /// 集合名称。 + /// 查询到的文档。 + public async FTask QueryNotLock(long id, bool isDeserialize = false, string collection = null) where T : Entity + { + var cursor = await GetCollection(collection).FindAsync(d => d.Id == id); + var v = await cursor.FirstOrDefaultAsync(); + + if (isDeserialize && v != null) + { + v.Deserialize(_scene); + } + + return v; + } + + /// + /// 查询指定 ID 的文档,并加数据库锁定以确保数据一致性。 + /// + /// 文档实体类型。 + /// 要查询的文档 ID。 + /// 是否在查询后反序列化,执行反序列化后会自动将实体注册到框架系统中,并且能正常使用组件相关功能。 + /// 集合名称。 + /// 查询到的文档。 + public async FTask Query(long id, bool isDeserialize = false, string collection = null) where T : Entity + { + using (await _dataBaseLock.Wait(id)) + { + var cursor = await GetCollection(collection).FindAsync(d => d.Id == id); + var v = await cursor.FirstOrDefaultAsync(); + + if (isDeserialize && v != null) + { + v.Deserialize(_scene); + } + + return v; + } + } + + /// + /// 通过分页查询并返回满足条件的文档数量和日期列表(不加锁)。 + /// + /// 文档实体类型。 + /// 查询过滤条件。 + /// 页码。 + /// 每页大小。 + /// 是否在查询后反序列化,执行反序列化后会自动将实体注册到框架系统中,并且能正常使用组件相关功能。 + /// 集合名称。 + /// 满足条件的文档数量和日期列表。 + public async FTask<(int count, List dates)> QueryCountAndDatesByPage(Expression> filter, int pageIndex, int pageSize, bool isDeserialize = false, string collection = null) where T : Entity + { + using (await _dataBaseLock.Wait(RandomHelper.RandInt64() % DefaultTaskSize)) + { + var count = await Count(filter); + var dates = await QueryByPage(filter, pageIndex, pageSize, isDeserialize, collection); + return ((int)count, dates); + } + } + + /// + /// 通过分页查询并返回满足条件的文档数量和日期列表(加锁)。 + /// + /// 文档实体类型。 + /// 查询过滤条件。 + /// 页码。 + /// 每页大小。 + /// 要查询的列名称数组。 + /// 是否在查询后反序列化,执行反序列化后会自动将实体注册到框架系统中,并且能正常使用组件相关功能。 + /// 集合名称。 + /// 满足条件的文档数量和日期列表。 + public async FTask<(int count, List dates)> QueryCountAndDatesByPage(Expression> filter, int pageIndex, int pageSize, string[] cols, bool isDeserialize = false, string collection = null) where T : Entity + { + using (await _dataBaseLock.Wait(RandomHelper.RandInt64() % DefaultTaskSize)) + { + var count = await Count(filter); + var dates = await QueryByPage(filter, pageIndex, pageSize, cols, isDeserialize, collection); + return ((int)count, dates); + } + } + + /// + /// 通过分页查询并返回满足条件的文档列表(不加锁)。 + /// + /// 文档实体类型。 + /// 查询过滤条件。 + /// 页码。 + /// 每页大小。 + /// 是否在查询后反序列化,执行反序列化后会自动将实体注册到框架系统中,并且能正常使用组件相关功能。 + /// 集合名称。 + /// 满足条件的文档列表。 + public async FTask> QueryByPage(Expression> filter, int pageIndex, int pageSize, bool isDeserialize = false, string collection = null) where T : Entity + { + using (await _dataBaseLock.Wait(RandomHelper.RandInt64() % DefaultTaskSize)) + { + var list = await GetCollection(collection).Find(filter).Skip((pageIndex - 1) * pageSize) + .Limit(pageSize) + .ToListAsync(); + + if (!isDeserialize || list is not { Count: > 0 }) + { + return list; + } + + foreach (var entity in list) + { + entity.Deserialize(_scene); + } + + return list; + } + } + + /// + /// 通过分页查询并返回满足条件的文档列表(加锁)。 + /// + /// 文档实体类型。 + /// 查询过滤条件。 + /// 页码。 + /// 每页大小。 + /// 要查询的列名称数组。 + /// 是否在查询后反序列化,执行反序列化后会自动将实体注册到框架系统中,并且能正常使用组件相关功能。 + /// 集合名称。 + /// 满足条件的文档列表。 + public async FTask> QueryByPage(Expression> filter, int pageIndex, int pageSize, string[] cols, bool isDeserialize = false, string collection = null) where T : Entity + { + using (await _dataBaseLock.Wait(RandomHelper.RandInt64() % DefaultTaskSize)) + { + var projection = Builders.Projection.Include(""); + + foreach (var col in cols) + { + projection = projection.Include(col); + } + + var list = await GetCollection(collection).Find(filter).Project(projection) + .Skip((pageIndex - 1) * pageSize).Limit(pageSize).ToListAsync(); + + if (!isDeserialize || list is not { Count: > 0 }) + { + return list; + } + + foreach (var entity in list) + { + entity.Deserialize(_scene); + } + + return list; + } + } + + /// + /// 通过分页查询并返回满足条件的文档列表,并按指定表达式进行排序(加锁)。 + /// + /// 文档实体类型。 + /// 查询过滤条件。 + /// 页码。 + /// 每页大小。 + /// 排序表达式。 + /// 是否升序排序。 + /// 是否在查询后反序列化,执行反序列化后会自动将实体注册到框架系统中,并且能正常使用组件相关功能。 + /// 集合名称。 + /// 满足条件的文档列表。 + public async FTask> QueryByPageOrderBy(Expression> filter, int pageIndex, int pageSize, Expression> orderByExpression, bool isAsc = true, bool isDeserialize = false, string collection = null) where T : Entity + { + using (await _dataBaseLock.Wait(RandomHelper.RandInt64() % DefaultTaskSize)) + { + List list; + + if (isAsc) + { + list = await GetCollection(collection).Find(filter).SortBy(orderByExpression).Skip((pageIndex - 1) * pageSize).Limit(pageSize).ToListAsync(); + } + else + { + list = await GetCollection(collection).Find(filter).SortByDescending(orderByExpression).Skip((pageIndex - 1) * pageSize).Limit(pageSize).ToListAsync(); + } + + if (!isDeserialize || list is not { Count: > 0 }) + { + return list; + } + + foreach (var entity in list) + { + entity.Deserialize(_scene); + } + + return list; + } + } + + /// + /// 通过指定过滤条件查询并返回满足条件的第一个文档(加锁)。 + /// + /// 文档实体类型。 + /// 查询过滤条件。 + /// 是否在查询后反序列化,执行反序列化后会自动将实体注册到框架系统中,并且能正常使用组件相关功能。 + /// 集合名称。 + /// 满足条件的第一个文档,如果未找到则为 null。 + public async FTask First(Expression> filter, bool isDeserialize = false, string collection = null) where T : Entity + { + using (await _dataBaseLock.Wait(RandomHelper.RandInt64() % DefaultTaskSize)) + { + var cursor = await GetCollection(collection).FindAsync(filter); + var t = await cursor.FirstOrDefaultAsync(); + + if (isDeserialize && t != null) + { + t.Deserialize(_scene); + } + + return t; + } + } + + /// + /// 通过指定 JSON 格式查询并返回满足条件的第一个文档(加锁)。 + /// + /// 文档实体类型。 + /// JSON 查询条件。 + /// 要查询的列名称数组。 + /// 是否在查询后反序列化,执行反序列化后会自动将实体注册到框架系统中,并且能正常使用组件相关功能。 + /// 集合名称。 + /// 满足条件的第一个文档。 + public async FTask First(string json, string[] cols, bool isDeserialize = false, string collection = null) where T : Entity + { + using (await _dataBaseLock.Wait(RandomHelper.RandInt64() % DefaultTaskSize)) + { + var projection = Builders.Projection.Include(""); + + foreach (var col in cols) + { + projection = projection.Include(col); + } + + var options = new FindOptions { Projection = projection }; + + FilterDefinition filterDefinition = new JsonFilterDefinition(json); + + var cursor = await GetCollection(collection).FindAsync(filterDefinition, options); + var t = await cursor.FirstOrDefaultAsync(); + + if (isDeserialize && t != null) + { + t.Deserialize(_scene); + } + + return t; + } + } + + /// + /// 通过指定过滤条件查询并返回满足条件的文档列表,并按指定表达式进行排序(加锁)。 + /// + /// 文档实体类型。 + /// 查询过滤条件。 + /// 排序表达式。 + /// 是否升序排序。 + /// 是否在查询后反序列化,执行反序列化后会自动将实体注册到框架系统中,并且能正常使用组件相关功能。 + /// 集合名称。 + /// 满足条件的文档列表。 + public async FTask> QueryOrderBy(Expression> filter, Expression> orderByExpression, bool isAsc = true, bool isDeserialize = false, string collection = null) where T : Entity + { + using (await _dataBaseLock.Wait(RandomHelper.RandInt64() % DefaultTaskSize)) + { + List list; + + if (isAsc) + { + list = await GetCollection(collection).Find(filter).SortBy(orderByExpression).ToListAsync(); + } + else + { + list = await GetCollection(collection).Find(filter).SortByDescending(orderByExpression).ToListAsync(); + } + + if (!isDeserialize || list is not { Count: > 0 }) + { + return list; + } + + foreach (var entity in list) + { + entity.Deserialize(_scene); + } + + return list; + } + } + + /// + /// 通过指定过滤条件查询并返回满足条件的文档列表(加锁)。 + /// + /// 文档实体类型。 + /// 查询过滤条件。 + /// 是否在查询后反序列化,执行反序列化后会自动将实体注册到框架系统中,并且能正常使用组件相关功能。 + /// 集合名称。 + /// 满足条件的文档列表。 + public async FTask> Query(Expression> filter, bool isDeserialize = false, string collection = null) where T : Entity + { + using (await _dataBaseLock.Wait(RandomHelper.RandInt64() % DefaultTaskSize)) + { + var cursor = await GetCollection(collection).FindAsync(filter); + var list = await cursor.ToListAsync(); + + if (!isDeserialize || list is not { Count: > 0 }) + { + return list; + } + + foreach (var entity in list) + { + entity.Deserialize(_scene); + } + + return list; + } + } + + /// + /// 根据指定 ID 加锁查询多个集合中的文档。 + /// + /// 文档 ID。 + /// 要查询的集合名称列表。 + /// 查询结果存储列表。 + /// 是否在查询后反序列化,执行反序列化后会自动将实体注册到框架系统中,并且能正常使用组件相关功能。 + public async FTask Query(long id, List? collectionNames, List result, bool isDeserialize = false) + { + using (await _dataBaseLock.Wait(id)) + { + if (collectionNames == null || collectionNames.Count == 0) + { + return; + } + + foreach (var collectionName in collectionNames) + { + var cursor = await GetCollection(collectionName).FindAsync(d => d.Id == id); + + var e = await cursor.FirstOrDefaultAsync(); + + if (e == null) + { + continue; + } + + if (isDeserialize) + { + e.Deserialize(_scene); + } + + result.Add(e); + } + } + } + + /// + /// 根据指定的 JSON 查询条件查询并返回满足条件的文档列表(加锁)。 + /// + /// 文档实体类型。 + /// JSON 查询条件。 + /// 是否在查询后反序列化,执行反序列化后会自动将实体注册到框架系统中,并且能正常使用组件相关功能。 + /// 集合名称。 + /// 满足条件的文档列表。 + public async FTask> QueryJson(string json, bool isDeserialize = false, string collection = null) where T : Entity + { + using (await _dataBaseLock.Wait(RandomHelper.RandInt64() % DefaultTaskSize)) + { + FilterDefinition filterDefinition = new JsonFilterDefinition(json); + var cursor = await GetCollection(collection).FindAsync(filterDefinition); + var list = await cursor.ToListAsync(); + + if (!isDeserialize || list is not { Count: > 0 }) + { + return list; + } + + foreach (var entity in list) + { + entity.Deserialize(_scene); + } + + return list; + } + } + + /// + /// 根据指定的 JSON 查询条件查询并返回满足条件的文档列表,并选择指定的列(加锁)。 + /// + /// 文档实体类型。 + /// JSON 查询条件。 + /// 要查询的列名称数组。 + /// 是否在查询后反序列化,执行反序列化后会自动将实体注册到框架系统中,并且能正常使用组件相关功能。 + /// 集合名称。 + /// 满足条件的文档列表。 + public async FTask> QueryJson(string json, string[] cols, bool isDeserialize = false, string collection = null) where T : Entity + { + using (await _dataBaseLock.Wait(RandomHelper.RandInt64() % DefaultTaskSize)) + { + var projection = Builders.Projection.Include(""); + + foreach (var col in cols) + { + projection = projection.Include(col); + } + + var options = new FindOptions { Projection = projection }; + + FilterDefinition filterDefinition = new JsonFilterDefinition(json); + + var cursor = await GetCollection(collection).FindAsync(filterDefinition, options); + var list = await cursor.ToListAsync(); + + if (!isDeserialize || list is not { Count: > 0 }) + { + return list; + } + + foreach (var entity in list) + { + entity.Deserialize(_scene); + } + + return list; + } + } + + /// + /// 根据指定的 JSON 查询条件和任务 ID 查询并返回满足条件的文档列表(加锁)。 + /// + /// 文档实体类型。 + /// 任务 ID。 + /// JSON 查询条件。 + /// 是否在查询后反序列化,执行反序列化后会自动将实体注册到框架系统中,并且能正常使用组件相关功能。 + /// 集合名称。 + /// 满足条件的文档列表。 + public async FTask> QueryJson(long taskId, string json, bool isDeserialize = false, string collection = null) where T : Entity + { + using (await _dataBaseLock.Wait(taskId)) + { + FilterDefinition filterDefinition = new JsonFilterDefinition(json); + var cursor = await GetCollection(collection).FindAsync(filterDefinition); + var list = await cursor.ToListAsync(); + + if (!isDeserialize || list is not { Count: > 0 }) + { + return list; + } + + foreach (var entity in list) + { + entity.Deserialize(_scene); + } + + return list; + } + } + + /// + /// 根据指定过滤条件查询并返回满足条件的文档列表,选择指定的列(加锁)。 + /// + /// 文档实体类型。 + /// 查询过滤条件。 + /// 要查询的列名称数组。 + /// 是否在查询后反序列化,执行反序列化后会自动将实体注册到框架系统中,并且能正常使用组件相关功能。 + /// 集合名称。 + /// 满足条件的文档列表。 + public async FTask> Query(Expression> filter, string[] cols, bool isDeserialize = false, string collection = null) where T : Entity + { + using (await _dataBaseLock.Wait(RandomHelper.RandInt64() % DefaultTaskSize)) + { + var projection = Builders.Projection.Include("_id"); + + foreach (var t in cols) + { + projection = projection.Include(t); + } + + var list = await GetCollection(collection).Find(filter).Project(projection).ToListAsync(); + + if (!isDeserialize || list is not { Count: > 0 }) + { + return list; + } + + foreach (var entity in list) + { + entity.Deserialize(_scene); + } + + return list; + } + } + + /// + /// 根据指定过滤条件查询并返回满足条件的文档列表,选择指定的列(加锁)。 + /// + /// 文档实体类型。 + /// 查询过滤条件。 + /// 要查询的列名称数组。 + /// 是否在查询后反序列化,执行反序列化后会自动将实体注册到框架系统中,并且能正常使用组件相关功能。 + /// 集合名称。 + /// + public async FTask> Query(Expression> filter, Expression>[] cols, bool isDeserialize = false, string collection = null) where T : Entity + { + using (await _dataBaseLock.Wait(RandomHelper.RandInt64() % DefaultTaskSize)) + { + var projection = Builders.Projection.Include("_id"); + + foreach (var col in cols) + { + if (col.Body is not MemberExpression memberExpression) + { + throw new ArgumentException("Lambda expression must be a member access expression."); + } + + projection = projection.Include(memberExpression.Member.Name); + } + + var list = await GetCollection(collection).Find(filter).Project(projection).ToListAsync(); + + if (!isDeserialize || list is not { Count: > 0 }) + { + return list; + } + + foreach (var entity in list) + { + entity.Deserialize(_scene); + } + + return list; + } + } + + #endregion + + #region Save + + /// + /// 保存实体对象到数据库(加锁)。 + /// + /// 实体类型。 + /// 事务会话对象。 + /// 要保存的实体对象。 + /// 集合名称。 + public async FTask Save(object transactionSession, T? entity, string collection = null) where T : Entity + { + if (entity == null) + { + Log.Error($"save entity is null: {typeof(T).Name}"); + return; + } + + var clone = _serializer.Clone(entity); + + using (await _dataBaseLock.Wait(clone.Id)) + { + await GetCollection(collection).ReplaceOneAsync( + (IClientSessionHandle)transactionSession, d => d.Id == clone.Id, clone, + new ReplaceOptions { IsUpsert = true }); + } + } + + /// + /// 保存实体对象到数据库(加锁)。 + /// + /// 实体类型。 + /// 要保存的实体对象。 + /// 集合名称。 + public async FTask Save(T? entity, string collection = null) where T : Entity, new() + { + if (entity == null) + { + Log.Error($"save entity is null: {typeof(T).Name}"); + + return; + } + + var clone = _serializer.Clone(entity); + + using (await _dataBaseLock.Wait(clone.Id)) + { + await GetCollection(collection).ReplaceOneAsync(d => d.Id == clone.Id, clone, new ReplaceOptions { IsUpsert = true }); + } + } + + /// + /// 保存实体对象到数据库(加锁)。 + /// + /// 保存的条件表达式。 + /// 实体类型。 + /// 集合名称。 + /// + public async FTask Save(Expression> filter, T? entity, string collection = null) where T : Entity, new() + { + if (entity == null) + { + Log.Error($"save entity is null: {typeof(T).Name}"); + return; + } + + T clone = _serializer.Clone(entity); + + using (await _dataBaseLock.Wait(clone.Id)) + { + await GetCollection(collection).ReplaceOneAsync(filter, clone, new ReplaceOptions { IsUpsert = true }); + } + } + + /// + /// 保存多个实体对象到数据库(加锁)。 + /// + /// 文档 ID。 + /// 要保存的实体对象列表。 + public async FTask Save(long id, List? entities) + { + if (entities == null || entities.Count == 0) + { + Log.Error("save entity is null"); + return; + } + + using var listPool = ListPool.Create(); + + foreach (var entity in entities) + { + listPool.Add(_serializer.Clone(entity)); + } + + using (await _dataBaseLock.Wait(id)) + { + foreach (var clone in listPool) + { + try + { + await GetCollection(clone.GetType().Name).ReplaceOneAsync(d => d.Id == clone.Id, clone, new ReplaceOptions { IsUpsert = true }); + } + catch (Exception e) + { + Log.Error($"Save List Entity Error: {clone.GetType().Name} {clone}\n{e}"); + } + } + } + } + + #endregion + + #region Insert + + /// + /// 插入单个实体对象到数据库(加锁)。 + /// + /// 实体类型。 + /// 要插入的实体对象。 + /// 集合名称。 + public async FTask Insert(T? entity, string collection = null) where T : Entity, new() + { + if (entity == null) + { + Log.Error($"insert entity is null: {typeof(T).Name}"); + return; + } + + var clone = _serializer.Clone(entity); + + using (await _dataBaseLock.Wait(entity.Id)) + { + await GetCollection(collection).InsertOneAsync(clone); + } + } + + /// + /// 批量插入实体对象列表到数据库(加锁)。 + /// + /// 实体类型。 + /// 要插入的实体对象列表。 + /// 集合名称。 + public async FTask InsertBatch(IEnumerable list, string collection = null) where T : Entity, new() + { + using (await _dataBaseLock.Wait(RandomHelper.RandInt64() % DefaultTaskSize)) + { + await GetCollection(collection).InsertManyAsync(list); + } + } + + /// + /// 批量插入实体对象列表到数据库(加锁)。 + /// + /// 实体类型。 + /// 事务会话对象。 + /// 要插入的实体对象列表。 + /// 集合名称。 + public async FTask InsertBatch(object transactionSession, IEnumerable list, string collection = null) + where T : Entity, new() + { + using (await _dataBaseLock.Wait(RandomHelper.RandInt64() % DefaultTaskSize)) + { + await GetCollection(collection).InsertManyAsync((IClientSessionHandle)transactionSession, list); + } + } + + /// + /// 插入BsonDocument到数据库(加锁)。 + /// + /// + /// + /// + public async Task Insert(BsonDocument bsonDocument, long taskId) where T : Entity + { + using (await _dataBaseLock.Wait(taskId)) + { + await GetCollection(typeof(T).Name).InsertOneAsync(bsonDocument); + } + } + + #endregion + + #region Remove + + /// + /// 根据ID删除单个实体对象(加锁)。 + /// + /// 实体类型。 + /// 事务会话对象。 + /// 要删除的实体的ID。 + /// 集合名称。 + /// 删除的实体数量。 + public async FTask Remove(object transactionSession, long id, string collection = null) + where T : Entity, new() + { + using (await _dataBaseLock.Wait(id)) + { + var result = await GetCollection(collection) + .DeleteOneAsync((IClientSessionHandle)transactionSession, d => d.Id == id); + return result.DeletedCount; + } + } + + /// + /// 根据ID删除单个实体对象(加锁)。 + /// + /// 实体类型。 + /// 要删除的实体的ID。 + /// 集合名称。 + /// 删除的实体数量。 + public async FTask Remove(long id, string collection = null) where T : Entity, new() + { + using (await _dataBaseLock.Wait(id)) + { + var result = await GetCollection(collection).DeleteOneAsync(d => d.Id == id); + return result.DeletedCount; + } + } + + /// + /// 根据ID和筛选条件删除多个实体对象(加锁)。 + /// + /// 实体类型。 + /// 异步锁Id。 + /// 事务会话对象。 + /// 筛选条件。 + /// 集合名称。 + /// 删除的实体数量。 + public async FTask Remove(long coroutineLockQueueKey, object transactionSession, + Expression> filter, string collection = null) where T : Entity, new() + { + using (await _dataBaseLock.Wait(coroutineLockQueueKey)) + { + var result = await GetCollection(collection) + .DeleteManyAsync((IClientSessionHandle)transactionSession, filter); + return result.DeletedCount; + } + } + + /// + /// 根据ID和筛选条件删除多个实体对象(加锁)。 + /// + /// 实体类型。 + /// 异步锁Id。 + /// 筛选条件。 + /// 集合名称。 + /// 删除的实体数量。 + public async FTask Remove(long coroutineLockQueueKey, Expression> filter, + string collection = null) where T : Entity, new() + { + using (await _dataBaseLock.Wait(coroutineLockQueueKey)) + { + var result = await GetCollection(collection).DeleteManyAsync(filter); + return result.DeletedCount; + } + } + + #endregion + + #region Index + + /// + /// 创建数据库索引(加锁)。 + /// + /// + /// + /// + /// + /// 使用例子(可多个): + /// 1 : Builders.IndexKeys.Ascending(d=>d.Id) + /// 2 : Builders.IndexKeys.Descending(d=>d.Id).Ascending(d=>d.Name) + /// 3 : Builders.IndexKeys.Descending(d=>d.Id),Builders.IndexKeys.Descending(d=>d.Name) + /// + public async FTask CreateIndex(string collection, params object[]? keys) where T : Entity + { + if (keys == null || keys.Length <= 0) + { + return; + } + + var indexModels = new List>(); + + foreach (object key in keys) + { + IndexKeysDefinition indexKeysDefinition = (IndexKeysDefinition)key; + + indexModels.Add(new CreateIndexModel(indexKeysDefinition)); + } + + await GetCollection(collection).Indexes.CreateManyAsync(indexModels); + } + + /// + /// 创建数据库的索引(加锁)。 + /// + /// 实体类型。 + /// 索引键定义。 + public async FTask CreateIndex(params object[]? keys) where T : Entity + { + if (keys == null) + { + return; + } + + List> indexModels = new List>(); + + foreach (object key in keys) + { + IndexKeysDefinition indexKeysDefinition = (IndexKeysDefinition)key; + + indexModels.Add(new CreateIndexModel(indexKeysDefinition)); + } + + await GetCollection().Indexes.CreateManyAsync(indexModels); + } + + #endregion + + #region CreateDB + + /// + /// 创建数据库集合(如果不存在)。 + /// + /// 实体类型。 + public async FTask CreateDB() where T : Entity + { + // 已经存在数据库表 + string name = typeof(T).Name; + + if (_collections.Contains(name)) + { + return; + } + + await _mongoDatabase.CreateCollectionAsync(name); + + _collections.Add(name); + } + + /// + /// 创建数据库集合(如果不存在)。 + /// + /// 实体类型。 + public async FTask CreateDB(Type type) + { + string name = type.Name; + + if (_collections.Contains(name)) + { + return; + } + + await _mongoDatabase.CreateCollectionAsync(name); + + _collections.Add(name); + } + + #endregion + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/DataBase/World.cs b/Fantasy/Fantays.Console/Runtime/Core/DataBase/World.cs new file mode 100644 index 0000000..c53351f --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/DataBase/World.cs @@ -0,0 +1,77 @@ +#pragma warning disable CS8603 // Possible null reference return. +#if FANTASY_NET +using Fantasy.Platform.Net; + +namespace Fantasy.DataBase +{ + /// + /// 表示一个游戏世界。 + /// + public sealed class World : IDisposable + { + /// + /// 获取游戏世界的唯一标识。 + /// + public byte Id { get; private init; } + /// + /// 获取游戏世界的数据库接口。 + /// + public IDataBase DataBase { get; private init; } + /// + /// 获取游戏世界的配置信息。 + /// + public WorldConfig Config => WorldConfigData.Instance.Get(Id); + + /// + /// 使用指定的配置信息创建一个游戏世界实例。 + /// + /// + /// + private World(Scene scene, byte worldConfigId) + { + Id = worldConfigId; + var worldConfig = Config; + var dbType = worldConfig.DbType.ToLower(); + + switch (dbType) + { + case "mongodb": + { + DataBase = new MongoDataBase(); + DataBase.Initialize(scene, worldConfig.DbConnection, worldConfig.DbName); + break; + } + default: + { + throw new Exception("No supported database"); + } + } + } + + /// + /// 创建一个指定唯一标识的游戏世界实例。 + /// + /// + /// 游戏世界的唯一标识。 + /// 游戏世界实例。 + internal static World Create(Scene scene, byte id) + { + if (!WorldConfigData.Instance.TryGet(id, out var worldConfigData)) + { + return null; + } + + return string.IsNullOrEmpty(worldConfigData.DbConnection) ? null : new World(scene, id); + } + + /// + /// 释放游戏世界资源。 + /// + public void Dispose() + { + DataBase.Dispose(); + } + } +} + +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/DataStructure/Collection/CircularBuffer.cs b/Fantasy/Fantays.Console/Runtime/Core/DataStructure/Collection/CircularBuffer.cs new file mode 100644 index 0000000..d80ec55 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/DataStructure/Collection/CircularBuffer.cs @@ -0,0 +1,346 @@ +using System; +using System.Collections.Generic; +using System.IO; +#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. + +namespace Fantasy.DataStructure.Collection +{ + /// 环形缓存(自增式缓存,自动扩充、不会收缩缓存、所以不要用这个操作过大的IO流) + /// 1、环大小8192,溢出的会自动增加环的大小。 + /// 2、每个块都是一个环形缓存,当溢出的时候会自动添加到下一个环中。 + /// 3、当读取完成后用过的环会放在缓存中,不会销毁掉。 + /// + /// 自增式缓存类,继承自 Stream 和 IDisposable 接口。 + /// 环形缓存具有自动扩充的特性,但不会收缩,适用于操作不过大的 IO 流。 + /// + public sealed class CircularBuffer : Stream, IDisposable + { + private byte[] _lastBuffer; + /// + /// 环形缓存块的默认大小 + /// + public const int ChunkSize = 8192; + private readonly Queue _bufferCache = new Queue(); + private readonly Queue _bufferQueue = new Queue(); + /// + /// 获取或设置环形缓存的第一个索引位置 + /// + public int FirstIndex { get; set; } + /// + /// 获取或设置环形缓存的最后一个索引位置 + /// + public int LastIndex { get; set; } + /// + /// 获取环形缓存的总长度 + /// + public override long Length + { + get + { + if (_bufferQueue.Count == 0) + { + return 0; + } + + return (_bufferQueue.Count - 1) * ChunkSize + LastIndex - FirstIndex; + } + } + + /// + /// 获取环形缓存的第一个块 + /// + public byte[] First + { + get + { + if (_bufferQueue.Count == 0) + { + AddLast(); + } + + return _bufferQueue.Peek(); + } + } + + /// + /// 获取环形缓存的最后一个块 + /// + public byte[] Last + { + get + { + if (_bufferQueue.Count == 0) + { + AddLast(); + } + + return _lastBuffer; + } + } + /// + /// 向环形缓存中添加一个新的块 + /// + public void AddLast() + { + var buffer = _bufferCache.Count > 0 ? _bufferCache.Dequeue() : new byte[ChunkSize]; + _bufferQueue.Enqueue(buffer); + _lastBuffer = buffer; + } + /// + /// 从环形缓存中移除第一个块 + /// + public void RemoveFirst() + { + _bufferCache.Enqueue(_bufferQueue.Dequeue()); + } + + /// + /// 从流中读取指定数量的数据到缓存。 + /// + /// 源数据流。 + /// 要读取的字节数。 + public void Read(Stream stream, int count) + { + if (count > Length) + { + throw new Exception($"bufferList length < count, {Length} {count}"); + } + + var copyCount = 0; + while (copyCount < count) + { + var n = count - copyCount; + if (ChunkSize - FirstIndex > n) + { + stream.Write(First, FirstIndex, n); + FirstIndex += n; + copyCount += n; + } + else + { + stream.Write(First, FirstIndex, ChunkSize - FirstIndex); + copyCount += ChunkSize - FirstIndex; + FirstIndex = 0; + RemoveFirst(); + } + } + } + + /// + /// 从缓存中读取指定数量的数据到内存。 + /// + /// 目标内存。 + /// 要读取的字节数。 + public void Read(Memory memory, int count) + { + if (count > Length) + { + throw new Exception($"bufferList length < count, {Length} {count}"); + } + + var copyCount = 0; + while (copyCount < count) + { + var n = count - copyCount; + var asMemory = First.AsMemory(); + + if (ChunkSize - FirstIndex > n) + { + var slice = asMemory.Slice(FirstIndex, n); + slice.CopyTo(memory.Slice(copyCount, n)); + FirstIndex += n; + copyCount += n; + } + else + { + var length = ChunkSize - FirstIndex; + var slice = asMemory.Slice(FirstIndex, length); + slice.CopyTo(memory.Slice(copyCount, length)); + copyCount += ChunkSize - FirstIndex; + FirstIndex = 0; + RemoveFirst(); + } + } + } + + /// + /// 从自定义流中读取数据到指定的缓冲区。 + /// + /// 目标缓冲区,用于存储读取的数据。 + /// 目标缓冲区中的起始偏移量。 + /// 要读取的字节数。 + /// 实际读取的字节数。 + public override int Read(byte[] buffer, int offset, int count) + { + if (buffer.Length < offset + count) + { + throw new Exception($"buffer length < count, buffer length: {buffer.Length} {offset} {count}"); + } + + var length = Length; + if (length < count) + { + count = (int) length; + } + + var copyCount = 0; + + // 循环直到成功读取所需的字节数 + while (copyCount < count) + { + var copyLength = count - copyCount; + + if (ChunkSize - FirstIndex > copyLength) + { + // 将数据从当前块的缓冲区复制到目标缓冲区 + Array.Copy(First, FirstIndex, buffer, copyCount + offset, copyLength); + + FirstIndex += copyLength; + copyCount += copyLength; + continue; + } + + // 复制当前块中剩余的数据,并切换到下一个块 + Array.Copy(First, FirstIndex, buffer, copyCount + offset, ChunkSize - FirstIndex); + copyCount += ChunkSize - FirstIndex; + FirstIndex = 0; + + RemoveFirst(); + } + + return count; + } + + /// + /// 将数据从给定的字节数组写入流中。 + /// + /// 包含要写入的数据的字节数组。 + public void Write(byte[] buffer) + { + Write(buffer, 0, buffer.Length); + } + + /// + /// 将数据从给定的流写入流中。 + /// + /// 包含要写入的数据的流。 + public void Write(Stream stream) + { + var copyCount = 0; + var count = (int) (stream.Length - stream.Position); + + while (copyCount < count) + { + if (LastIndex == ChunkSize) + { + AddLast(); + LastIndex = 0; + } + + var n = count - copyCount; + + if (ChunkSize - LastIndex > n) + { + _ = stream.Read(Last, LastIndex, n); + LastIndex += count - copyCount; + copyCount += n; + } + else + { + _ = stream.Read(Last, LastIndex, ChunkSize - LastIndex); + copyCount += ChunkSize - LastIndex; + LastIndex = ChunkSize; + } + } + } + + /// + /// 将数据从给定的字节数组写入流中。 + /// + /// 包含要写入的数据的字节数组。 + /// 开始写入的缓冲区中的索引。 + /// 要写入的字节数。 + public override void Write(byte[] buffer, int offset, int count) + { + var copyCount = 0; + + while (copyCount < count) + { + if (ChunkSize == LastIndex) + { + AddLast(); + LastIndex = 0; + } + + var byteLength = count - copyCount; + + if (ChunkSize - LastIndex > byteLength) + { + Array.Copy(buffer, copyCount + offset, Last, LastIndex, byteLength); + LastIndex += byteLength; + copyCount += byteLength; + } + else + { + Array.Copy(buffer, copyCount + offset, Last, LastIndex, ChunkSize - LastIndex); + copyCount += ChunkSize - LastIndex; + LastIndex = ChunkSize; + } + } + } + + /// + /// 获取一个值,指示流是否支持读取操作。 + /// + public override bool CanRead { get; } = true; + /// + /// 获取一个值,指示流是否支持寻找操作。 + /// + public override bool CanSeek { get; } = false; + /// + /// 获取一个值,指示流是否支持写入操作。 + /// + public override bool CanWrite { get; } = true; + /// + /// 获取或设置流中的位置。 + /// + public override long Position { get; set; } + + /// + /// 刷新流(在此实现中引发未实现异常)。 + /// + public override void Flush() + { + throw new NotImplementedException(); + } + + /// + /// 在流中寻找特定位置(在此实现中引发未实现异常)。 + /// + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotImplementedException(); + } + + /// + /// 设置流的长度(在此实现中引发未实现异常)。 + /// + public override void SetLength(long value) + { + throw new NotImplementedException(); + } + + /// + /// 释放 CustomStream 使用的所有资源。 + /// + public new void Dispose() + { + _bufferQueue.Clear(); + _lastBuffer = null; + FirstIndex = 0; + LastIndex = 0; + base.Dispose(); + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/DataStructure/Collection/ConcurrentOneToManyListPool.cs b/Fantasy/Fantays.Console/Runtime/Core/DataStructure/Collection/ConcurrentOneToManyListPool.cs new file mode 100644 index 0000000..e110a0b --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/DataStructure/Collection/ConcurrentOneToManyListPool.cs @@ -0,0 +1,197 @@ +#if !FANTASY_WEBGL +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using Fantasy.Pool; + +#pragma warning disable CS8603 // Possible null reference return. + +namespace Fantasy.DataStructure.Collection +{ + /// + /// 并发的一对多列表池,用于维护具有相同键的多个值的关联关系,实现了 接口。 + /// + /// 关键字的类型,不能为空。 + /// 值的类型。 + public class ConcurrentOneToManyListPool : ConcurrentOneToManyList, IDisposable, IPool where TKey : notnull + { + private bool _isPool; + private bool _isDispose; + + /// + /// 创建一个 的实例。 + /// + /// 创建的实例。 + public static ConcurrentOneToManyListPool Create() + { + var a = MultiThreadPool.Rent>(); + a._isDispose = false; + a._isPool = true; + return a; + } + + /// + /// 释放实例占用的资源。 + /// + public void Dispose() + { + if (_isDispose) + { + return; + } + + _isDispose = true; + // 清空实例的数据 + Clear(); + // 将实例返回到池中以便重用 + MultiThreadPool.Return(this); + } + + /// + /// 获取一个值,该值指示当前实例是否为对象池中的实例。 + /// + /// + public bool IsPool() + { + return _isPool; + } + + /// + /// 设置一个值,该值指示当前实例是否为对象池中的实例。 + /// + /// + public void SetIsPool(bool isPool) + { + _isPool = isPool; + } + } + + /// + /// 并发的一对多列表,用于维护具有相同键的多个值的关联关系。 + /// + /// 关键字的类型,不能为空。 + /// 值的类型。 + public class ConcurrentOneToManyList : ConcurrentDictionary> where TKey : notnull + { + private readonly Queue> _queue = new Queue>(); + private readonly int _recyclingLimit = 120; + + /// + /// 初始化 类的新实例。 + /// + public ConcurrentOneToManyList() + { + } + + /// + /// 设置最大缓存数量 + /// + /// + /// 1:防止数据量过大、所以超过recyclingLimit的数据还是走GC. + /// 2:设置成0不控制数量,全部缓存 + /// + public ConcurrentOneToManyList(int recyclingLimit) + { + _recyclingLimit = recyclingLimit; + } + + /// + /// 判断指定键的列表是否包含指定值。 + /// + /// 要搜索的键。 + /// 要搜索的值。 + /// 如果列表包含值,则为 true;否则为 false。 + public bool Contains(TKey key, TValue value) + { + TryGetValue(key, out var list); + + return list != null && list.Contains(value); + } + + /// + /// 向指定键的列表中添加一个值。 + /// + /// 要添加值的键。 + /// 要添加的值。 + public void Add(TKey key, TValue value) + { + if (!TryGetValue(key, out var list)) + { + list = Fetch(); + list.Add(value); + base[key] = list; + return; + } + + list.Add(value); + } + + /// + /// 获取指定键的列表中的第一个值。 + /// + /// 要获取第一个值的键。 + /// 指定键的列表中的第一个值,如果不存在则为默认值。 + public TValue First(TKey key) + { + return !TryGetValue(key, out var list) ? default : list.FirstOrDefault(); + } + + /// + /// 从指定键的列表中移除一个值。 + /// + /// 要移除值的键。 + /// 要移除的值。 + public void RemoveValue(TKey key, TValue value) + { + if (!TryGetValue(key, out var list)) return; + + list.Remove(value); + + if (list.Count == 0) RemoveKey(key); + } + + /// + /// 从字典中移除指定键以及其关联的列表。 + /// + /// 要移除的键。 + public void RemoveKey(TKey key) + { + if (!TryRemove(key, out var list)) return; + + Recycle(list); + } + + /// + /// 从队列中获取一个列表,如果队列为空则创建一个新的列表。 + /// + /// 获取的列表。 + private List Fetch() + { + return _queue.Count <= 0 ? new List() : _queue.Dequeue(); + } + + /// + /// 将一个列表回收到队列中。 + /// + /// 要回收的列表。 + private void Recycle(List list) + { + list.Clear(); + + if (_recyclingLimit != 0 && _queue.Count > _recyclingLimit) return; + + _queue.Enqueue(list); + } + + /// + /// 清空当前类的数据,包括从基类继承的数据以及自定义的数据队列。 + /// + protected new void Clear() + { + base.Clear(); + _queue.Clear(); + } + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/DataStructure/Collection/ConcurrentOneToManyQueuePool.cs b/Fantasy/Fantays.Console/Runtime/Core/DataStructure/Collection/ConcurrentOneToManyQueuePool.cs new file mode 100644 index 0000000..59c4367 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/DataStructure/Collection/ConcurrentOneToManyQueuePool.cs @@ -0,0 +1,194 @@ +#if !FANTASY_WEBGL +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using Fantasy.Pool; + +#pragma warning disable CS8603 + +namespace Fantasy.DataStructure.Collection +{ + /// + /// 表示一个并发的一对多队列池,用于维护具有相同键的多个值的关联关系,实现了 接口。 + /// + /// 关键字的类型,不能为空。 + /// 值的类型。 + public class ConcurrentOneToManyQueuePool : ConcurrentOneToManyQueue, IDisposable, IPool where TKey : notnull + { + private bool _isPool; + private bool _isDispose; + + /// + /// 创建并返回一个 的实例。 + /// + /// 创建的实例。 + public static ConcurrentOneToManyQueuePool Create() + { + var a = MultiThreadPool.Rent>(); + a._isDispose = false; + a._isPool = true; + return a; + } + + /// + /// 释放当前实例所占用的资源,并将实例返回到对象池中,以便重用。 + /// + public void Dispose() + { + if (_isDispose) + { + return; + } + + _isDispose = true; + Clear(); + // 将实例返回到对象池中,以便重用 + MultiThreadPool.Return(this); + } + + /// + /// 获取一个值,该值指示当前实例是否为对象池中的实例。 + /// + /// + public bool IsPool() + { + return _isPool; + } + + /// + /// 设置一个值,该值指示当前实例是否为对象池中的实例。 + /// + /// + public void SetIsPool(bool isPool) + { + _isPool = isPool; + } + } + + /// + /// 表示一个并发的一对多队列,用于维护具有相同键的多个值的关联关系。 + /// + /// 关键字的类型,不能为空。 + /// 值的类型。 + public class ConcurrentOneToManyQueue : ConcurrentDictionary> where TKey : notnull + { + private readonly Queue> _queue = new Queue>(); + private readonly int _recyclingLimit; + + /// + /// 设置最大缓存数量 + /// + /// + /// 1:防止数据量过大、所以超过recyclingLimit的数据还是走GC. + /// 2:设置成0不控制数量,全部缓存 + /// + public ConcurrentOneToManyQueue(int recyclingLimit = 0) + { + _recyclingLimit = recyclingLimit; + } + + /// + /// 判断指定键的队列是否包含指定值。 + /// + /// 要搜索的键。 + /// 要搜索的值。 + /// 如果队列包含值,则为 true;否则为 false。 + public bool Contains(TKey key, TValue value) + { + TryGetValue(key, out var list); + + return list != null && list.Contains(value); + } + + /// + /// 向指定键的队列中添加一个值。 + /// + /// 要添加值的键。 + /// 要添加的值。 + public void Enqueue(TKey key, TValue value) + { + if (!TryGetValue(key, out var list)) + { + list = Fetch(); + list.Enqueue(value); + TryAdd(key, list); + return; + } + + list.Enqueue(value); + } + + /// + /// 从指定键的队列中出队并返回一个值。 + /// + /// 要出队的键。 + /// 出队的值,如果队列为空则为默认值。 + public TValue Dequeue(TKey key) + { + if (!TryGetValue(key, out var list) || list.Count == 0) return default; + + var value = list.Dequeue(); + + if (list.Count == 0) RemoveKey(key); + + return value; + } + + /// + /// 尝试从指定键的队列中出队一个值。 + /// + /// 要出队的键。 + /// 出队的值,如果队列为空则为默认值。 + /// 如果成功出队,则为 true;否则为 false。 + public bool TryDequeue(TKey key, out TValue value) + { + value = Dequeue(key); + + return value != null; + } + + /// + /// 从字典中移除指定键以及其关联的队列。 + /// + /// 要移除的键。 + public void RemoveKey(TKey key) + { + if (!TryGetValue(key, out var list)) return; + + TryRemove(key, out _); + Recycle(list); + } + + /// + /// 从队列中获取一个新的队列,如果队列为空则创建一个新的队列。 + /// + /// 获取的队列。 + private Queue Fetch() + { + return _queue.Count <= 0 ? new Queue() : _queue.Dequeue(); + } + + /// + /// 将一个队列回收到队列池中。 + /// + /// 要回收的队列。 + private void Recycle(Queue list) + { + list.Clear(); + + if (_recyclingLimit != 0 && _queue.Count > _recyclingLimit) return; + + _queue.Enqueue(list); + } + + /// + /// 清空当前类的数据,包括从基类继承的键值对字典中的数据以及自定义的队列池。 + /// + protected new void Clear() + { + base.Clear(); + _queue.Clear(); + } + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/DataStructure/Collection/HashSetPool.cs b/Fantasy/Fantays.Console/Runtime/Core/DataStructure/Collection/HashSetPool.cs new file mode 100644 index 0000000..bb64ae8 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/DataStructure/Collection/HashSetPool.cs @@ -0,0 +1,134 @@ +using System; +using System.Collections.Generic; +using Fantasy.Pool; + +namespace Fantasy.DataStructure.Collection +{ + /// + /// 可释放的哈希集合对象池。 + /// + /// 哈希集合中元素的类型。 + public sealed class HashSetPool : HashSet, IDisposable, IPool + { + private bool _isPool; + private bool _isDispose; + + /// + /// 释放实例所占用的资源,并将实例返回到对象池中,以便重用。 + /// + public void Dispose() + { + if (_isDispose) + { + return; + } + + _isDispose = true; + Clear(); +#if FANTASY_WEBGL + Pool>.Return(this); +#else + MultiThreadPool.Return(this); +#endif + } + + /// + /// 创建一个 哈希集合池的实例。 + /// + /// 创建的实例。 + public static HashSetPool Create() + { +#if FANTASY_WEBGL + var list = Pool>.Rent(); + list._isDispose = false; + list._isPool = true; + return list; +#else + var list = MultiThreadPool.Rent>(); + list._isDispose = false; + list._isPool = true; + return list; +#endif + } + + /// + /// 获取一个值,该值指示当前实例是否为对象池中的实例。 + /// + /// + public bool IsPool() + { + return _isPool; + } + + /// + /// 设置一个值,该值指示当前实例是否为对象池中的实例。 + /// + /// + public void SetIsPool(bool isPool) + { + _isPool = isPool; + } + } + + /// + /// 基本哈希集合对象池,他自持有实际的哈希集合。 + /// + /// 哈希集合中元素的类型。 + public sealed class HashSetBasePool : IDisposable, IPool + { + private bool _isPool; + + /// + /// 存储实际的哈希集合 + /// + public HashSet Set = new HashSet(); + + /// + /// 创建一个 基本哈希集合对象池的实例。 + /// + /// 创建的实例。 + public static HashSetBasePool Create() + { +#if FANTASY_WEBGL + var hashSetBasePool = Pool>.Rent(); + hashSetBasePool._isPool = true; + return hashSetBasePool; +#else + var hashSetBasePool = MultiThreadPool.Rent>(); + hashSetBasePool._isPool = true; + return hashSetBasePool; +#endif + } + + /// + /// 释放实例所占用的资源,并将实例返回到对象池中,以便重用。 + /// + public void Dispose() + { + Set.Clear(); +#if FANTASY_WEBGL + Pool>.Return(this); +#else + MultiThreadPool.Return(this); +#endif + } + + /// + /// 获取一个值,该值指示当前实例是否为对象池中的实例。 + /// + /// + public bool IsPool() + { + return _isPool; + } + + /// + /// 设置一个值,该值指示当前实例是否为对象池中的实例。 + /// + /// + public void SetIsPool(bool isPool) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/DataStructure/Collection/ListPool.cs b/Fantasy/Fantays.Console/Runtime/Core/DataStructure/Collection/ListPool.cs new file mode 100644 index 0000000..de70fe2 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/DataStructure/Collection/ListPool.cs @@ -0,0 +1,101 @@ +using System; +using System.Collections.Generic; +using Fantasy.Pool; + +// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract + +namespace Fantasy.DataStructure.Collection +{ + /// + /// 可释放的列表(List)对象池。 + /// + /// 列表中元素的类型。 + public sealed class ListPool : List, IDisposable, IPool + { + private bool _isPool; + private bool _isDispose; + + /// + /// 释放实例所占用的资源,并将实例返回到对象池中,以便重用。 + /// + public void Dispose() + { + if (_isDispose) + { + return; + } + + _isDispose = true; + Clear(); +#if FANTASY_WEBGL + Pool>.Return(this); +#else + MultiThreadPool.Return(this); +#endif + } + + /// + /// 使用指定的元素创建一个 列表(List)对象池的实例。 + /// + /// 要添加到列表的元素。 + /// 创建的实例。 + public static ListPool Create(params T[] args) + { +#if FANTASY_WEBGL + var list = Pool>.Rent(); +#else + var list = MultiThreadPool.Rent>(); +#endif + list._isDispose = false; + list._isPool = true; + + if (args != null) + { + list.AddRange(args); + } + + return list; + } + + /// + /// 使用指定的列表创建一个 列表(List)对象池的实例。 + /// + /// 要添加到列表的元素列表。 + /// 创建的实例。 + public static ListPool Create(List args) + { +#if FANTASY_WEBGL + var list = Pool>.Rent(); +#else + var list = MultiThreadPool.Rent>(); +#endif + list._isDispose = false; + list._isPool = true; + + if (args != null) + { + list.AddRange(args); + } + + return list; + } + + /// + /// 获取一个值,该值指示当前实例是否为对象池中的实例。 + /// + /// + public bool IsPool() + { + return _isPool; + } + + /// + /// 设置一个值,该值指示当前实例是否为对象池中的实例。 + /// + /// + public void SetIsPool(bool isPool) + { + _isPool = isPool; + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/DataStructure/Collection/OneToManyHashSetPool.cs b/Fantasy/Fantays.Console/Runtime/Core/DataStructure/Collection/OneToManyHashSetPool.cs new file mode 100644 index 0000000..8a5766c --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/DataStructure/Collection/OneToManyHashSetPool.cs @@ -0,0 +1,208 @@ +using System; +using System.Collections.Generic; +using Fantasy.Pool; + +#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type. + +namespace Fantasy.DataStructure.Collection +{ + /// + /// 一对多哈希集合(OneToManyHashSet)对象池。 + /// + /// 键的类型。 + /// 值的类型。 + public class OneToManyHashSetPool : OneToManyHashSet, IDisposable, IPool where TKey : notnull + { + private bool _isPool; + private bool _isDispose; + + /// + /// 创建一个 一对多哈希集合(OneToManyHashSet)对象池的实例。 + /// + /// 创建的实例。 + public static OneToManyHashSetPool Create() + { +#if FANTASY_WEBGL + var a = Pool>.Rent(); +#else + var a = MultiThreadPool.Rent>(); +#endif + a._isDispose = false; + a._isPool = true; + return a; + } + + /// + /// 释放实例所占用的资源,并将实例返回到对象池中,以便重用。 + /// + public void Dispose() + { + if (_isDispose) + { + return; + } + + _isDispose = true; + Clear(); +#if FANTASY_WEBGL + Pool>.Return(this); +#else + MultiThreadPool.Return(this); +#endif + } + + /// + /// 获取一个值,该值指示当前实例是否为对象池中的实例。 + /// + /// + public bool IsPool() + { + return _isPool; + } + + /// + /// 设置一个值,该值指示当前实例是否为对象池中的实例。 + /// + /// + public void SetIsPool(bool isPool) + { + _isPool = isPool; + } + } + + /// + /// 一对多哈希集合(OneToManyHashSet),用于创建和管理键对应多个值的集合。 + /// + /// 键的类型。 + /// 值的类型。 + public class OneToManyHashSet : Dictionary> where TKey : notnull + { + /// 用于回收和重用的空闲值集合队列。 + private readonly Queue> _queue = new Queue>(); + /// 设置最大回收限制,用于控制值集合的最大数量。 + private readonly int _recyclingLimit = 120; + /// 一个空的、不包含任何元素的哈希集合,用于在查找失败时返回。 + private static HashSet _empty = new HashSet(); + + /// + /// 初始化 类的新实例。 + /// + public OneToManyHashSet() { } + + /// + /// 设置最大缓存数量 + /// + /// + /// 1:防止数据量过大、所以超过recyclingLimit的数据还是走GC. + /// 2:设置成0不控制数量,全部缓存 + /// + public OneToManyHashSet(int recyclingLimit) + { + _recyclingLimit = recyclingLimit; + } + + /// + /// 判断指定的键值对是否存在于集合中。 + /// + /// 键。 + /// 值。 + /// 如果存在则为 true,否则为 false。 + public bool Contains(TKey key, TValue value) + { + TryGetValue(key, out var list); + + return list != null && list.Contains(value); + } + + /// + /// 添加指定的键值对到集合中。 + /// + /// 键。 + /// 值。 + public void Add(TKey key, TValue value) + { + if (!TryGetValue(key, out var list)) + { + list = Fetch(); + list.Add(value); + Add(key, list); + + return; + } + + list.Add(value); + } + + /// + /// 从集合中移除指定键对应的值。 + /// + /// 键。 + /// 要移除的值。 + public void RemoveValue(TKey key, TValue value) + { + if (!TryGetValue(key, out var list)) return; + + list.Remove(value); + + if (list.Count == 0) RemoveKey(key); + } + + /// + /// 从集合中移除指定键及其对应的值集合。 + /// + /// 键。 + public void RemoveKey(TKey key) + { + if (!TryGetValue(key, out var list)) return; + + Remove(key); + Recycle(list); + } + + /// + /// 获取指定键对应的值集合,如果不存在则返回一个空的哈希集合。 + /// + /// 键。 + /// 对应的值集合或空的哈希集合。 + public HashSet GetValue(TKey key) + { + if (TryGetValue(key, out HashSet value)) + { + return value; + } + + return _empty; + } + + /// + /// 从队列中获取一个空闲的值集合,或者创建一个新的。 + /// + /// 值集合。 + private HashSet Fetch() + { + return _queue.Count <= 0 ? new HashSet() : _queue.Dequeue(); + } + + /// + /// 回收值集合到队列中,以便重复利用。 + /// + /// 要回收的值集合。 + private void Recycle(HashSet list) + { + list.Clear(); + + if (_recyclingLimit != 0 && _queue.Count > _recyclingLimit) return; + + _queue.Enqueue(list); + } + + /// + /// 清空集合中的数据并和队列。 + /// + protected new void Clear() + { + base.Clear(); + _queue.Clear(); + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/DataStructure/Collection/OneToManyListPool.cs b/Fantasy/Fantays.Console/Runtime/Core/DataStructure/Collection/OneToManyListPool.cs new file mode 100644 index 0000000..80568ef --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/DataStructure/Collection/OneToManyListPool.cs @@ -0,0 +1,232 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Fantasy.Pool; + +#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type. +#pragma warning disable CS8603 // Possible null reference return. + +namespace Fantasy.DataStructure.Collection +{ + /// + /// 可回收的、一对多关系的列表池。 + /// + /// 键的类型。 + /// 值的类型。 + public class OneToManyListPool : OneToManyList, IDisposable, IPool where TKey : notnull + { + private bool _isPool; + private bool _isDispose; + + /// + /// 创建一个 一对多关系的列表池的实例。 + /// + /// 创建的实例。 + public static OneToManyListPool Create() + { +#if FANTASY_WEBGL || FANTASY_EXPORTER + var list = Pool>.Rent(); +#else + var list = MultiThreadPool.Rent>(); +#endif + list._isDispose = false; + list._isPool = true; + return list; + } + + /// + /// 释放当前对象所占用的资源,并将对象回收到对象池中。 + /// + public void Dispose() + { + if (_isDispose) + { + return; + } + + _isDispose = true; + Clear(); +#if FANTASY_WEBGL || FANTASY_EXPORTER + Pool>.Return(this); +#else + MultiThreadPool.Return(this); +#endif + } + + /// + /// 获取一个值,该值指示当前实例是否为对象池中的实例。 + /// + /// + public bool IsPool() + { + return _isPool; + } + + /// + /// 设置一个值,该值指示当前实例是否为对象池中的实例。 + /// + /// + public void SetIsPool(bool isPool) + { + _isPool = isPool; + } + } + + /// + /// 一对多关系的列表字典。 + /// + /// 键的类型。 + /// 值的类型。 + public class OneToManyList : Dictionary> where TKey : notnull + { + private readonly int _recyclingLimit = 120; + private static readonly List Empty = new List(); + private readonly Queue> _queue = new Queue>(); + + /// + /// 初始化一个新的 实例。 + /// + public OneToManyList() { } + + /// + /// 设置最大缓存数量 + /// + /// + /// 1:防止数据量过大、所以超过recyclingLimit的数据还是走GC. + /// 2:设置成0不控制数量,全部缓存 + /// + public OneToManyList(int recyclingLimit) + { + _recyclingLimit = recyclingLimit; + } + + /// + /// 判断给定的键和值是否存在于列表中。 + /// + /// 要搜索的键。 + /// 要搜索的值。 + /// 如果存在则为 ,否则为 + public bool Contains(TKey key, TValue value) + { + TryGetValue(key, out var list); + + return list != null && list.Contains(value); + } + + /// + /// 向列表中添加指定键和值。 + /// + /// 要添加值的键。 + /// 要添加的值。 + public void Add(TKey key, TValue value) + { + if (!TryGetValue(key, out var list)) + { + list = Fetch(); + list.Add(value); + Add(key, list); + + return; + } + + list.Add(value); + } + + /// + /// 获取指定键对应的列表中的第一个值。 + /// + /// 要获取值的键。 + /// 键对应的列表中的第一个值。 + public TValue First(TKey key) + { + return !TryGetValue(key, out var list) ? default : list.FirstOrDefault(); + } + + /// + /// 从列表中移除指定键和值。 + /// + /// 要移除值的键。 + /// 要移除的值。 + /// 如果成功移除则为 ,否则为 + public bool RemoveValue(TKey key, TValue value) + { + if (!TryGetValue(key, out var list)) + { + return true; + } + + var isRemove = list.Remove(value); + + if (list.Count == 0) + { + isRemove = RemoveByKey(key); + } + + return isRemove; + } + + /// + /// 从列表中移除指定键及其关联的所有值。 + /// + /// 要移除的键。 + /// 如果成功移除则为 ,否则为 + public bool RemoveByKey(TKey key) + { + if (!TryGetValue(key, out var list)) + { + return false; + } + + Remove(key); + Recycle(list); + return true; + } + + /// + /// 获取指定键关联的所有值的列表。 + /// + /// 要获取值的键。 + /// 键关联的所有值的列表。 + public List GetValues(TKey key) + { + if (TryGetValue(key, out List list)) + { + return list; + } + + return Empty; + } + + /// + /// 清除字典中的所有键值对,并回收相关的值集合。 + /// + public new void Clear() + { + foreach (var keyValuePair in this) Recycle(keyValuePair.Value); + + base.Clear(); + } + + /// + /// 从空闲值集合队列中获取一个值集合,如果队列为空则创建一个新的值集合。 + /// + /// 从队列中获取的值集合。 + private List Fetch() + { + return _queue.Count <= 0 ? new List() : _queue.Dequeue(); + } + + /// + /// 回收一个不再使用的值集合到空闲值集合队列中。 + /// + /// 要回收的值集合。 + private void Recycle(List list) + { + list.Clear(); + + if (_recyclingLimit != 0 && _queue.Count > _recyclingLimit) return; + + _queue.Enqueue(list); + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/DataStructure/Collection/OneToManyQueuePool.cs b/Fantasy/Fantays.Console/Runtime/Core/DataStructure/Collection/OneToManyQueuePool.cs new file mode 100644 index 0000000..222576e --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/DataStructure/Collection/OneToManyQueuePool.cs @@ -0,0 +1,204 @@ +using System; +using System.Collections.Generic; +using Fantasy.Pool; + +#pragma warning disable CS8603 + +namespace Fantasy.DataStructure.Collection +{ + /// + /// 支持一对多关系的队列池,用于存储具有相同键的值的队列集合。 + /// + /// 键的类型。 + /// 值的类型。 + public class OneToManyQueuePool : OneToManyQueue, IDisposable, IPool where TKey : notnull + { + private bool _isPool; + private bool _isDispose; + + /// + /// 创建一个 一对多关系的队列池的实例。 + /// + /// 创建的实例。 + public static OneToManyQueuePool Create() + { +#if FANTASY_WEBGL + var a = Pool>.Rent(); +#else + var a = MultiThreadPool.Rent>(); +#endif + a._isDispose = false; + a._isPool = true; + return a; + } + + /// + /// 释放当前实例所占用的资源,并将实例回收到对象池中。 + /// + public void Dispose() + { + if (_isDispose) + { + return; + } + + _isDispose = true; + Clear(); +#if FANTASY_WEBGL + Pool>.Return(this); +#else + MultiThreadPool.Return(this); +#endif + } + + /// + /// 获取一个值,该值指示当前实例是否为对象池中的实例。 + /// + /// + public bool IsPool() + { + return _isPool; + } + + /// + /// 设置一个值,该值指示当前实例是否为对象池中的实例。 + /// + /// + public void SetIsPool(bool isPool) + { + _isPool = isPool; + } + } + + /// + /// 支持一对多关系的队列,用于存储具有相同键的值的队列集合。 + /// + /// 键的类型。 + /// 值的类型。 + public class OneToManyQueue : Dictionary> where TKey : notnull + { + private readonly Queue> _queue = new Queue>(); + private readonly int _recyclingLimit; + + /// + /// 创建一个 一对多关系的队列的实例。设置最大缓存数量 + /// + /// + /// 1:防止数据量过大、所以超过recyclingLimit的数据还是走GC. + /// 2:设置成0不控制数量,全部缓存 + /// + public OneToManyQueue(int recyclingLimit = 0) + { + _recyclingLimit = recyclingLimit; + } + + /// + /// 判断指定键的值队列是否包含指定的值。 + /// + /// 要查找的键。 + /// 要查找的值。 + /// 如果存在,则为 true;否则为 false + public bool Contains(TKey key, TValue value) + { + TryGetValue(key, out var list); + + return list != null && list.Contains(value); + } + + /// + /// 将指定的值添加到指定键的值队列中。 + /// + /// 要添加值的键。 + /// 要添加的值。 + public void Enqueue(TKey key, TValue value) + { + if (!TryGetValue(key, out var list)) + { + list = Fetch(); + list.Enqueue(value); + Add(key, list); + return; + } + + list.Enqueue(value); + } + + /// + /// 从指定键的值队列中出队一个值。 + /// + /// 要出队的键。 + /// 出队的值。 + public TValue Dequeue(TKey key) + { + if (!TryGetValue(key, out var list) || list.Count == 0) + { + return default; + } + + var value = list.Dequeue(); + + if (list.Count == 0) + { + RemoveKey(key); + } + + return value; + } + + /// + /// 尝试从指定键的值队列中出队一个值。 + /// + /// 要出队的键。 + /// 出队的值。 + /// 如果成功出队,则为 true;否则为 false + public bool TryDequeue(TKey key, out TValue value) + { + value = Dequeue(key); + + return value != null; + } + + /// + /// 从字典中移除指定键及其对应的值队列。 + /// + /// 要移除的键。 + public void RemoveKey(TKey key) + { + if (!TryGetValue(key, out var list)) return; + + Remove(key); + Recycle(list); + } + + /// + /// 从队列池中获取一个值队列。如果队列池为空,则创建一个新的值队列。 + /// + /// 获取的值队列。 + private Queue Fetch() + { + return _queue.Count <= 0 ? new Queue() : _queue.Dequeue(); + } + + /// + /// 回收一个不再使用的值队列到队列池中,以便重用。 + /// + /// 要回收的值队列。 + private void Recycle(Queue list) + { + list.Clear(); + + if (_recyclingLimit != 0 && _queue.Count > _recyclingLimit) return; + + _queue.Enqueue(list); + } + + /// + /// 清空当前实例的数据,同时回收所有值队列。 + /// + protected new void Clear() + { + base.Clear(); + _queue.Clear(); + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/DataStructure/Collection/ReuseList.cs b/Fantasy/Fantays.Console/Runtime/Core/DataStructure/Collection/ReuseList.cs new file mode 100644 index 0000000..b4395e8 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/DataStructure/Collection/ReuseList.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using Fantasy.Pool; + +namespace Fantasy.DataStructure.Collection +{ + /// + /// 可重用的列表,继承自 类。该类支持通过对象池重用列表实例,以减少对象分配和释放的开销。 + /// + /// 列表中元素的类型。 + public sealed class ReuseList : List, IDisposable, IPool + { + private bool _isPool; + private bool _isDispose; + + /// + /// 创建一个 可重用的列表的实例。 + /// + /// 创建的实例。 + public static ReuseList Create() + { +#if FANTASY_WEBGL + var list = Pool>.Rent(); +#else + var list = MultiThreadPool.Rent>(); +#endif + list._isDispose = false; + list._isPool = true; + return list; + } + + /// + /// 释放该实例所占用的资源,并将实例返回到对象池中,以便重用。 + /// + public void Dispose() + { + if (_isDispose) + { + return; + } + + _isDispose = true; + Clear(); +#if FANTASY_WEBGL + Pool>.Return(this); +#else + MultiThreadPool.Return(this); +#endif + } + + /// + /// 获取一个值,该值指示当前实例是否为对象池中的实例。 + /// + /// + public bool IsPool() + { + return _isPool; + } + + /// + /// 设置一个值,该值指示当前实例是否为对象池中的实例。 + /// + /// + public void SetIsPool(bool isPool) + { + _isPool = isPool; + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/DataStructure/Collection/SortedConcurrentOneToManyListPool.cs b/Fantasy/Fantays.Console/Runtime/Core/DataStructure/Collection/SortedConcurrentOneToManyListPool.cs new file mode 100644 index 0000000..464ab58 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/DataStructure/Collection/SortedConcurrentOneToManyListPool.cs @@ -0,0 +1,226 @@ +#if !FANTASY_WEBGL +using System; +using System.Collections.Generic; +using System.Linq; +using Fantasy.Pool; + +#pragma warning disable CS8603 + +namespace Fantasy.DataStructure.Collection +{ + /// + /// 基于排序字典和并发集合实现的一对多映射列表的对象池包装类,继承自 类, + /// 同时实现了 接口,以支持对象的重用和释放。 + /// + /// 键的类型。 + /// 值的类型。 + public class SortedConcurrentOneToManyListPool : SortedConcurrentOneToManyList, IDisposable, IPool where TKey : notnull + { + private bool _isPool; + private bool _isDispose; + + /// + /// 创建一个新的 实例,使用默认的参数设置。 + /// + /// 新创建的 实例。 + public static SortedConcurrentOneToManyListPool Create() + { + var a = MultiThreadPool.Rent>(); + a._isDispose = false; + a._isPool = true; + return a; + } + + /// + /// 释放当前对象池实例,将其返回到对象池以供重用。 + /// + public void Dispose() + { + if (_isDispose) + { + return; + } + + _isDispose = true; + Clear(); + MultiThreadPool.Return(this); + } + + /// + /// 获取一个值,该值指示当前实例是否为对象池中的实例。 + /// + /// + public bool IsPool() + { + return _isPool; + } + + /// + /// 设置一个值,该值指示当前实例是否为对象池中的实例。 + /// + /// + public void SetIsPool(bool isPool) + { + _isPool = isPool; + } + } + + /// + /// 基于排序字典和并发集合实现的一多对映射列表类,继承自 类, + /// 用于在多个值与一个键关联的情况下进行管理和存储。该类支持并发操作,适用于多线程环境。 + /// + /// 键的类型。 + /// 值的类型。 + public class SortedConcurrentOneToManyList : SortedDictionary> where TKey : notnull + { + /// 用于同步操作的锁对象,它确保在多线程环境下对数据的安全访问。 + private readonly object _lockObject = new object(); + /// 用于存储缓存的队列。 + private readonly Queue> _queue = new Queue>(); + /// 控制缓存回收的限制。当缓存的数量超过此限制时,旧的缓存将会被回收。 + private readonly int _recyclingLimit; + + /// + /// 初始化一个新的 类的实例,使用默认的参数设置。 + /// + public SortedConcurrentOneToManyList() + { + } + + /// + /// 初始化一个新的 类的实例,指定最大缓存数量。 + /// + /// + /// 1:防止数据量过大、所以超过recyclingLimit的数据还是走GC. + /// 2:设置成0不控制数量,全部缓存 + /// + public SortedConcurrentOneToManyList(int recyclingLimit = 0) + { + _recyclingLimit = recyclingLimit; + } + + /// + /// 检查指定的键和值是否存在于映射列表中。 + /// + /// 要检查的键。 + /// 要检查的值。 + /// 如果存在,则为 true;否则为 false。 + public bool Contains(TKey key, TValue value) + { + lock (_lockObject) + { + TryGetValue(key, out var list); + + return list != null && list.Contains(value); + } + } + + /// + /// 将指定的值添加到与指定键关联的列表中。 + /// + /// 要关联值的键。 + /// 要添加到列表的值。 + public void Add(TKey key, TValue value) + { + lock (_lockObject) + { + if (!TryGetValue(key, out var list)) + { + list = Fetch(); + list.Add(value); + base[key] = list; + return; + } + + list.Add(value); + } + } + + /// + /// 获取与指定键关联的列表中的第一个值。 + /// 如果列表不存在或为空,则返回默认值。 + /// + /// 要获取第一个值的键。 + /// 第一个值,或默认值。 + public TValue First(TKey key) + { + lock (_lockObject) + { + return !TryGetValue(key, out var list) ? default : list.FirstOrDefault(); + } + } + + /// + /// 从与指定键关联的列表中移除指定的值。 + /// 如果列表不存在或值不存在于列表中,则不执行任何操作。 + /// + /// 要移除值的键。 + /// 要移除的值。 + public void RemoveValue(TKey key, TValue value) + { + lock (_lockObject) + { + if (!TryGetValue(key, out var list)) return; + + list.Remove(value); + + if (list.Count == 0) RemoveKey(key); + } + } + + /// + /// 从映射列表中移除指定的键及其关联的列表。 + /// 如果键不存在于映射列表中,则不执行任何操作。 + /// + /// 要移除的键。 + public void RemoveKey(TKey key) + { + lock (_lockObject) + { + if (!TryGetValue(key, out var list)) return; + + Remove(key); + + Recycle(list); + } + } + + /// + /// 从缓存中获取一个可重用的列表。如果缓存中不存在列表,则创建一个新的列表并返回。 + /// + /// 可重用的列表。 + private List Fetch() + { + lock (_lockObject) + { + return _queue.Count <= 0 ? new List() : _queue.Dequeue(); + } + } + + /// + /// 将不再使用的列表回收到缓存中,以便重复利用。如果缓存数量超过限制,则丢弃列表而不进行回收。 + /// + /// 要回收的列表。 + private void Recycle(List list) + { + lock (_lockObject) + { + list.Clear(); + + if (_recyclingLimit != 0 && _queue.Count > _recyclingLimit) return; + + _queue.Enqueue(list); + } + } + + /// + /// 清空映射列表以及队列。 + /// + protected new void Clear() + { + base.Clear(); + _queue.Clear(); + } + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/DataStructure/Collection/SortedOneToManyHashSetPool.cs b/Fantasy/Fantays.Console/Runtime/Core/DataStructure/Collection/SortedOneToManyHashSetPool.cs new file mode 100644 index 0000000..8158db5 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/DataStructure/Collection/SortedOneToManyHashSetPool.cs @@ -0,0 +1,192 @@ +using System; +using System.Collections.Generic; +using Fantasy.Pool; + +namespace Fantasy.DataStructure.Collection +{ + /// + /// 基于排序字典实现的一对多关系的映射哈希集合的对象池包装类,将唯一键映射到多个值的哈希集合。 + /// 同时实现了 接口,以支持对象的重用和释放。 + /// + /// 字典中键的类型。 + /// 哈希集合中值的类型。 + public class SortedOneToManyHashSetPool : SortedOneToManyHashSet, IDisposable, IPool where TKey : notnull + { + private bool _isPool; + private bool _isDispose; + + /// + /// 创建一个 实例。 + /// + /// 新创建的实例。 + public static SortedOneToManyHashSetPool Create() + { +#if FANTASY_WEBGL + var a = Pool>.Rent(); +#else + var a = MultiThreadPool.Rent>(); +#endif + a._isDispose = false; + a._isPool = true; + return a; + } + + /// + /// 释放当前对象池实例,将其返回到对象池以供重用。 + /// + public void Dispose() + { + if (_isDispose) + { + return; + } + + _isDispose = true; + Clear(); +#if FANTASY_WEBGL + Pool>.Return(this); +#else + MultiThreadPool.Return(this); +#endif + } + + /// + /// 获取一个值,该值指示当前实例是否为对象池中的实例。 + /// + /// + public bool IsPool() + { + return _isPool; + } + + /// + /// 设置一个值,该值指示当前实例是否为对象池中的实例。 + /// + /// + public void SetIsPool(bool isPool) + { + _isPool = isPool; + } + } + + /// + /// 基于排序字典实现的一对多关系的映射哈希集合类,将唯一键映射到多个值的哈希集合。 + /// 用于在多个值与一个键关联的情况下进行管理和存储。 + /// + /// 字典中键的类型。 + /// 集合中值的类型。 + public class SortedOneToManyHashSet : SortedDictionary> where TKey : notnull + { + private readonly Queue> _queue = new Queue>(); + private readonly int _recyclingLimit = 120; + + /// + /// 创建一个新的 实例。 + /// + public SortedOneToManyHashSet() { } + + /// + /// 创建一个新的 实例,设置最大缓存数量 + /// + /// + /// 1:防止数据量过大、所以超过recyclingLimit的数据还是走GC. + /// 2:设置成0不控制数量,全部缓存 + /// + public SortedOneToManyHashSet(int recyclingLimit) + { + _recyclingLimit = recyclingLimit; + } + + /// + /// 判断哈希集合中是否包含指定的键值对。 + /// + /// 要查找的键。 + /// 要查找的值。 + /// 如果键值对存在,则为 true;否则为 false。 + public bool Contains(TKey key, TValue value) + { + TryGetValue(key, out var list); + + return list != null && list.Contains(value); + } + + /// + /// 将指定值添加到给定键关联的哈希集合中。 + /// + /// 要添加值的键。 + /// 要添加的值。 + public void Add(TKey key, TValue value) + { + if (!TryGetValue(key, out var list)) + { + list = Fetch(); + list.Add(value); + Add(key, list); + + return; + } + + list.Add(value); + } + + /// + /// 从指定键关联的哈希集合中移除特定值。 + /// 如果哈希集合不存在或值不存在于集合中,则不执行任何操作。 + /// + /// 要移除值的键。 + /// 要移除的值。 + public void RemoveValue(TKey key, TValue value) + { + if (!TryGetValue(key, out var list)) return; + + list.Remove(value); + + if (list.Count == 0) RemoveKey(key); + } + + /// + /// 从字典中移除指定键以及关联的哈希集合,并将集合进行回收。 + /// 如果键不存在于映射列表中,则不执行任何操作。 + /// + /// 要移除的键。 + public void RemoveKey(TKey key) + { + if (!TryGetValue(key, out var list)) return; + + Remove(key); + + Recycle(list); + } + + /// + /// 获取一个空的或回收的哈希集合。 + /// + /// 获取的哈希集合实例。 + private HashSet Fetch() + { + return _queue.Count <= 0 ? new HashSet() : _queue.Dequeue(); + } + + /// + /// 回收一个哈希集合,将其清空并放入回收队列中。 + /// + /// 要回收的哈希集合。 + private void Recycle(HashSet list) + { + list.Clear(); + + if (_recyclingLimit != 0 && _queue.Count > _recyclingLimit) return; + + _queue.Enqueue(list); + } + + /// + /// 重写 Clear 方法,清空字典并清空回收队列。 + /// + protected new void Clear() + { + base.Clear(); + _queue.Clear(); + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/DataStructure/Collection/SortedOneToManyListPool.cs b/Fantasy/Fantays.Console/Runtime/Core/DataStructure/Collection/SortedOneToManyListPool.cs new file mode 100644 index 0000000..f0bae12 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/DataStructure/Collection/SortedOneToManyListPool.cs @@ -0,0 +1,217 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Fantasy.Pool; + +#pragma warning disable CS8603 + +namespace Fantasy.DataStructure.Collection +{ + /// + /// 基于排序字典实现的一对多映射列表的对象池包装类,继承自 类, + /// 同时实现了 接口,以支持对象的重用和释放。 + /// + /// 字典中键的类型。 + /// 列表中值的类型。 + public class SortedOneToManyListPool : SortedOneToManyList, IDisposable, IPool where TKey : notnull + { + private bool _isPool; + private bool _isDispose; + + /// + /// 创建一个 实例。 + /// + /// 新创建的实例。 + public static SortedOneToManyListPool Create() + { +#if FANTASY_WEBGL + var a = Pool>.Rent(); +#else + var a = MultiThreadPool.Rent>(); +#endif + a._isDispose = false; + a._isPool = true; + return a; + } + + /// + /// 释放当前对象池实例,将其返回到对象池以供重用。 + /// + public void Dispose() + { + if (_isDispose) + { + return; + } + + _isDispose = true; + Clear(); +#if FANTASY_WEBGL + Pool>.Return(this); +#else + MultiThreadPool.Return(this); +#endif + } + + /// + /// 获取一个值,该值指示当前实例是否为对象池中的实例。 + /// + /// + public bool IsPool() + { + return _isPool; + } + + /// + /// 设置一个值,该值指示当前实例是否为对象池中的实例。 + /// + /// + public void SetIsPool(bool isPool) + { + _isPool = isPool; + } + } + + /// + /// 基于排序字典实现的一对多关系的映射列表类,将唯一键映射到包含多个值的列表。 + /// 用于在多个值与一个键关联的情况下进行管理和存储。 + /// + /// 字典中键的类型。 + /// 列表中值的类型。 + public class SortedOneToManyList : SortedDictionary> where TKey : notnull + { + private readonly Queue> _queue = new Queue>(); + private readonly int _recyclingLimit; + + /// + /// 创建一个新的 实例。 + /// + public SortedOneToManyList() + { + } + + /// + /// 创建一个新的 实例,设置最大缓存数量 + /// + /// + /// 1:防止数据量过大、所以超过recyclingLimit的数据还是走GC. + /// 2:设置成0不控制数量,全部缓存 + /// + public SortedOneToManyList(int recyclingLimit = 0) + { + _recyclingLimit = recyclingLimit; + } + + /// + /// 判断列表中是否包含指定的键值对。 + /// + /// 要查找的键。 + /// 要查找的值。 + /// 如果键值对存在,则为 true;否则为 false。 + public bool Contains(TKey key, TValue value) + { + TryGetValue(key, out var list); + + return list != null && list.Contains(value); + } + + /// + /// 将指定值添加到给定键关联的列表中。 + /// + /// 要添加值的键。 + /// 要添加的值。 + public void Add(TKey key, TValue value) + { + if (!TryGetValue(key, out var list)) + { + list = Fetch(); + list.Add(value); + base[key] = list; + return; + } + + list.Add(value); + } + + /// + /// 获取指定键关联的列表中的第一个值。 + /// + /// 要查找值的键。 + /// 指定键关联的列表中的第一个值,如果列表为空则返回默认值。 + public TValue First(TKey key) + { + return !TryGetValue(key, out var list) ? default : list.FirstOrDefault(); + } + + /// + /// 从指定键关联的列表中移除特定值。 + /// + /// 要移除值的键。 + /// 要移除的值。 + + public void RemoveValue(TKey key, TValue value) + { + if (!TryGetValue(key, out var list)) + { + return; + } + + list.Remove(value); + + if (list.Count == 0) + { + RemoveKey(key); + } + } + + /// + /// 从字典中移除指定键以及关联的列表,并将列表进行回收。 + /// + /// 要移除的键。 + + public void RemoveKey(TKey key) + { + if (!TryGetValue(key, out var list)) + { + return; + } + + Remove(key); + Recycle(list); + } + + /// + /// 获取一个空的或回收的列表。 + /// + /// 获取的列表实例。 + private List Fetch() + { + return _queue.Count <= 0 ? new List() : _queue.Dequeue(); + } + + /// + /// 回收一个列表,将其清空并放入回收队列中。如果缓存数量超过限制,则丢弃列表而不进行回收 + /// + /// 要回收的列表。 + private void Recycle(List list) + { + list.Clear(); + + if (_recyclingLimit != 0 && _queue.Count > _recyclingLimit) + { + return; + } + + _queue.Enqueue(list); + } + + /// + /// 重写 Clear 方法,清空字典并清空回收队列。 + /// + protected new void Clear() + { + base.Clear(); + _queue.Clear(); + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/DataStructure/Dictionary/DictionaryExtensions.cs b/Fantasy/Fantays.Console/Runtime/Core/DataStructure/Dictionary/DictionaryExtensions.cs new file mode 100644 index 0000000..ed0aa57 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/DataStructure/Dictionary/DictionaryExtensions.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; +#pragma warning disable CS8601 // Possible null reference assignment. + +namespace Fantasy.DataStructure.Dictionary +{ + /// + /// 提供对字典的扩展方法。 + /// + public static class DictionaryExtensions + { + /// + /// 尝试从字典中移除指定键,并返回相应的值。 + /// + /// 字典中键的类型。 + /// 字典中值的类型。 + /// 要操作的字典实例。 + /// 要移除的键。 + /// 从字典中移除的值(如果成功移除)。 + /// 如果成功移除键值对,则为 true;否则为 false。 + public static bool TryRemove(this IDictionary self, T key, out TV value) + { + if (!self.TryGetValue(key, out value)) + { + return false; + } + + self.Remove(key); + return true; + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/DataStructure/Dictionary/DictionaryPool.cs b/Fantasy/Fantays.Console/Runtime/Core/DataStructure/Dictionary/DictionaryPool.cs new file mode 100644 index 0000000..567901c --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/DataStructure/Dictionary/DictionaryPool.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using Fantasy.Pool; + +namespace Fantasy.DataStructure.Dictionary +{ + /// + /// 提供一个可以使用对象池管理的字典类。 + /// + /// 字典中键的类型。 + /// 字典中值的类型。 + public sealed class DictionaryPool : Dictionary, IDisposable, IPool where TM : notnull + { + private bool _isPool; + private bool _isDispose; + + /// + /// 释放实例占用的资源。 + /// + public void Dispose() + { + if (_isDispose) + { + return; + } + + _isDispose = true; + Clear(); +#if FANTASY_WEBGL + Pool>.Return(this); +#else + MultiThreadPool.Return(this); +#endif + } + + /// + /// 创建一个新的 实例。 + /// + /// 新创建的实例。 + public static DictionaryPool Create() + { +#if FANTASY_WEBGL + var dictionary = Pool>.Rent(); +#else + var dictionary = MultiThreadPool.Rent>(); +#endif + dictionary._isDispose = false; + dictionary._isPool = true; + return dictionary; + } + + /// + /// 获取一个值,该值指示当前实例是否为对象池中的实例。 + /// + /// + public bool IsPool() + { + return _isPool; + } + + /// + /// 设置一个值,该值指示当前实例是否为对象池中的实例。 + /// + /// + public void SetIsPool(bool isPool) + { + _isPool = isPool; + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/DataStructure/Dictionary/DoubleMapDictionaryPool.cs b/Fantasy/Fantays.Console/Runtime/Core/DataStructure/Dictionary/DoubleMapDictionaryPool.cs new file mode 100644 index 0000000..e868710 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/DataStructure/Dictionary/DoubleMapDictionaryPool.cs @@ -0,0 +1,289 @@ +using System; +using System.Collections.Generic; +using Fantasy.Pool; + +#pragma warning disable CS8601 // Possible null reference assignment. +#pragma warning disable CS8604 // Possible null reference argument. +#pragma warning disable CS8603 // Possible null reference return. + +namespace Fantasy.DataStructure.Dictionary +{ + /// + /// 提供一个双向映射字典对象池类,用于双向键值对映射。 + /// + /// 字典中键的类型。 + /// 字典中值的类型。 + public class DoubleMapDictionaryPool : DoubleMapDictionary, IDisposable, IPool where TKey : notnull where TValue : notnull + { + private bool _isPool; + private bool _isDispose; + + /// + /// 创建一个新的 实例。 + /// + /// 新创建的实例。 + public static DoubleMapDictionaryPool Create() + { +#if FANTASY_WEBGL + var a = Pool>.Rent(); +#else + var a = MultiThreadPool.Rent>(); +#endif + a._isDispose = false; + a._isPool = true; + return a; + } + + /// + /// 释放实例占用的资源。 + /// + public void Dispose() + { + if (_isDispose) + { + return; + } + + _isDispose = true; + Clear(); +#if FANTASY_WEBGL + Pool>.Return(this); +#else + MultiThreadPool.Return(this); +#endif + } + + /// + /// 获取一个值,该值指示当前实例是否为对象池中的实例。 + /// + /// + public bool IsPool() + { + return _isPool; + } + + /// + /// 设置一个值,该值指示当前实例是否为对象池中的实例。 + /// + /// + public void SetIsPool(bool isPool) + { + _isPool = isPool; + } + } + + /// + /// 可以实现双向映射的字典类,用于将键和值进行双向映射。 + /// + /// 键的类型,不能为 null。 + /// 值的类型,不能为 null。 + public class DoubleMapDictionary where TK : notnull where TV : notnull + { + private readonly Dictionary _kv = new Dictionary(); + private readonly Dictionary _vk = new Dictionary(); + + /// + /// 创建一个新的空的 实例。 + /// + public DoubleMapDictionary() { } + + /// + /// 创建一个新的具有指定初始容量的 实例。 + /// + /// 初始容量。 + public DoubleMapDictionary(int capacity) + { + _kv = new Dictionary(capacity); + _vk = new Dictionary(capacity); + } + + /// + /// 获取包含字典中所有键的列表。 + /// + public List Keys => new List(_kv.Keys); + + /// + /// 获取包含字典中所有值的列表。 + /// + public List Values => new List(_vk.Keys); + + /// + /// 对字典中的每个键值对执行指定的操作。 + /// + /// 要执行的操作。 + public void ForEach(Action action) + { + if (action == null) + { + return; + } + + var keys = _kv.Keys; + foreach (var key in keys) + { + action(key, _kv[key]); + } + } + + /// + /// 将指定的键值对添加到字典中。 + /// + /// 要添加的键。 + /// 要添加的值。 + public void Add(TK key, TV value) + { + if (key == null || value == null || _kv.ContainsKey(key) || _vk.ContainsKey(value)) + { + return; + } + + _kv.Add(key, value); + _vk.Add(value, key); + } + + /// + /// 根据指定的键获取相应的值。 + /// + /// 要查找值的键。 + /// 与指定键关联的值,如果找不到键,则返回默认值。 + public TV GetValueByKey(TK key) + { + if (key != null && _kv.ContainsKey(key)) + { + return _kv[key]; + } + + return default; + } + + /// + /// 尝试根据指定的键获取相应的值。 + /// + /// 要查找值的键。 + /// 如果找到,则为与指定键关联的值;否则为值的默认值。 + /// 如果找到键,则为 true;否则为 false。 + public bool TryGetValueByKey(TK key, out TV value) + { + var result = key != null && _kv.ContainsKey(key); + + value = result ? _kv[key] : default; + + return result; + } + + /// + /// 根据指定的值获取相应的键。 + /// + /// 要查找键的值。 + /// 与指定值关联的键,如果找不到值,则返回默认键。 + public TK GetKeyByValue(TV value) + { + if (value != null && _vk.ContainsKey(value)) + { + return _vk[value]; + } + + return default; + } + + /// + /// 尝试根据指定的值获取相应的键。 + /// + /// 要查找键的值。 + /// 如果找到,则为与指定值关联的键;否则为键的默认值。 + /// 如果找到值,则为 true;否则为 false。 + public bool TryGetKeyByValue(TV value, out TK key) + { + var result = value != null && _vk.ContainsKey(value); + + key = result ? _vk[value] : default; + + return result; + } + + /// + /// 根据指定的键移除键值对。 + /// + /// 要移除的键。 + public void RemoveByKey(TK key) + { + if (key == null) + { + return; + } + + if (!_kv.TryGetValue(key, out var value)) + { + return; + } + + _kv.Remove(key); + _vk.Remove(value); + } + + /// + /// 根据指定的值移除键值对。 + /// + /// 要移除的值。 + public void RemoveByValue(TV value) + { + if (value == null) + { + return; + } + + if (!_vk.TryGetValue(value, out var key)) + { + return; + } + + _kv.Remove(key); + _vk.Remove(value); + } + + /// + /// 清空字典中的所有键值对。 + /// + public void Clear() + { + _kv.Clear(); + _vk.Clear(); + } + + /// + /// 判断字典是否包含指定的键。 + /// + /// 要检查的键。 + /// 如果字典包含指定的键,则为 true;否则为 false。 + public bool ContainsKey(TK key) + { + return key != null && _kv.ContainsKey(key); + } + + /// + /// 判断字典是否包含指定的值。 + /// + /// 要检查的值。 + /// 如果字典包含指定的值,则为 true;否则为 false。 + public bool ContainsValue(TV value) + { + return value != null && _vk.ContainsKey(value); + } + + /// + /// 判断字典是否包含指定的键值对。 + /// + /// 要检查的键。 + /// 要检查的值。 + /// 如果字典包含指定的键值对,则为 true;否则为 false。 + public bool Contains(TK key, TV value) + { + if (key == null || value == null) + { + return false; + } + + return _kv.ContainsKey(key) && _vk.ContainsKey(value); + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/DataStructure/Dictionary/EntityDictionary.cs b/Fantasy/Fantays.Console/Runtime/Core/DataStructure/Dictionary/EntityDictionary.cs new file mode 100644 index 0000000..fc4d44f --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/DataStructure/Dictionary/EntityDictionary.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; +using Fantasy.Pool; + +namespace Fantasy.DataStructure.Dictionary +{ + /// + /// 提供一个带资源释放功能的实体字典类,支持使用对象池管理。 + /// + /// 字典中键的类型。 + /// 字典中值的类型,必须实现 IDisposable 接口。 + public sealed class EntityDictionary : Dictionary, IDisposable, IPool where TN : IDisposable where TM : notnull + { + private bool _isPool; + private bool _isDispose; + + /// + /// 创建一个新的 实例。 + /// + /// 新创建的实例。 + public static EntityDictionary Create() + { +#if FANTASY_WEBGL + var entityDictionary = Pool>.Rent(); +#else + var entityDictionary = MultiThreadPool.Rent>(); +#endif + entityDictionary._isDispose = false; + entityDictionary._isPool = true; + return entityDictionary; + } + + /// + /// 清空字典中的所有键值对,并释放值的资源。 + /// + public new void Clear() + { + foreach (var keyValuePair in this) + { + keyValuePair.Value.Dispose(); + } + + base.Clear(); + } + + /// + /// 清空字典中的所有键值对,但不释放值的资源。 + /// + public void ClearNotDispose() + { + base.Clear(); + } + + /// + /// 释放实例占用的资源。 + /// + public void Dispose() + { + if (_isDispose) + { + return; + } + + _isDispose = true; + Clear(); +#if FANTASY_WEBGL + Pool>.Return(this); +#else + MultiThreadPool.Return(this); +#endif + } + + /// + /// 获取一个值,该值指示当前实例是否为对象池中的实例。 + /// + /// + public bool IsPool() + { + return _isPool; + } + + /// + /// 设置一个值,该值指示当前实例是否为对象池中的实例。 + /// + /// + public void SetIsPool(bool isPool) + { + _isPool = isPool; + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/DataStructure/Dictionary/OneToManyDictionaryPool.cs b/Fantasy/Fantays.Console/Runtime/Core/DataStructure/Dictionary/OneToManyDictionaryPool.cs new file mode 100644 index 0000000..0515423 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/DataStructure/Dictionary/OneToManyDictionaryPool.cs @@ -0,0 +1,247 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Fantasy.Pool; + +#pragma warning disable CS8603 +#pragma warning disable CS8601 + +namespace Fantasy.DataStructure.Dictionary +{ + /// + /// 一对多映射关系的字典对象池。 + /// + /// 外部字典中的键类型。 + /// 内部字典中的键类型。 + /// 内部字典中的值类型。 + public class OneToManyDictionaryPool : OneToManyDictionary, IDisposable, IPool where TKey : notnull where TValueKey : notnull + { + private bool _isPool; + private bool _isDispose; + + /// + /// 创建一个 的实例。 + /// + /// 新创建的 OneToManyDictionaryPool 实例。 + public static OneToManyDictionaryPool Create() + { +#if FANTASY_WEBGL + var a = Pool>.Rent(); +#else + var a = MultiThreadPool.Rent>(); +#endif + a._isDispose = false; + a._isPool = true; + return a; + } + + /// + /// 释放当前实例及其资源。 + /// + public void Dispose() + { + if (_isDispose) + { + return; + } + + _isDispose = true; + Clear(); +#if FANTASY_WEBGL + Pool>.Return(this); +#else + MultiThreadPool.Return(this); +#endif + } + + /// + /// 获取一个值,该值指示当前实例是否为对象池中的实例。 + /// + /// + public bool IsPool() + { + return _isPool; + } + + /// + /// 设置一个值,该值指示当前实例是否为对象池中的实例。 + /// + /// + public void SetIsPool(bool isPool) + { + _isPool = isPool; + } + } + + /// + /// 一对多映射关系的字典。每个键都对应一个内部字典,该内部字典将键值映射到相应的值。 + /// + /// 外部字典中的键类型。 + /// 内部字典中的键类型。 + /// 内部字典中的值类型。 + public class OneToManyDictionary : Dictionary> + where TKey : notnull where TValueKey : notnull + { + private readonly Queue> _queue = new Queue>(); + private readonly int _recyclingLimit = 120; + + /// + /// 创建一个新的 实例。 + /// + public OneToManyDictionary() { } + + /// + /// 创建一个新的 实例,并指定最大缓存数量。 + /// + /// + /// 1:防止数据量过大、所以超过recyclingLimit的数据还是走GC. + /// 2:设置成0不控制数量,全部缓存 + /// + public OneToManyDictionary(int recyclingLimit = 0) + { + _recyclingLimit = recyclingLimit; + } + + /// + /// 检查是否包含指定的键值对。 + /// + /// 外部字典中的键。 + /// 内部字典中的键。 + /// 如果包含指定的键值对,则为 true;否则为 false。 + public bool Contains(TKey key, TValueKey valueKey) + { + TryGetValue(key, out var dic); + + return dic != null && dic.ContainsKey(valueKey); + } + + /// + /// 尝试获取指定键值对的值。 + /// + /// 外部字典中的键。 + /// 内部字典中的键。 + /// 获取的值,如果操作成功,则为值;否则为默认值。 + /// 如果操作成功,则为 true;否则为 false。 + public bool TryGetValue(TKey key, TValueKey valueKey, out TValue value) + { + value = default; + return TryGetValue(key, out var dic) && dic.TryGetValue(valueKey, out value); + } + + /// + /// 获取指定键的第一个值。 + /// + /// 要获取第一个值的键。 + public TValue First(TKey key) + { + return !TryGetValue(key, out var dic) ? default : dic.First().Value; + } + + /// + /// 向字典中添加指定的键值对。 + /// + /// 要添加键值对的键。 + /// 要添加键值对的内部字典键。 + /// 要添加的值。 + public void Add(TKey key, TValueKey valueKey, TValue value) + { + if (!TryGetValue(key, out var dic)) + { + dic = Fetch(); + dic[valueKey] = value; + // dic.Add(valueKey, value); + Add(key, dic); + + return; + } + + dic[valueKey] = value; + // dic.Add(valueKey, value); + } + + /// + /// 从字典中移除指定的键值对。 + /// + /// 要移除键值对的键。 + /// 要移除键值对的内部字典键。 + /// 如果成功移除键值对,则为 true;否则为 false。 + public bool Remove(TKey key, TValueKey valueKey) + { + if (!TryGetValue(key, out var dic)) return false; + + var result = dic.Remove(valueKey); + + if (dic.Count == 0) RemoveKey(key); + + return result; + } + + /// + /// 从字典中移除指定的键值对。 + /// + /// 要移除键值对的键。 + /// 要移除键值对的内部字典键。 + /// 如果成功移除键值对,则为移除的值;否则为默认值。 + /// 如果成功移除键值对,则为 true;否则为 false。 + public bool Remove(TKey key, TValueKey valueKey, out TValue value) + { + if (!TryGetValue(key, out var dic)) + { + value = default; + return false; + } + + var result = dic.TryGetValue(valueKey, out value); + + if (result) dic.Remove(valueKey); + + if (dic.Count == 0) RemoveKey(key); + + return result; + } + + /// + /// 移除字典中的指定键及其相关的所有键值对。 + /// + /// 要移除的键。 + public void RemoveKey(TKey key) + { + if (!TryGetValue(key, out var dic)) return; + + Remove(key); + Recycle(dic); + } + + /// + /// 从对象池中获取一个内部字典实例,如果池中没有,则创建一个新实例。 + /// + /// 获取的内部字典实例。 + private Dictionary Fetch() + { + return _queue.Count <= 0 ? new Dictionary() : _queue.Dequeue(); + } + + /// + /// 将不再使用的内部字典实例放回对象池中,以便后续重用。 + /// + /// 要放回对象池的内部字典实例。 + private void Recycle(Dictionary dic) + { + dic.Clear(); + + if (_recyclingLimit != 0 && _queue.Count > _recyclingLimit) return; + + _queue.Enqueue(dic); + } + + /// + /// 清空字典中的所有键值对,并将不再使用的内部字典实例放回对象池中。 + /// + public new void Clear() + { + foreach (var keyValuePair in this) Recycle(keyValuePair.Value); + + base.Clear(); + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/DataStructure/Dictionary/OneToManySortedDictionaryPool.cs b/Fantasy/Fantays.Console/Runtime/Core/DataStructure/Dictionary/OneToManySortedDictionaryPool.cs new file mode 100644 index 0000000..127a8ac --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/DataStructure/Dictionary/OneToManySortedDictionaryPool.cs @@ -0,0 +1,250 @@ +using System; +using System.Collections.Generic; +using Fantasy.Pool; + +#pragma warning disable CS8601 + +namespace Fantasy.DataStructure.Dictionary +{ + /// + /// 一对多映射关系的排序字典对象池。 + /// + /// 外部字典中的键类型。 + /// 内部字典中的排序键类型。 + /// 内部字典中的值类型。 + public class OneToManySortedDictionaryPool : OneToManySortedDictionary, IDisposable, IPool where TKey : notnull where TSortedKey : notnull + { + private bool _isPool; + private bool _isDispose; + + /// + /// 创建一个 的实例。 + /// + /// 新创建的 OneToManySortedDictionaryPool 实例。 + public static OneToManySortedDictionaryPool Create() + { +#if FANTASY_WEBGL + var a = Pool>.Rent(); +#else + var a = MultiThreadPool.Rent>(); +#endif + a._isDispose = false; + a._isPool = true; + return a; + } + + /// + /// 释放当前实例及其资源。 + /// + public void Dispose() + { + if (_isDispose) + { + return; + } + + _isDispose = true; + Clear(); +#if FANTASY_WEBGL + Pool>.Return(this); +#else + MultiThreadPool.Return(this); +#endif + } + + /// + /// 获取一个值,该值指示当前实例是否为对象池中的实例。 + /// + /// + public bool IsPool() + { + return _isPool; + } + + /// + /// 设置一个值,该值指示当前实例是否为对象池中的实例。 + /// + /// + public void SetIsPool(bool isPool) + { + _isPool = isPool; + } + } + + /// + /// 一对多映射关系的排序字典。每个外部键映射到一个内部排序字典,该内部排序字典将排序键映射到相应的值。 + /// + /// 外部字典中的键类型。 + /// 内部字典中的排序键类型。 + /// 内部字典中的值类型。 + public class + OneToManySortedDictionary : Dictionary> + where TSortedKey : notnull where TKey : notnull + { + /// 缓存队列的回收限制 + private readonly int _recyclingLimit = 120; + /// 缓存队列,用于存储已回收的内部排序字典 + private readonly Queue> _queue = new Queue>(); + + /// + /// 创建一个新的 实例。 + /// + protected OneToManySortedDictionary() { } + + /// + /// 创建一个新的 实例。设置最大缓存数量 + /// + /// + /// 1:防止数据量过大、所以超过recyclingLimit的数据还是走GC. + /// 2:设置成0不控制数量,全部缓存 + /// + public OneToManySortedDictionary(int recyclingLimit) + { + _recyclingLimit = recyclingLimit; + } + + /// + /// 检查字典是否包含指定的外部键。 + /// + /// 要检查的外部键。 + /// 如果字典包含指定的外部键,则为 true;否则为 false。 + public bool Contains(TKey key) + { + return this.ContainsKey(key); + } + + /// + /// 检查字典是否包含指定的外部键和排序键。 + /// + /// 要检查的外部键。 + /// 要检查的排序键。 + /// 如果字典包含指定的外部键和排序键,则为 true;否则为 false。 + public bool Contains(TKey key, TSortedKey sortedKey) + { + return TryGetValue(key, out var dic) && dic.ContainsKey(sortedKey); + } + + /// + /// 尝试从字典中获取指定外部键对应的内部排序字典。 + /// + /// 要获取内部排序字典的外部键。 + /// 获取到的内部排序字典,如果找不到则为 null。 + /// 如果找到内部排序字典,则为 true;否则为 false。 + public new bool TryGetValue(TKey key, out SortedDictionary dic) + { + return base.TryGetValue(key, out dic); + } + + /// + /// 尝试从字典中获取指定外部键和排序键对应的值。 + /// + /// 要获取值的外部键。 + /// 要获取值的排序键。 + /// 获取到的值,如果找不到则为 default。 + /// 如果找到值,则为 true;否则为 false。 + public bool TryGetValueBySortedKey(TKey key, TSortedKey sortedKey, out TValue value) + { + if (base.TryGetValue(key, out var dic)) + { + return dic.TryGetValue(sortedKey, out value); + } + + value = default; + return false; + } + + /// + /// 向字典中添加一个值,关联到指定的外部键和排序键。 + /// + /// 要关联值的外部键。 + /// 要关联值的排序键。 + /// 要添加的值。 + public void Add(TKey key, TSortedKey sortedKey, TValue value) + { + if (!TryGetValue(key, out var dic)) + { + dic = Fetch(); + dic.Add(sortedKey, value); + Add(key, dic); + + return; + } + + dic.Add(sortedKey, value); + } + + /// + /// 从字典中移除指定外部键和排序键关联的值。 + /// + /// 要移除值的外部键。 + /// 要移除值的排序键。 + /// 如果成功移除值,则为 true;否则为 false。 + public bool RemoveSortedKey(TKey key, TSortedKey sortedKey) + { + if (!TryGetValue(key, out var dic)) + { + return false; + } + + var isRemove = dic.Remove(sortedKey); + + if (dic.Count == 0) + { + isRemove = RemoveKey(key); + } + + return isRemove; + } + + /// + /// 从字典中移除指定外部键及其关联的所有值。 + /// + /// 要移除的外部键。 + /// 如果成功移除外部键及其关联的所有值,则为 true;否则为 false。 + public bool RemoveKey(TKey key) + { + if (!TryGetValue(key, out var list)) + { + return false; + } + + Remove(key); + Recycle(list); + return true; + } + + /// + /// 从缓存队列中获取一个内部排序字典。 + /// + /// 一个内部排序字典。 + private SortedDictionary Fetch() + { + return _queue.Count <= 0 ? new SortedDictionary() : _queue.Dequeue(); + } + + /// + /// 回收一个内部排序字典到缓存队列。 + /// + /// 要回收的内部排序字典。 + private void Recycle(SortedDictionary dic) + { + dic.Clear(); + + if (_recyclingLimit != 0 && _queue.Count > _recyclingLimit) + { + return; + } + + _queue.Enqueue(dic); + } + + /// + /// 清空字典以及内部排序字典缓存队列,释放所有资源。 + /// + protected new void Clear() + { + base.Clear(); + _queue.Clear(); + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/DataStructure/Dictionary/ReuseDictionary.cs b/Fantasy/Fantays.Console/Runtime/Core/DataStructure/Dictionary/ReuseDictionary.cs new file mode 100644 index 0000000..ebe12e9 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/DataStructure/Dictionary/ReuseDictionary.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using Fantasy.Pool; + +namespace Fantasy.DataStructure.Dictionary +{ + /// + /// 提供一个可以重用的字典类,支持使用对象池管理。 + /// + /// 字典中键的类型。 + /// 字典中值的类型。 + public sealed class ReuseDictionary : Dictionary, IDisposable, IPool where TM : notnull + { + private bool _isPool; + private bool _isDispose; + + /// + /// 创建一个新的 实例。 + /// + /// 新创建的实例。 + public static ReuseDictionary Create() + { +#if FANTASY_WEBGL + var entityDictionary = Pool>.Rent(); +#else + var entityDictionary = MultiThreadPool.Rent>(); +#endif + entityDictionary._isDispose = false; + entityDictionary._isPool = true; + return entityDictionary; + } + + /// + /// 释放实例占用的资源。 + /// + public void Dispose() + { + if (_isDispose) + { + return; + } + + _isDispose = true; + Clear(); +#if FANTASY_WEBGL + Pool>.Return(this); +#else + MultiThreadPool.Return(this); +#endif + } + + /// + /// 获取一个值,该值指示当前实例是否为对象池中的实例。 + /// + /// + public bool IsPool() + { + return _isPool; + } + + /// + /// 设置一个值,该值指示当前实例是否为对象池中的实例。 + /// + /// + public void SetIsPool(bool isPool) + { + _isPool = isPool; + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/DataStructure/Dictionary/SortedDictionaryPool.cs b/Fantasy/Fantays.Console/Runtime/Core/DataStructure/Dictionary/SortedDictionaryPool.cs new file mode 100644 index 0000000..8db66a8 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/DataStructure/Dictionary/SortedDictionaryPool.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using Fantasy.Pool; + +namespace Fantasy.DataStructure.Dictionary +{ + /// + /// 提供一个可以使用对象池管理的排序字典类。 + /// + /// + /// + public sealed class SortedDictionaryPool : SortedDictionary, IDisposable, IPool where TM : notnull + { + private bool _isPool; + private bool _isDispose; + + /// + /// 释放实例占用的资源。 + /// + public void Dispose() + { + if (_isDispose) + { + return; + } + + _isDispose = true; + Clear(); +#if FANTASY_WEBGL + Pool>.Return(this); +#else + MultiThreadPool.Return(this); +#endif + } + + /// + /// 创建一个新的 实例。 + /// + /// 新创建的实例。 + public static SortedDictionaryPool Create() + { +#if FANTASY_WEBGL + var dictionary = Pool>.Rent(); +#else + var dictionary = MultiThreadPool.Rent>(); +#endif + dictionary._isDispose = false; + dictionary._isPool = true; + return dictionary; + } + + /// + /// 获取一个值,该值指示当前实例是否为对象池中的实例。 + /// + /// + public bool IsPool() + { + return _isPool; + } + + /// + /// 设置一个值,该值指示当前实例是否为对象池中的实例。 + /// + /// + public void SetIsPool(bool isPool) + { + _isPool = isPool; + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/DataStructure/PriorityQueue/PriorityQueueGenerics.cs b/Fantasy/Fantays.Console/Runtime/Core/DataStructure/PriorityQueue/PriorityQueueGenerics.cs new file mode 100644 index 0000000..2ef6704 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/DataStructure/PriorityQueue/PriorityQueueGenerics.cs @@ -0,0 +1,121 @@ +// ReSharper disable SwapViaDeconstruction +// ReSharper disable UseIndexFromEndExpression +// ReSharper disable ConvertToPrimaryConstructor +using System; +using System.Collections.Generic; +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + +#pragma warning disable CS8601 // Possible null reference assignment. +namespace Fantasy.DataStructure.PriorityQueue +{ + /// + /// 优先队列 + /// + /// 节点数据 + /// 排序的类型、 + public sealed class PriorityQueue where TPriority : IComparable + { + private readonly List> _heap; + + public PriorityQueue(int initialCapacity = 16) + { + _heap = new List>(initialCapacity); + } + + public int Count => _heap.Count; + + public void Enqueue(TElement element, TPriority priority) + { + _heap.Add(new PriorityQueueItem(element, priority)); + HeapifyUp(_heap.Count - 1); + } + + public TElement Dequeue() + { + if (_heap.Count == 0) + { + throw new InvalidOperationException("The queue is empty."); + } + + var item = _heap[0]; + _heap[0] = _heap[_heap.Count - 1]; + _heap.RemoveAt(_heap.Count - 1); + HeapifyDown(0); + return item.Element; + } + + public bool TryDequeue(out TElement element) + { + if (_heap.Count == 0) + { + element = default(TElement); + return false; + } + + element = Dequeue(); + return true; + } + + public TElement Peek() + { + if (_heap.Count == 0) + { + throw new InvalidOperationException("The queue is empty."); + } + return _heap[0].Element; + } + + // ReSharper disable once IdentifierTypo + private void HeapifyUp(int index) + { + while (index > 0) + { + var parentIndex = (index - 1) / 2; + if (_heap[index].Priority.CompareTo(_heap[parentIndex].Priority) >= 0) + { + break; + } + Swap(index, parentIndex); + index = parentIndex; + } + } + + // ReSharper disable once IdentifierTypo + private void HeapifyDown(int index) + { + var lastIndex = _heap.Count - 1; + while (true) + { + var smallestIndex = index; + var leftChildIndex = 2 * index + 1; + var rightChildIndex = 2 * index + 2; + + if (leftChildIndex <= lastIndex && _heap[leftChildIndex].Priority.CompareTo(_heap[smallestIndex].Priority) < 0) + { + smallestIndex = leftChildIndex; + } + + if (rightChildIndex <= lastIndex && _heap[rightChildIndex].Priority.CompareTo(_heap[smallestIndex].Priority) < 0) + { + smallestIndex = rightChildIndex; + } + + if (smallestIndex == index) + { + break; + } + + Swap(index, smallestIndex); + index = smallestIndex; + } + } + + private void Swap(int index1, int index2) + { + var temp = _heap[index1]; + _heap[index1] = _heap[index2]; + _heap[index2] = temp; + } + } +} + diff --git a/Fantasy/Fantays.Console/Runtime/Core/DataStructure/PriorityQueue/PriorityQueueItem.cs b/Fantasy/Fantays.Console/Runtime/Core/DataStructure/PriorityQueue/PriorityQueueItem.cs new file mode 100644 index 0000000..5b020b2 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/DataStructure/PriorityQueue/PriorityQueueItem.cs @@ -0,0 +1,30 @@ +// ReSharper disable ConvertToPrimaryConstructor +// ReSharper disable SwapViaDeconstruction +// ReSharper disable InconsistentNaming +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +namespace Fantasy.DataStructure.PriorityQueue +{ + public struct PriorityQueueItemUint + { + public T Element { get; set; } + public uint Priority { get; set; } + + public PriorityQueueItemUint(T element, uint priority) + { + Element = element; + Priority = priority; + } + } + + public struct PriorityQueueItem + { + public T Element { get; } + public T1 Priority { get; } + + public PriorityQueueItem(T element, T1 priority) + { + Element = element; + Priority = priority; + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/DataStructure/PriorityQueue/PriorityQueueSimple.cs b/Fantasy/Fantays.Console/Runtime/Core/DataStructure/PriorityQueue/PriorityQueueSimple.cs new file mode 100644 index 0000000..63a4418 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/DataStructure/PriorityQueue/PriorityQueueSimple.cs @@ -0,0 +1,116 @@ +// ReSharper disable SwapViaDeconstruction +// ReSharper disable UseIndexFromEndExpression +// ReSharper disable ConvertToPrimaryConstructor +using System; +using System.Collections.Generic; +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +#pragma warning disable CS8601 // Possible null reference assignment. +namespace Fantasy.DataStructure.PriorityQueue +{ + public sealed class PriorityQueue where T : IComparable + { + private readonly List _heap; + + public PriorityQueue(int initialCapacity = 16) + { + _heap = new List(initialCapacity); + } + + public int Count => _heap.Count; + + public void Enqueue(T item) + { + _heap.Add(item); + HeapifyUp(_heap.Count - 1); + } + + public T Dequeue() + { + if (_heap.Count == 0) + { + throw new InvalidOperationException("The queue is empty."); + } + + var item = _heap[0]; + var heapCount = _heap.Count - 1; + _heap[0] = _heap[heapCount]; + _heap.RemoveAt(heapCount); + HeapifyDown(0); + return item; + } + + public bool TryDequeue(out T item) + { + if (_heap.Count == 0) + { + item = default(T); + return false; + } + + item = Dequeue(); + return true; + } + + public T Peek() + { + if (_heap.Count == 0) + { + throw new InvalidOperationException("The queue is empty."); + } + return _heap[0]; + } + + // ReSharper disable once IdentifierTypo + private void HeapifyUp(int index) + { + while (index > 0) + { + var parentIndex = (index - 1) / 2; + if (_heap[index].CompareTo(_heap[parentIndex]) >= 0) + { + break; + } + Swap(index, parentIndex); + index = parentIndex; + } + } + + // ReSharper disable once IdentifierTypo + private void HeapifyDown(int index) + { + var lastIndex = _heap.Count - 1; + while (true) + { + var smallestIndex = index; + var leftChildIndex = 2 * index + 1; + var rightChildIndex = 2 * index + 2; + + if (leftChildIndex <= lastIndex && _heap[leftChildIndex].CompareTo(_heap[smallestIndex]) < 0) + { + smallestIndex = leftChildIndex; + } + + if (rightChildIndex <= lastIndex && _heap[rightChildIndex].CompareTo(_heap[smallestIndex]) < 0) + { + smallestIndex = rightChildIndex; + } + + if (smallestIndex == index) + { + break; + } + + Swap(index, smallestIndex); + index = smallestIndex; + } + } + + private void Swap(int index1, int index2) + { + var temp = _heap[index1]; + _heap[index1] = _heap[index2]; + _heap[index2] = temp; + } + } +} + diff --git a/Fantasy/Fantays.Console/Runtime/Core/DataStructure/SkipTable/SkipTable.cs b/Fantasy/Fantays.Console/Runtime/Core/DataStructure/SkipTable/SkipTable.cs new file mode 100644 index 0000000..acbc2c1 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/DataStructure/SkipTable/SkipTable.cs @@ -0,0 +1,190 @@ + +#pragma warning disable CS8602 // Dereference of a possibly null reference. +#pragma warning disable CS8601 // Possible null reference assignment. +#pragma warning disable CS8604 // Possible null reference argument. +#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. +#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type. +namespace Fantasy.DataStructure.SkipTable +{ + /// + /// 跳表数据结构(升序版) + /// + /// 跳表中存储的值的类型。 + public class SkipTable : SkipTableBase + { + /// + /// 创建一个新的跳表实例。 + /// + /// 跳表的最大层数。 + public SkipTable(int maxLayer = 8) : base(maxLayer) { } + + /// + /// 向跳表中添加一个新节点。 + /// + /// 节点的主排序键。 + /// 节点的副排序键。 + /// 节点的唯一键。 + /// 要添加的值。 + public override void Add(long sortKey, long viceKey, long key, TValue value) + { + var rLevel = 1; + + while (rLevel <= MaxLayer && Random.Next(3) == 0) + { + ++rLevel; + } + + SkipTableNode cur = TopHeader, last = null; + + for (var layer = MaxLayer; layer >= 1; --layer) + { + // 节点有next节点,且 (next主键 < 插入主键) 或 (next主键 == 插入主键 且 next副键 < 插入副键) + while (cur.Right != null && ((cur.Right.SortKey < sortKey) || + (cur.Right.SortKey == sortKey && cur.Right.ViceKey < viceKey))) + { + cur = cur.Right; + } + + if (layer <= rLevel) + { + var currentRight = cur.Right; + + // 在当前层插入新节点 + cur.Right = new SkipTableNode(sortKey, viceKey, key, value, layer == 1 ? cur.Index + 1 : 0, cur, cur.Right, null); + + if (currentRight != null) + { + currentRight.Left = cur.Right; + } + + if (last != null) + { + last.Down = cur.Right; + } + + if (layer == 1) + { + // 更新索引信息 + cur.Right.Index = cur.Index + 1; + Node.Add(key, cur.Right); + + SkipTableNode v = cur.Right.Right; + + while (v != null) + { + v.Index++; + v = v.Right; + } + } + + last = cur.Right; + } + + cur = cur.Down; + } + } + + /// + /// 从跳表中移除一个节点。 + /// + /// 节点的主排序键。 + /// 节点的副排序键。 + /// 节点的唯一键。 + /// 被移除的节点的值。 + /// 如果成功移除节点,则为 true;否则为 false。 + public override bool Remove(long sortKey, long viceKey, long key, out TValue value) + { + value = default; + var seen = false; + var cur = TopHeader; + + for (var layer = MaxLayer; layer >= 1; --layer) + { + // 先按照主键查找 再 按副键查找 + while (cur.Right != null && cur.Right.SortKey < sortKey && cur.Right.Key != key) cur = cur.Right; + while (cur.Right != null && (cur.Right.SortKey == sortKey && cur.Right.ViceKey <= viceKey) && + cur.Right.Key != key) cur = cur.Right; + + var isFind = false; + var currentCur = cur; + SkipTableNode removeCur = null; + // 如果当前不是要删除的节点、但主键和副键都一样、需要特殊处理下。 + if (cur.Right != null && cur.Right.Key == key) + { + isFind = true; + removeCur = cur.Right; + currentCur = cur; + } + else + { + // 先向左查找下 + var currentNode = cur.Left; + while (currentNode != null && currentNode.SortKey == sortKey && currentNode.ViceKey == viceKey) + { + if (currentNode.Key == key) + { + isFind = true; + removeCur = currentNode; + currentCur = currentNode.Left; + break; + } + + currentNode = currentNode.Left; + } + + // 再向右查找下 + if (!isFind) + { + currentNode = cur.Right; + while (currentNode != null && currentNode.SortKey == sortKey && currentNode.ViceKey == viceKey) + { + if (currentNode.Key == key) + { + isFind = true; + removeCur = currentNode; + currentCur = currentNode.Left; + break; + } + + currentNode = currentNode.Right; + } + } + } + + if (isFind && currentCur != null) + { + value = removeCur.Value; + currentCur.Right = removeCur.Right; + + if (removeCur.Right != null) + { + removeCur.Right.Left = currentCur; + removeCur.Right = null; + } + + removeCur.Left = null; + removeCur.Down = null; + removeCur.Value = default; + + if (layer == 1) + { + var tempCur = currentCur.Right; + while (tempCur != null) + { + tempCur.Index--; + tempCur = tempCur.Right; + } + + Node.Remove(removeCur.Key); + } + + seen = true; + } + + cur = cur.Down; + } + + return seen; + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/DataStructure/SkipTable/SkipTableBase.cs b/Fantasy/Fantays.Console/Runtime/Core/DataStructure/SkipTable/SkipTableBase.cs new file mode 100644 index 0000000..82783e8 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/DataStructure/SkipTable/SkipTableBase.cs @@ -0,0 +1,282 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using Fantasy.DataStructure.Collection; + +#pragma warning disable CS8601 +#pragma warning disable CS8603 +#pragma warning disable CS8625 +#pragma warning disable CS8604 + +namespace Fantasy.DataStructure.SkipTable +{ + /// + /// 抽象的跳表基类,提供跳表的基本功能和操作。 + /// + /// 跳表中存储的值的类型。 + public abstract class SkipTableBase : IEnumerable> + { + /// + /// 跳表的最大层数 + /// + public readonly int MaxLayer; + /// + /// 跳表的顶部头节点 + /// + public readonly SkipTableNode TopHeader; + /// + /// 跳表的底部头节点 + /// + public SkipTableNode BottomHeader; + /// + /// 跳表中节点的数量,使用了 Node 字典的计数 + /// + public int Count => Node.Count; + /// + /// 用于生成随机数的随机数生成器 + /// + protected readonly Random Random = new Random(); + /// + /// 存储跳表节点的字典 + /// + protected readonly Dictionary> Node = new(); + /// + /// 用于辅助反向查找的栈 + /// + protected readonly Stack> AntiFindStack = new Stack>(); + + /// + /// 初始化一个新的跳表实例。 + /// + /// 跳表的最大层数,默认为 8。 + protected SkipTableBase(int maxLayer = 8) + { + MaxLayer = maxLayer; + var cur = TopHeader = new SkipTableNode(long.MinValue, 0, 0, default, 0, null, null, null); + + for (var layer = MaxLayer - 1; layer >= 1; --layer) + { + cur.Down = new SkipTableNode(long.MinValue, 0, 0, default, 0, null, null, null); + cur = cur.Down; + } + + BottomHeader = cur; + } + + /// + /// 获取指定键的节点的值,若不存在则返回默认值。 + /// + /// 要查找的键。 + public TValue this[long key] => !TryGetValueByKey(key, out TValue value) ? default : value; + + /// + /// 获取指定键的节点在跳表中的排名。 + /// + /// 要查找的键。 + /// 节点的排名。 + public int GetRanking(long key) + { + if (!Node.TryGetValue(key, out var node)) + { + return 0; + } + + return node.Index; + } + + /// + /// 获取指定键的反向排名,即在比该键更大的节点中的排名。 + /// + /// 要查找的键。 + /// 反向排名。 + public int GetAntiRanking(long key) + { + var ranking = GetRanking(key); + + if (ranking == 0) + { + return 0; + } + + return Count + 1 - ranking; + } + + /// + /// 尝试通过键获取节点的值。 + /// + /// 要查找的键。 + /// 获取到的节点的值,如果键不存在则为默认值。 + /// 是否成功获取节点的值。 + public bool TryGetValueByKey(long key, out TValue value) + { + if (!Node.TryGetValue(key, out var node)) + { + value = default; + return false; + } + + value = node.Value; + return true; + } + + /// + /// 尝试通过键获取节点。 + /// + /// 要查找的键。 + /// 获取到的节点,如果键不存在则为 null。 + /// 是否成功获取节点。 + public bool TryGetNodeByKey(long key, out SkipTableNode node) + { + if (Node.TryGetValue(key, out node)) + { + return true; + } + + return false; + } + + /// + /// 在跳表中查找节点,返回从起始位置到结束位置的节点列表。 + /// + /// 起始位置的排名。 + /// 结束位置的排名。 + /// 用于存储节点列表的 实例。 + public void Find(int start, int end, ListPool> list) + { + var cur = BottomHeader; + var count = end - start; + + for (var i = 0; i < start; i++) + { + cur = cur.Right; + } + + for (var i = 0; i <= count; i++) + { + if (cur == null) + { + break; + } + + list.Add(cur); + cur = cur.Right; + } + } + + /// + /// 在跳表中进行反向查找节点,返回从结束位置到起始位置的节点列表。 + /// + /// 结束位置的排名。 + /// 起始位置的排名。 + /// 用于存储节点列表的 实例。 + public void AntiFind(int start, int end, ListPool> list) + { + var cur = BottomHeader; + start = Count + 1 - start; + end = start - end; + + for (var i = 0; i < start; i++) + { + cur = cur.Right; + + if (cur == null) + { + break; + } + + if (i < end) + { + continue; + } + + AntiFindStack.Push(cur); + } + + while (AntiFindStack.TryPop(out var node)) + { + list.Add(node); + } + } + + /// + /// 获取跳表中最后一个节点的值。 + /// + /// 最后一个节点的值。 + public TValue GetLastValue() + { + var cur = TopHeader; + + while (cur.Right != null || cur.Down != null) + { + while (cur.Right != null) + { + cur = cur.Right; + } + + if (cur.Down != null) + { + cur = cur.Down; + } + } + + return cur.Value; + } + + /// + /// 移除跳表中指定键的节点。 + /// + /// 要移除的节点的键。 + /// 移除是否成功。 + public bool Remove(long key) + { + if (!Node.TryGetValue(key, out var node)) + { + return false; + } + + return Remove(node.SortKey, node.ViceKey, key, out _); + } + + /// + /// 向跳表中添加节点。 + /// + /// 节点的排序键。 + /// 节点的副键。 + /// 节点的键。 + /// 节点的值。 + public abstract void Add(long sortKey, long viceKey, long key, TValue value); + + /// + /// 从跳表中移除指定键的节点。 + /// + /// 节点的排序键。 + /// 节点的副键。 + /// 节点的键。 + /// 被移除的节点的值。 + /// 移除是否成功。 + public abstract bool Remove(long sortKey, long viceKey, long key, out TValue value); + + /// + /// 返回一个枚举器,用于遍历跳表中的节点。 + /// + /// 一个可用于遍历跳表节点的枚举器。 + public IEnumerator> GetEnumerator() + { + var cur = BottomHeader.Right; + while (cur != null) + { + yield return cur; + cur = cur.Right; + } + } + + /// + /// 返回一个非泛型枚举器,用于遍历跳表中的节点。 + /// + /// 一个非泛型枚举器,可用于遍历跳表节点。 + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/DataStructure/SkipTable/SkipTableDesc.cs b/Fantasy/Fantays.Console/Runtime/Core/DataStructure/SkipTable/SkipTableDesc.cs new file mode 100644 index 0000000..63daa16 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/DataStructure/SkipTable/SkipTableDesc.cs @@ -0,0 +1,188 @@ + +#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type. +#pragma warning disable CS8604 // Possible null reference argument. +#pragma warning disable CS8602 // Dereference of a possibly null reference. +#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. +#pragma warning disable CS8601 // Possible null reference assignment. +namespace Fantasy.DataStructure.SkipTable +{ + /// + /// 跳表降序版,用于存储降序排列的数据。 + /// + /// 存储的值的类型。 + public class SkipTableDesc : SkipTableBase + { + /// + /// 初始化跳表降序版的新实例。 + /// + /// 跳表的最大层数,默认为 8。 + public SkipTableDesc(int maxLayer = 8) : base(maxLayer) { } + + /// + /// 向跳表中添加一个节点,根据降序规则进行插入。 + /// + /// 排序主键。 + /// 副键。 + /// 键。 + /// 值。 + public override void Add(long sortKey, long viceKey, long key, TValue value) + { + var rLevel = 1; + + while (rLevel <= MaxLayer && Random.Next(3) == 0) + { + ++rLevel; + } + + SkipTableNode cur = TopHeader, last = null; + + for (var layer = MaxLayer; layer >= 1; --layer) + { + // 节点有next节点,且 (next主键 > 插入主键) 或 (next主键 == 插入主键 且 next副键 > 插入副键) + while (cur.Right != null && ((cur.Right.SortKey > sortKey) || + (cur.Right.SortKey == sortKey && cur.Right.ViceKey > viceKey))) + { + cur = cur.Right; + } + + if (layer <= rLevel) + { + var currentRight = cur.Right; + cur.Right = new SkipTableNode(sortKey, viceKey, key, value, + layer == 1 ? cur.Index + 1 : 0, cur, cur.Right, null); + + if (currentRight != null) + { + currentRight.Left = cur.Right; + } + + if (last != null) + { + last.Down = cur.Right; + } + + if (layer == 1) + { + cur.Right.Index = cur.Index + 1; + Node.Add(key, cur.Right); + + SkipTableNode v = cur.Right.Right; + + while (v != null) + { + v.Index++; + v = v.Right; + } + } + + last = cur.Right; + } + + cur = cur.Down; + } + } + + /// + /// 从跳表中移除一个节点,根据降序规则进行移除。 + /// + /// 排序主键。 + /// 副键。 + /// 键。 + /// 移除的节点值。 + /// 如果成功移除节点,则返回 true,否则返回 false。 + public override bool Remove(long sortKey, long viceKey, long key, out TValue value) + { + value = default; + var seen = false; + var cur = TopHeader; + + for (var layer = MaxLayer; layer >= 1; --layer) + { + // 先按照主键查找 再 按副键查找 + while (cur.Right != null && cur.Right.SortKey > sortKey && cur.Right.Key != key) cur = cur.Right; + while (cur.Right != null && (cur.Right.SortKey == sortKey && cur.Right.ViceKey >= viceKey) && + cur.Right.Key != key) cur = cur.Right; + + var isFind = false; + var currentCur = cur; + SkipTableNode removeCur = null; + // 如果当前不是要删除的节点、但主键和副键都一样、需要特殊处理下。 + if (cur.Right != null && cur.Right.Key == key) + { + isFind = true; + removeCur = cur.Right; + currentCur = cur; + } + else + { + // 先向左查找下 + var currentNode = cur.Left; + while (currentNode != null && currentNode.SortKey == sortKey && currentNode.ViceKey == viceKey) + { + if (currentNode.Key == key) + { + isFind = true; + removeCur = currentNode; + currentCur = currentNode.Left; + break; + } + + currentNode = currentNode.Left; + } + + // 再向右查找下 + if (!isFind) + { + currentNode = cur.Right; + while (currentNode != null && currentNode.SortKey == sortKey && currentNode.ViceKey == viceKey) + { + if (currentNode.Key == key) + { + isFind = true; + removeCur = currentNode; + currentCur = currentNode.Left; + break; + } + + currentNode = currentNode.Right; + } + } + } + + if (isFind && currentCur != null) + { + value = removeCur.Value; + currentCur.Right = removeCur.Right; + + if (removeCur.Right != null) + { + removeCur.Right.Left = currentCur; + removeCur.Right = null; + } + + removeCur.Left = null; + removeCur.Down = null; + removeCur.Value = default; + + if (layer == 1) + { + var tempCur = currentCur.Right; + while (tempCur != null) + { + tempCur.Index--; + tempCur = tempCur.Right; + } + + Node.Remove(removeCur.Key); + } + + seen = true; + } + + cur = cur.Down; + } + + return seen; + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/DataStructure/SkipTable/SkipTableNode.cs b/Fantasy/Fantays.Console/Runtime/Core/DataStructure/SkipTable/SkipTableNode.cs new file mode 100644 index 0000000..8a1a144 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/DataStructure/SkipTable/SkipTableNode.cs @@ -0,0 +1,68 @@ +namespace Fantasy.DataStructure.SkipTable +{ + /// + /// 跳跃表节点。 + /// + /// 节点的值的类型。 + public class SkipTableNode + { + /// + /// 节点在跳跃表中的索引。 + /// + public int Index; + /// + /// 节点的主键。 + /// + public long Key; + /// + /// 节点的排序键。 + /// + public long SortKey; + /// + /// 节点的副键。 + /// + public long ViceKey; + /// + /// 节点存储的值。 + /// + public TValue Value; + /// + /// 指向左侧节点的引用。 + /// + public SkipTableNode Left; + /// + /// 指向右侧节点的引用。 + /// + public SkipTableNode Right; + /// + /// 指向下一层节点的引用。 + /// + public SkipTableNode Down; + + /// + /// 初始化跳跃表节点的新实例。 + /// + /// 节点的排序键。 + /// 节点的副键。 + /// 节点的主键。 + /// 节点存储的值。 + /// 节点在跳跃表中的索引。 + /// 指向左侧节点的引用。 + /// 指向右侧节点的引用。 + /// 指向下一层节点的引用。 + public SkipTableNode(long sortKey, long viceKey, long key, TValue value, int index, + SkipTableNode l, + SkipTableNode r, + SkipTableNode d) + { + Left = l; + Right = r; + Down = d; + Value = value; + Key = key; + Index = index; + SortKey = sortKey; + ViceKey = viceKey; + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Entitas/Component/CoroutineLock/CoroutineLock.cs b/Fantasy/Fantays.Console/Runtime/Core/Entitas/Component/CoroutineLock/CoroutineLock.cs new file mode 100644 index 0000000..a95d8f3 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Entitas/Component/CoroutineLock/CoroutineLock.cs @@ -0,0 +1,140 @@ +using System; +using System.Collections.Generic; +using Fantasy.Pool; + +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. +#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. +namespace Fantasy.Async +{ + /// + /// 协程锁专用的对象池 + /// + public sealed class CoroutineLockPool : PoolCore + { + /// + /// 协程锁专用的对象池的构造函数 + /// + public CoroutineLockPool() : base(2000) { } + } + + /// + /// 协程锁 + /// + public sealed class CoroutineLock : IPool, IDisposable + { + private Scene _scene; + private CoroutineLockComponent _coroutineLockComponent; + private readonly Dictionary _queue = new Dictionary(); + /// + /// 表示是否是对象池中创建的 + /// + private bool _isPool; + /// + /// 协程锁的类型 + /// + public long CoroutineLockType { get; private set; } + + internal void Initialize(CoroutineLockComponent coroutineLockComponent, ref long coroutineLockType) + { + _scene = coroutineLockComponent.Scene; + CoroutineLockType = coroutineLockType; + _coroutineLockComponent = coroutineLockComponent; + } + /// + /// 销毁协程锁,如果调用了该方法,所有使用当前协程锁等待的逻辑会按照顺序释放锁。 + /// + public void Dispose() + { + foreach (var (_, coroutineLockQueue) in _queue) + { + while (TryCoroutineLockQueueDequeue(coroutineLockQueue)) { } + } + + _queue.Clear(); + _scene = null; + CoroutineLockType = 0; + _coroutineLockComponent = null; + } + /// + /// 等待上一个任务完成 + /// + /// 需要等待的Id + /// 用于查询协程锁的标记,可不传入,只有在超时的时候排查是哪个锁超时时使用 + /// 等待多久会超时,当到达设定的时候会把当前锁给按照超时处理 + /// + public async FTask Wait(long coroutineLockQueueKey, string tag = null, int timeOut = 30000) + { + var waitCoroutineLock = _coroutineLockComponent.WaitCoroutineLockPool.Rent(this, ref coroutineLockQueueKey, tag, timeOut); + + if (!_queue.TryGetValue(coroutineLockQueueKey, out var queue)) + { + queue = _coroutineLockComponent.CoroutineLockQueuePool.Rent(); + _queue.Add(coroutineLockQueueKey, queue); + return waitCoroutineLock; + } + + queue.Enqueue(waitCoroutineLock); + return await waitCoroutineLock.Tcs; + } + /// + /// 按照先入先出的顺序,释放最早的一个协程锁 + /// + /// + public void Release(long coroutineLockQueueKey) + { + if (!_queue.TryGetValue(coroutineLockQueueKey, out var coroutineLockQueue)) + { + return; + } + + if (!TryCoroutineLockQueueDequeue(coroutineLockQueue)) + { + _queue.Remove(coroutineLockQueueKey); + } + } + + private bool TryCoroutineLockQueueDequeue(CoroutineLockQueue coroutineLockQueue) + { + if (!coroutineLockQueue.TryDequeue(out var waitCoroutineLock)) + { + _coroutineLockComponent.CoroutineLockQueuePool.Return(coroutineLockQueue); + return false; + } + + if (waitCoroutineLock.TimerId != 0) + { + _scene.TimerComponent.Net.Remove(waitCoroutineLock.TimerId); + } + + try + { + // 放到下一帧执行,如果不这样会导致逻辑的顺序不正常。 + _scene.ThreadSynchronizationContext.Post(waitCoroutineLock.SetResult); + } + catch (Exception e) + { + Log.Error($"Error in disposing CoroutineLock: {e}"); + } + + return true; + } + + /// + /// 获取一个值,该值指示当前实例是否为对象池中的实例。 + /// + /// + public bool IsPool() + { + return _isPool; + } + + /// + /// 设置一个值,该值指示当前实例是否为对象池中的实例。 + /// + /// + public void SetIsPool(bool isPool) + { + _isPool = isPool; + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Entitas/Component/CoroutineLock/CoroutineLockComponent.cs b/Fantasy/Fantays.Console/Runtime/Core/Entitas/Component/CoroutineLock/CoroutineLockComponent.cs new file mode 100644 index 0000000..4644274 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Entitas/Component/CoroutineLock/CoroutineLockComponent.cs @@ -0,0 +1,100 @@ +using System.Collections.Generic; +using Fantasy.Entitas; + +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. +#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. + +namespace Fantasy.Async +{ + /// + /// 协程锁组件 + /// + public class CoroutineLockComponent : Entity + { + private long _lockId; + private CoroutineLockPool _coroutineLockPool; + internal WaitCoroutineLockPool WaitCoroutineLockPool { get; private set; } + internal CoroutineLockQueuePool CoroutineLockQueuePool { get; private set; } + private readonly Dictionary _coroutineLocks = new Dictionary(); + internal CoroutineLockComponent Initialize() + { + _coroutineLockPool = new CoroutineLockPool(); + CoroutineLockQueuePool = new CoroutineLockQueuePool(); + WaitCoroutineLockPool = new WaitCoroutineLockPool(this); + return this; + } + + internal long LockId => ++_lockId; + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + public override void Dispose() +#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member + { + if (IsDisposed) + { + return; + } + + _lockId = 0; + base.Dispose(); + } + + /// + /// 创建一个新的协程锁 + /// 使用这个方法创建的协程锁,需要手动释放管理CoroutineLock。 + /// 不会再CoroutineLockComponent理进行管理。 + /// + /// + /// + public CoroutineLock Create(long coroutineLockType) + { + var coroutineLock = _coroutineLockPool.Rent(); + coroutineLock.Initialize(this, ref coroutineLockType); + return coroutineLock; + } + + /// + /// 请求一个协程锁。 + /// 使用这个方法创建的协程锁,会自动释放CoroutineLockQueueType。 + /// + /// 锁类型 + /// 锁队列Id + /// 当某些锁超时,需要一个标记来方便排查问题,正常的情况下这个默认为null就可以。 + /// 设置锁的超时时间,让超过设置的时间会触发超时,保证锁不会因为某一个锁一直不解锁导致卡住的问题。 + /// + /// 返回的WaitCoroutineLock通过Dispose来解除这个锁、建议用using来保住这个锁。 + /// 也可以返回的WaitCoroutineLock通过CoroutineLockComponent.UnLock来解除这个锁。 + /// + public FTask Wait(long coroutineLockType, long coroutineLockQueueKey, string tag = null, int time = 30000) + { + if (!_coroutineLocks.TryGetValue(coroutineLockType, out var coroutineLock)) + { + coroutineLock = _coroutineLockPool.Rent(); + coroutineLock.Initialize(this, ref coroutineLockType); + _coroutineLocks.Add(coroutineLockType, coroutineLock); + } + + return coroutineLock.Wait(coroutineLockQueueKey, tag, time); + } + + /// + /// 解除一个协程锁。 + /// + /// + /// + public void Release(int coroutineLockType, long coroutineLockQueueKey) + { + if (IsDisposed) + { + return; + } + + if (!_coroutineLocks.TryGetValue(coroutineLockType, out var coroutineLock)) + { + return; + } + + coroutineLock.Release(coroutineLockQueueKey); + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Entitas/Component/CoroutineLock/CoroutineLockQueue.cs b/Fantasy/Fantays.Console/Runtime/Core/Entitas/Component/CoroutineLock/CoroutineLockQueue.cs new file mode 100644 index 0000000..2948aca --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Entitas/Component/CoroutineLock/CoroutineLockQueue.cs @@ -0,0 +1,35 @@ +// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract +using System.Collections.Generic; +using Fantasy.Pool; + +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. +#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. +namespace Fantasy.Async +{ + internal sealed class CoroutineLockQueuePool : PoolCore + { + public CoroutineLockQueuePool() : base(2000) { } + } + + internal sealed class CoroutineLockQueue : Queue, IPool + { + private bool _isPool; + /// + /// 获取一个值,该值指示当前实例是否为对象池中的实例。 + /// + /// + public bool IsPool() + { + return _isPool; + } + + /// + /// 设置一个值,该值指示当前实例是否为对象池中的实例。 + /// + /// + public void SetIsPool(bool isPool) + { + _isPool = isPool; + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Entitas/Component/CoroutineLock/WaitCoroutineLock.cs b/Fantasy/Fantays.Console/Runtime/Core/Entitas/Component/CoroutineLock/WaitCoroutineLock.cs new file mode 100644 index 0000000..28d04d4 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Entitas/Component/CoroutineLock/WaitCoroutineLock.cs @@ -0,0 +1,146 @@ +using System; +using Fantasy.Event; +using Fantasy.Pool; + +#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. +namespace Fantasy.Async +{ + internal sealed class WaitCoroutineLockPool : PoolCore + { + private readonly Scene _scene; + private readonly CoroutineLockComponent _coroutineLockComponent; + + public WaitCoroutineLockPool(CoroutineLockComponent coroutineLockComponent) : base(2000) + { + _scene = coroutineLockComponent.Scene; + _coroutineLockComponent = coroutineLockComponent; + } + + public WaitCoroutineLock Rent(CoroutineLock coroutineLock, ref long coroutineLockQueueKey, string tag = null, int timeOut = 30000) + { + var timerId = 0L; + var lockId = _coroutineLockComponent.LockId; + var waitCoroutineLock = _coroutineLockComponent.WaitCoroutineLockPool.Rent(); + + if (timeOut > 0) + { + timerId = _scene.TimerComponent.Net.OnceTimer(timeOut, new CoroutineLockTimeout(ref lockId, waitCoroutineLock)); + } + + waitCoroutineLock.Initialize(coroutineLock, this, ref coroutineLockQueueKey, ref timerId, ref lockId, tag); + return waitCoroutineLock; + } + } + + internal struct CoroutineLockTimeout + { + public readonly long LockId; + public readonly WaitCoroutineLock WaitCoroutineLock; + + public CoroutineLockTimeout(ref long lockId, WaitCoroutineLock waitCoroutineLock) + { + LockId = lockId; + WaitCoroutineLock = waitCoroutineLock; + } + } + + internal sealed class OnCoroutineLockTimeout : EventSystem + { + protected override void Handler(CoroutineLockTimeout self) + { + var selfWaitCoroutineLock = self.WaitCoroutineLock; + + if (self.LockId != selfWaitCoroutineLock.LockId) + { + return; + } + + Log.Error($"coroutine lock timeout CoroutineLockQueueType:{selfWaitCoroutineLock.CoroutineLock.CoroutineLockType} Key:{selfWaitCoroutineLock.CoroutineLockQueueKey} Tag:{selfWaitCoroutineLock.Tag}"); + } + } + + /// + /// 一个协程锁的实例,用户可以用过这个手动释放锁 + /// + public sealed class WaitCoroutineLock : IPool, IDisposable + { + private bool _isPool; + internal string Tag { get; private set; } + internal long LockId { get; private set; } + internal long TimerId { get; private set; } + internal long CoroutineLockQueueKey { get; private set; } + internal CoroutineLock CoroutineLock { get; private set; } + + private bool _isSetResult; + private FTask _tcs; + private WaitCoroutineLockPool _waitCoroutineLockPool; + internal void Initialize(CoroutineLock coroutineLock, WaitCoroutineLockPool waitCoroutineLockPool, ref long coroutineLockQueueKey, ref long timerId, ref long lockId, string tag) + { + Tag = tag; + LockId = lockId; + TimerId = timerId; + CoroutineLock = coroutineLock; + CoroutineLockQueueKey = coroutineLockQueueKey; + _waitCoroutineLockPool = waitCoroutineLockPool; + } + /// + /// 释放协程锁 + /// + public void Dispose() + { + if (LockId == 0) + { + Log.Error("WaitCoroutineLock is already disposed"); + return; + } + + CoroutineLock.Release(CoroutineLockQueueKey); + + _tcs = null; + Tag = null; + LockId = 0; + TimerId = 0; + _isSetResult = false; + CoroutineLockQueueKey = 0; + _waitCoroutineLockPool.Return(this); + CoroutineLock = null; + _waitCoroutineLockPool = null; + } + + internal FTask Tcs + { + get { return _tcs ??= FTask.Create(); } + } + + internal void SetResult() + { + if (_isSetResult) + { + Log.Error("WaitCoroutineLock is already SetResult"); + return; + } + + _isSetResult = true; + Tcs.SetResult(this); + } + + /// + /// 获取一个值,该值指示当前实例是否为对象池中的实例。 + /// + /// + public bool IsPool() + { + return _isPool; + } + + /// + /// 设置一个值,该值指示当前实例是否为对象池中的实例。 + /// + /// + public void SetIsPool(bool isPool) + { + _isPool = isPool; + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Entitas/Component/EntityComponent.cs b/Fantasy/Fantays.Console/Runtime/Core/Entitas/Component/EntityComponent.cs new file mode 100644 index 0000000..5baa63e --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Entitas/Component/EntityComponent.cs @@ -0,0 +1,471 @@ +// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; +using Fantasy.Assembly; +using Fantasy.Async; +using Fantasy.DataStructure.Collection; +using Fantasy.Entitas; +using Fantasy.Entitas.Interface; +using Fantasy.Helper; +#pragma warning disable CS8602 // Dereference of a possibly null reference. +#pragma warning disable CS8765 // Nullability of type of parameter doesn't match overridden member (possibly because of nullability attributes). + +#pragma warning disable CS8604 // Possible null reference argument. +#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type. +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +namespace Fantasy.Entitas +{ + internal sealed class UpdateQueueInfo + { + public bool IsStop; + public readonly Type Type; + public readonly long RunTimeId; + + public UpdateQueueInfo(Type type, long runTimeId) + { + Type = type; + IsStop = false; + RunTimeId = runTimeId; + } + } + + internal sealed class FrameUpdateQueueInfo + { + public readonly Type Type; + public readonly long RunTimeId; + + public FrameUpdateQueueInfo(Type type, long runTimeId) + { + Type = type; + RunTimeId = runTimeId; + } + } + + internal struct CustomEntitiesSystemKey : IEquatable + { + public int CustomEventType { get; } + public Type EntitiesType { get; } + public CustomEntitiesSystemKey(int customEventType, Type entitiesType) + { + CustomEventType = customEventType; + EntitiesType = entitiesType; + } + public bool Equals(CustomEntitiesSystemKey other) + { + return CustomEventType == other.CustomEventType && EntitiesType == other.EntitiesType; + } + + public override bool Equals(object obj) + { + return obj is CustomEntitiesSystemKey other && Equals(other); + } + + public override int GetHashCode() + { + return HashCode.Combine(CustomEventType, EntitiesType); + } + } + + /// + /// Entity管理组件 + /// + public sealed class EntityComponent : Entity, ISceneUpdate, IAssembly + { + private readonly OneToManyList _assemblyList = new(); + private readonly OneToManyList _assemblyHashCodes = new(); + + private readonly Dictionary _awakeSystems = new(); + private readonly Dictionary _updateSystems = new(); + private readonly Dictionary _destroySystems = new(); + private readonly Dictionary _deserializeSystems = new(); + private readonly Dictionary _frameUpdateSystem = new(); + + private readonly OneToManyList _assemblyCustomSystemList = new(); + private readonly Dictionary _customEntitiesSystems = new Dictionary(); + + private readonly Dictionary _hashCodes = new Dictionary(); + private readonly Queue _updateQueue = new Queue(); + private readonly Queue _frameUpdateQueue = new Queue(); + private readonly Dictionary _updateQueueDic = new Dictionary(); + + internal async FTask Initialize() + { + await AssemblySystem.Register(this); + return this; + } + + #region Assembly + + public FTask Load(long assemblyIdentity) + { + var task = FTask.Create(false); + Scene?.ThreadSynchronizationContext.Post(() => + { + LoadInner(assemblyIdentity); + task.SetResult(); + }); + return task; + } + + public FTask ReLoad(long assemblyIdentity) + { + var task = FTask.Create(false); + Scene?.ThreadSynchronizationContext.Post(() => + { + OnUnLoadInner(assemblyIdentity); + LoadInner(assemblyIdentity); + task.SetResult(); + }); + + return task; + } + + public FTask OnUnLoad(long assemblyIdentity) + { + var task = FTask.Create(false); + Scene?.ThreadSynchronizationContext.Post(() => + { + OnUnLoadInner(assemblyIdentity); + task.SetResult(); + }); + return task; + } + + private void LoadInner(long assemblyIdentity) + { + foreach (var entityType in AssemblySystem.ForEach(assemblyIdentity, typeof(IEntity))) + { + _hashCodes.Add(entityType, HashCodeHelper.ComputeHash64(entityType.FullName)); + _assemblyHashCodes.Add(assemblyIdentity, entityType); + } + + foreach (var entitiesSystemType in AssemblySystem.ForEach(assemblyIdentity, typeof(IEntitiesSystem))) + { + Type entitiesType = null; + var entity = Activator.CreateInstance(entitiesSystemType); + + switch (entity) + { + case IAwakeSystem iAwakeSystem: + { + entitiesType = iAwakeSystem.EntitiesType(); + _awakeSystems.Add(entitiesType, iAwakeSystem); + break; + } + case IDestroySystem iDestroySystem: + { + entitiesType = iDestroySystem.EntitiesType(); + _destroySystems.Add(entitiesType, iDestroySystem); + break; + } + case IDeserializeSystem iDeserializeSystem: + { + entitiesType = iDeserializeSystem.EntitiesType(); + _deserializeSystems.Add(entitiesType, iDeserializeSystem); + break; + } + case IUpdateSystem iUpdateSystem: + { + entitiesType = iUpdateSystem.EntitiesType(); + _updateSystems.Add(entitiesType, iUpdateSystem); + break; + } + case IFrameUpdateSystem iFrameUpdateSystem: + { + entitiesType = iFrameUpdateSystem.EntitiesType(); + _frameUpdateSystem.Add(entitiesType, iFrameUpdateSystem); + break; + } + default: + { + Log.Error($"IEntitiesSystem not support type {entitiesSystemType}"); + return; + } + } + + _assemblyList.Add(assemblyIdentity, entitiesType); + } + + foreach (var customEntitiesSystemType in AssemblySystem.ForEach(assemblyIdentity, typeof(ICustomEntitiesSystem))) + { + var entity = (ICustomEntitiesSystem)Activator.CreateInstance(customEntitiesSystemType); + var customEntitiesSystemKey = new CustomEntitiesSystemKey(entity.CustomEventType, entity.EntitiesType()); + _customEntitiesSystems.Add(customEntitiesSystemKey, entity); + _assemblyCustomSystemList.Add(assemblyIdentity, customEntitiesSystemKey); + } + } + + private void OnUnLoadInner(long assemblyIdentity) + { + if (_assemblyHashCodes.TryGetValue(assemblyIdentity, out var entityType)) + { + foreach (var type in entityType) + { + _hashCodes.Remove(type); + } + + _assemblyHashCodes.RemoveByKey(assemblyIdentity); + } + + if (_assemblyList.TryGetValue(assemblyIdentity, out var assembly)) + { + foreach (var type in assembly) + { + _awakeSystems.Remove(type); + _updateSystems.Remove(type); + _destroySystems.Remove(type); + _deserializeSystems.Remove(type); + _frameUpdateSystem.Remove(type); + } + + _assemblyList.RemoveByKey(assemblyIdentity); + } + + if (_assemblyCustomSystemList.TryGetValue(assemblyIdentity, out var customSystemAssembly)) + { + foreach (var customEntitiesSystemKey in customSystemAssembly) + { + _customEntitiesSystems.Remove(customEntitiesSystemKey); + } + + _assemblyCustomSystemList.RemoveByKey(assemblyIdentity); + } + } + + #endregion + + #region Event + + /// + /// 触发实体的唤醒方法 + /// + /// 实体对象 + public void Awake(Entity entity) + { + if (!_awakeSystems.TryGetValue(entity.Type, out var awakeSystem)) + { + return; + } + + try + { + awakeSystem.Invoke(entity); + } + catch (Exception e) + { + Log.Error($"{entity.Type.FullName} Error {e}"); + } + } + + /// + /// 触发实体的销毁方法 + /// + /// 实体对象 + public void Destroy(Entity entity) + { + if (!_destroySystems.TryGetValue(entity.Type, out var system)) + { + return; + } + + try + { + system.Invoke(entity); + } + catch (Exception e) + { + Log.Error($"{entity.Type.FullName} Destroy Error {e}"); + } + } + + /// + /// 触发实体的反序列化方法 + /// + /// 实体对象 + public void Deserialize(Entity entity) + { + if (!_deserializeSystems.TryGetValue(entity.Type, out var system)) + { + return; + } + + try + { + system.Invoke(entity); + } + catch (Exception e) + { + Log.Error($"{entity.Type.FullName} Deserialize Error {e}"); + } + } + + #endregion + + #region CustomEvent + + public void CustomSystem(Entity entity, int customEventType) + { + var customEntitiesSystemKey = new CustomEntitiesSystemKey(customEventType, entity.Type); + + if (!_customEntitiesSystems.TryGetValue(customEntitiesSystemKey, out var system)) + { + return; + } + + try + { + system.Invoke(entity); + } + catch (Exception e) + { + Log.Error($"{entity.Type.FullName} CustomSystem Error {e}"); + } + } + + #endregion + + #region Update + + /// + /// 将实体加入更新队列,准备进行更新 + /// + /// 实体对象 + public void StartUpdate(Entity entity) + { + var type = entity.Type; + var entityRuntimeId = entity.RuntimeId; + + if (_updateSystems.ContainsKey(type)) + { + var updateQueueInfo = new UpdateQueueInfo(type, entityRuntimeId); + _updateQueue.Enqueue(updateQueueInfo); + _updateQueueDic.Add(entityRuntimeId, updateQueueInfo); + } + + if (_frameUpdateSystem.ContainsKey(type)) + { + _frameUpdateQueue.Enqueue(new FrameUpdateQueueInfo(type, entityRuntimeId)); + } + } + + /// + /// 停止实体进行更新 + /// + /// 实体对象 + public void StopUpdate(Entity entity) + { + if (!_updateQueueDic.Remove(entity.RuntimeId, out var updateQueueInfo)) + { + return; + } + + updateQueueInfo.IsStop = true; + } + + /// + /// 执行实体系统的更新逻辑 + /// + public void Update() + { + var updateQueueCount = _updateQueue.Count; + + while (updateQueueCount-- > 0) + { + var updateQueueStruct = _updateQueue.Dequeue(); + + if (updateQueueStruct.IsStop) + { + continue; + } + + if (!_updateSystems.TryGetValue(updateQueueStruct.Type, out var updateSystem)) + { + continue; + } + + var entity = Scene.GetEntity(updateQueueStruct.RunTimeId); + + if (entity == null || entity.IsDisposed) + { + _updateQueueDic.Remove(updateQueueStruct.RunTimeId); + continue; + } + + _updateQueue.Enqueue(updateQueueStruct); + + try + { + updateSystem.Invoke(entity); + } + catch (Exception e) + { + Log.Error($"{updateQueueStruct.Type.FullName} Update Error {e}"); + } + } + } + + /// + /// 执行实体系统的帧更新逻辑 + /// + public void FrameUpdate() + { + var count = _frameUpdateQueue.Count; + + while (count-- > 0) + { + var frameUpdateQueueStruct = _frameUpdateQueue.Dequeue(); + + if (!_frameUpdateSystem.TryGetValue(frameUpdateQueueStruct.Type, out var frameUpdateSystem)) + { + continue; + } + + var entity = Scene.GetEntity(frameUpdateQueueStruct.RunTimeId); + + if (entity == null || entity.IsDisposed) + { + continue; + } + + _frameUpdateQueue.Enqueue(frameUpdateQueueStruct); + + try + { + frameUpdateSystem.Invoke(entity); + } + catch (Exception e) + { + Log.Error($"{frameUpdateQueueStruct.Type.FullName} FrameUpdate Error {e}"); + } + } + } + + #endregion + + public long GetHashCode(Type type) + { + return _hashCodes[type]; + } + + /// + /// 释放实体系统管理器资源 + /// + public override void Dispose() + { + _updateQueue.Clear(); + _frameUpdateQueue.Clear(); + + _assemblyList.Clear(); + _awakeSystems.Clear(); + _updateSystems.Clear(); + _destroySystems.Clear(); + _deserializeSystems.Clear(); + _frameUpdateSystem.Clear(); + + AssemblySystem.UnRegister(this); + base.Dispose(); + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Entitas/Component/EventComponent/EventComponent.cs b/Fantasy/Fantays.Console/Runtime/Core/Entitas/Component/EventComponent/EventComponent.cs new file mode 100644 index 0000000..30ca61e --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Entitas/Component/EventComponent/EventComponent.cs @@ -0,0 +1,252 @@ +using System; +using System.Reflection; +using Fantasy.Assembly; +using Fantasy.Async; +using Fantasy.DataStructure.Collection; +using Fantasy.Entitas; + +// ReSharper disable PossibleMultipleEnumeration +#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type. +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +// ReSharper disable MethodOverloadWithOptionalParameter + +namespace Fantasy.Event +{ + internal sealed class EventCache + { + public readonly Type EnventType; + public readonly object Obj; + public EventCache(Type enventType, object obj) + { + EnventType = enventType; + Obj = obj; + } + } + + public sealed class EventComponent : Entity, IAssembly + { + private readonly OneToManyList _events = new(); + private readonly OneToManyList _asyncEvents = new(); + private readonly OneToManyList _assemblyEvents = new(); + private readonly OneToManyList _assemblyAsyncEvents = new(); + + internal async FTask Initialize() + { + await AssemblySystem.Register(this); + return this; + } + + #region Assembly + + public async FTask Load(long assemblyIdentity) + { + var tcs = FTask.Create(false); + Scene?.ThreadSynchronizationContext.Post(() => + { + LoadInner(assemblyIdentity); + tcs.SetResult(); + }); + await tcs; + } + + public async FTask ReLoad(long assemblyIdentity) + { + var tcs = FTask.Create(false); + Scene?.ThreadSynchronizationContext.Post(() => + { + OnUnLoadInner(assemblyIdentity); + LoadInner(assemblyIdentity); + tcs.SetResult(); + }); + await tcs; + } + + public async FTask OnUnLoad(long assemblyIdentity) + { + var tcs = FTask.Create(false); + Scene?.ThreadSynchronizationContext.Post(() => + { + OnUnLoadInner(assemblyIdentity); + tcs.SetResult(); + }); + await tcs; + } + + private void LoadInner(long assemblyIdentity) + { + foreach (var type in AssemblySystem.ForEach(assemblyIdentity, typeof(IEvent))) + { + var @event = (IEvent)Activator.CreateInstance(type); + + if (@event == null) + { + continue; + } + + var eventType = @event.EventType(); + _events.Add(eventType, @event); + _assemblyEvents.Add(assemblyIdentity, new EventCache(eventType, @event)); + } + + foreach (var type in AssemblySystem.ForEach(assemblyIdentity, typeof(IAsyncEvent))) + { + var @event = (IAsyncEvent)Activator.CreateInstance(type); + + if (@event == null) + { + continue; + } + + var eventType = @event.EventType(); + _asyncEvents.Add(eventType, @event); + _assemblyAsyncEvents.Add(assemblyIdentity, new EventCache(eventType, @event)); + } + } + + private void OnUnLoadInner(long assemblyIdentity) + { + if (_assemblyEvents.TryGetValue(assemblyIdentity, out var events)) + { + foreach (var @event in events) + { + _events.RemoveValue(@event.EnventType, (IEvent)@event.Obj); + } + + _assemblyEvents.RemoveByKey(assemblyIdentity); + } + + if (_assemblyAsyncEvents.TryGetValue(assemblyIdentity, out var asyncEvents)) + { + foreach (var @event in asyncEvents) + { + _asyncEvents.RemoveValue(@event.EnventType, (IAsyncEvent)@event.Obj); + } + + _assemblyAsyncEvents.RemoveByKey(assemblyIdentity); + } + } + + #endregion + + #region Publish + + /// + /// 发布一个值类型的事件数据。 + /// + /// 事件数据类型(值类型)。 + /// 事件数据实例。 + public void Publish(TEventData eventData) where TEventData : struct + { + if (!_events.TryGetValue(typeof(TEventData), out var list)) + { + return; + } + + foreach (var @event in list) + { + try + { + @event.Invoke(eventData); + } + catch (Exception e) + { + Log.Error(e); + } + } + } + + /// + /// 发布一个继承自 Entity 的事件数据。 + /// + /// 事件数据类型(继承自 Entity)。 + /// 事件数据实例。 + /// 是否释放事件数据。 + public void Publish(TEventData eventData, bool isDisposed = true) where TEventData : Entity + { + if (!_events.TryGetValue(typeof(TEventData), out var list)) + { + return; + } + + foreach (var @event in list) + { + try + { + @event.Invoke(eventData); + } + catch (Exception e) + { + Log.Error(e); + } + } + + if (isDisposed) + { + eventData.Dispose(); + } + } + + /// + /// 异步发布一个值类型的事件数据。 + /// + /// 事件数据类型(值类型)。 + /// 事件数据实例。 + /// 表示异步操作的任务。 + public async FTask PublishAsync(TEventData eventData) where TEventData : struct + { + if (!_asyncEvents.TryGetValue(typeof(TEventData), out var list)) + { + return; + } + + using var tasks = ListPool.Create(); + + foreach (var @event in list) + { + tasks.Add(@event.InvokeAsync(eventData)); + } + + await FTask.WaitAll(tasks); + } + + /// + /// 异步发布一个继承自 Entity 的事件数据。 + /// + /// 事件数据类型(继承自 Entity)。 + /// 事件数据实例。 + /// 是否释放事件数据。 + /// 表示异步操作的任务。 + public async FTask PublishAsync(TEventData eventData, bool isDisposed = true) where TEventData : Entity + { + if (!_asyncEvents.TryGetValue(eventData.GetType(), out var list)) + { + return; + } + + using var tasks = ListPool.Create(); + + foreach (var @event in list) + { + tasks.Add(@event.InvokeAsync(eventData)); + } + + await FTask.WaitAll(tasks); + + if (isDisposed) + { + eventData.Dispose(); + } + } + + #endregion + + public override void Dispose() + { + _events.Clear(); + _asyncEvents.Clear(); + _assemblyEvents.Clear(); + _assemblyAsyncEvents.Clear(); + base.Dispose(); + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Entitas/Component/EventComponent/Interface/IEvent.cs b/Fantasy/Fantays.Console/Runtime/Core/Entitas/Component/EventComponent/Interface/IEvent.cs new file mode 100644 index 0000000..5314995 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Entitas/Component/EventComponent/Interface/IEvent.cs @@ -0,0 +1,112 @@ +using System; +using Fantasy.Async; + +namespace Fantasy.Event +{ + /// + /// 事件的接口 + /// + public interface IEvent + { + /// + /// 用于指定事件的Type + /// + /// + Type EventType(); + /// + /// 时间内部使用的入口 + /// + /// + void Invoke(object self); + } + + /// + /// 异步事件的接口 + /// + public interface IAsyncEvent + { + /// + /// + /// + /// + Type EventType(); + /// + /// + /// + /// + FTask InvokeAsync(object self); + } + + /// + /// 事件的抽象类,要使用事件必须要继承这个抽象接口。 + /// + /// 要监听的事件泛型类型 + public abstract class EventSystem : IEvent + { + private readonly Type _selfType = typeof(T); + /// + /// + /// + /// + public Type EventType() + { + return _selfType; + } + /// + /// 事件调用的方法,要在这个方法里编写事件发生的逻辑 + /// + /// + protected abstract void Handler(T self); + /// + /// + /// + /// + public void Invoke(object self) + { + try + { + Handler((T) self); + } + catch (Exception e) + { + Log.Error($"{_selfType.Name} Error {e}"); + } + } + } + /// + /// 异步事件的抽象类,要使用事件必须要继承这个抽象接口。 + /// + /// 要监听的事件泛型类型 + public abstract class AsyncEventSystem : IAsyncEvent + { + private readonly Type _selfType = typeof(T); + /// + /// + /// + /// + public Type EventType() + { + return _selfType; + } + /// + /// 事件调用的方法,要在这个方法里编写事件发生的逻辑 + /// + /// + protected abstract FTask Handler(T self); + /// + /// + /// + /// + public async FTask InvokeAsync(object self) + { + try + { + await Handler((T) self); + } + catch (Exception e) + { + Log.Error($"{_selfType.Name} Error {e}"); + } + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Entitas/Component/MessagePoolComponent.cs b/Fantasy/Fantays.Console/Runtime/Core/Entitas/Component/MessagePoolComponent.cs new file mode 100644 index 0000000..f1ddb23 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Entitas/Component/MessagePoolComponent.cs @@ -0,0 +1,139 @@ +// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract + +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using Fantasy.DataStructure.Collection; +using Fantasy.Entitas; +using Fantasy.Pool; +using Fantasy.Serialize; + +namespace Fantasy.Entitas +{ + /// + /// 消息的对象池组件 + /// + public sealed class MessagePoolComponent : Entity + { + private int _poolCount; + private const int MaxCapacity = ushort.MaxValue; + private readonly OneToManyQueue _poolQueue = new OneToManyQueue(); + private readonly Dictionary> _typeCheckCache = new Dictionary>(); + /// + /// 销毁组件 + /// + public override void Dispose() + { + _poolCount = 0; + _poolQueue.Clear(); + _typeCheckCache.Clear(); + base.Dispose(); + } + /// + /// 从对象池里获取一个消息,如果没有就创建一个新的 + /// + /// 消息的泛型类型 + /// + public T Rent() where T : AMessage, new() + { + if (!_poolQueue.TryDequeue(typeof(T), out var queue)) + { + var instance = new T(); + instance.SetScene(Scene); + instance.SetIsPool(true); + return instance; + } + + queue.SetIsPool(true); + _poolCount--; + return (T)queue; + } + + /// + /// + /// + /// 消息的类型 + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public AMessage Rent(Type type) + { + if (!_poolQueue.TryDequeue(type, out var queue)) + { + if (!_typeCheckCache.TryGetValue(type, out var createInstance)) + { + if (!typeof(AMessage).IsAssignableFrom(type)) + { + throw new NotSupportedException($"{this.GetType().FullName} Type:{type.FullName} must inherit from IPool"); + } + else + { + createInstance = CreateInstance.CreateMessage(type); + _typeCheckCache[type] = createInstance; + } + } + + var instance = createInstance(); + instance.SetScene(Scene); + instance.SetIsPool(true); + return instance; + } + + queue.SetIsPool(true); + _poolCount--; + return queue; + } + /// + /// 返还一个消息到对象池中 + /// + /// + public void Return(AMessage obj) + { + if (obj == null) + { + return; + } + + if (!obj.IsPool()) + { + return; + } + + if (_poolCount >= MaxCapacity) + { + return; + } + + _poolCount++; + obj.SetIsPool(false); + _poolQueue.Enqueue(obj.GetType(), obj); + } + + /// + /// + /// + /// 返还的消息 + /// 返还的消息泛型类型 + public void Return(T obj) where T : AMessage + { + if (obj == null) + { + return; + } + + if (!obj.IsPool()) + { + return; + } + + if (_poolCount >= MaxCapacity) + { + return; + } + + _poolCount++; + obj.SetIsPool(false); + _poolQueue.Enqueue(typeof(T), obj); + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Entitas/Component/SingleCollectionComponent/SingleCollectionComponent.cs b/Fantasy/Fantays.Console/Runtime/Core/Entitas/Component/SingleCollectionComponent/SingleCollectionComponent.cs new file mode 100644 index 0000000..5c0969d --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Entitas/Component/SingleCollectionComponent/SingleCollectionComponent.cs @@ -0,0 +1,167 @@ +// ReSharper disable SuspiciousTypeConversion.Global + +using Fantasy.Assembly; +using Fantasy.Async; +using Fantasy.DataStructure.Collection; +using Fantasy.Entitas; +using Fantasy.Entitas.Interface; +using Fantasy.Helper; + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +#pragma warning disable CS8604 // Possible null reference argument. +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. +#if FANTASY_NET +namespace Fantasy.SingleCollection +{ + /// + /// 用于处理Entity下的实体进行数据库分表存储的组件 + /// + public sealed class SingleCollectionComponent : Entity, IAssembly + { + private CoroutineLock _coroutineLock; + private readonly OneToManyHashSet _collection = new OneToManyHashSet(); + + private readonly OneToManyList _assemblyCollections = + new OneToManyList(); + + private sealed class SingleCollectionInfo(Type rootType, string collectionName) + { + public readonly Type RootType = rootType; + public readonly string CollectionName = collectionName; + } + + internal async FTask Initialize() + { + var coroutineLockType = HashCodeHelper.ComputeHash64(GetType().FullName); + _coroutineLock = Scene.CoroutineLockComponent.Create(coroutineLockType); + await AssemblySystem.Register(this); + return this; + } + + #region Assembly + + public async FTask Load(long assemblyIdentity) + { + var tcs = FTask.Create(false); + Scene?.ThreadSynchronizationContext.Post(() => + { + LoadInner(assemblyIdentity); + tcs.SetResult(); + }); + await tcs; + } + + public async FTask ReLoad(long assemblyIdentity) + { + var tcs = FTask.Create(false); + Scene?.ThreadSynchronizationContext.Post(() => + { + OnUnLoadInner(assemblyIdentity); + LoadInner(assemblyIdentity); + tcs.SetResult(); + }); + await tcs; + } + + public async FTask OnUnLoad(long assemblyIdentity) + { + var tcs = FTask.Create(false); + Scene?.ThreadSynchronizationContext.Post(() => + { + OnUnLoadInner(assemblyIdentity); + tcs.SetResult(); + }); + await tcs; + } + + private void LoadInner(long assemblyIdentity) + { + foreach (var type in AssemblySystem.ForEach(assemblyIdentity, typeof(ISupportedSingleCollection))) + { + var customAttributes = type.GetCustomAttributes(typeof(SingleCollectionAttribute), false); + if (customAttributes.Length == 0) + { + Log.Error( + $"type {type.FullName} Implemented the interface of ISingleCollection, requiring the implementation of SingleCollectionAttribute"); + continue; + } + + var singleCollectionAttribute = (SingleCollectionAttribute)customAttributes[0]; + var rootType = singleCollectionAttribute.RootType; + var collectionName = singleCollectionAttribute.CollectionName; + _collection.Add(rootType, collectionName); + _assemblyCollections.Add(assemblyIdentity, new SingleCollectionInfo(rootType, collectionName)); + } + } + + private void OnUnLoadInner(long assemblyIdentity) + { + if (!_assemblyCollections.TryGetValue(assemblyIdentity, out var types)) + { + return; + } + + foreach (var singleCollectionInfo in types) + { + _collection.RemoveValue(singleCollectionInfo.RootType, singleCollectionInfo.CollectionName); + } + + _assemblyCollections.RemoveByKey(assemblyIdentity); + } + + #endregion + + #region Collections + + /// + /// 通过数据库获取某一个实体类型下所有的分表数据到当前实体下,并且会自动建立父子关系。 + /// + /// 实体实例 + /// 实体泛型类型 + public async FTask GetCollections(T entity) where T : Entity, ISingleCollectionRoot + { + if (!_collection.TryGetValue(typeof(T), out var collections)) + { + return; + } + + var worldDateBase = Scene.World.DataBase; + + using (await _coroutineLock.Wait(entity.Id)) + { + foreach (var collectionName in collections) + { + var singleCollection = await worldDateBase.QueryNotLock(entity.Id, true, collectionName); + entity.AddComponent(singleCollection); + } + } + } + + /// + /// 存储当前实体下支持分表的组件到数据中,包括存储实体本身。 + /// + /// 实体实例 + /// 实体泛型类型 + public async FTask SaveCollections(T entity) where T : Entity, ISingleCollectionRoot + { + using var collections = ListPool.Create(); + + foreach (var treeEntity in entity.ForEachSingleCollection) + { + if (treeEntity is not ISupportedSingleCollection) + { + continue; + } + + collections.Add(treeEntity); + } + + collections.Add(entity); + await entity.Scene.World.DataBase.Save(entity.Id, collections); + } + + #endregion + } +} + +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Entitas/Component/TimerComponent/Interface/TimerHandler.cs b/Fantasy/Fantays.Console/Runtime/Core/Entitas/Component/TimerComponent/Interface/TimerHandler.cs new file mode 100644 index 0000000..9e1f4c9 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Entitas/Component/TimerComponent/Interface/TimerHandler.cs @@ -0,0 +1,10 @@ +using Fantasy.Event; + +namespace Fantasy.Timer +{ + /// + /// 计时器抽象类,提供了一个基础框架,用于创建处理计时器事件的具体类。 + /// + /// 事件的类型参数 + public abstract class TimerHandler : EventSystem { } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Entitas/Component/TimerComponent/TimeWheel/ScheduledTask.cs b/Fantasy/Fantays.Console/Runtime/Core/Entitas/Component/TimerComponent/TimeWheel/ScheduledTask.cs new file mode 100644 index 0000000..d644386 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Entitas/Component/TimerComponent/TimeWheel/ScheduledTask.cs @@ -0,0 +1,49 @@ +// #pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. +// #pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. +// namespace Fantasy +// { +// public sealed class ScheduledTaskPool : PoolCore +// { +// public ScheduledTaskPool() : base(2000) { } +// +// public ScheduledTask Rent(Action action, ref int rounds, ref int finalSlot) +// { +// var scheduledTask = Rent(); +// scheduledTask.Rounds = rounds; +// scheduledTask.Action = action; +// scheduledTask.FinalSlot = finalSlot; +// return scheduledTask; +// } +// +// public override void Return(ScheduledTask item) +// { +// base.Return(item); +// item.Dispose(); +// } +// } +// +// public sealed class ScheduledTask : IPool, IDisposable +// { +// public int Rounds; +// public int FinalSlot; +// public Action Action; +// public LinkedListNode Node; +// +// public bool IsPool { get; set; } +// public ScheduledTask() { } +// public ScheduledTask(Action action, ref int rounds, ref int finalSlot) +// { +// Action = action; +// Rounds = rounds; +// FinalSlot = finalSlot; +// } +// +// public void Dispose() +// { +// Rounds = 0; +// FinalSlot = 0; +// Action = null; +// Node = null; +// } +// } +// } \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Entitas/Component/TimerComponent/TimeWheel/TimeWheel.cs b/Fantasy/Fantays.Console/Runtime/Core/Entitas/Component/TimerComponent/TimeWheel/TimeWheel.cs new file mode 100644 index 0000000..58916b1 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Entitas/Component/TimerComponent/TimeWheel/TimeWheel.cs @@ -0,0 +1,134 @@ +// using System.Runtime.CompilerServices; +// // ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract +// #pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. +// +// namespace Fantasy +// { +// public sealed class TimeWheel +// { +// private int _currentIndex; +// private ScheduledTaskPool _scheduledTaskPool; +// +// private readonly Scene _scene; +// private readonly int _wheelSize; +// private readonly int _tickDuration; +// private readonly TimeWheel _upperLevelWheel; +// private readonly LinkedList[] _wheel; +// private readonly Queue _tasksToReschedule = new Queue(); +// private readonly Dictionary _taskDictionary = new Dictionary(); +// +// public TimeWheel(TimerComponent timerComponent, int wheelSize, int tickDuration, TimeWheel upperLevelWheel = null) +// { +// _scene = timerComponent.Scene; +// _wheelSize = wheelSize; +// _tickDuration = tickDuration; +// _upperLevelWheel = upperLevelWheel; +// _scheduledTaskPool = timerComponent.ScheduledTaskPool; +// _wheel = new LinkedList[_wheelSize]; +// for (var i = 0; i < wheelSize; i++) +// { +// _wheel[i] = new LinkedList(); +// } +// } +// +// public long Schedule(Action action, int delay) +// { +// var ticks = delay / _tickDuration; +// var futureIndex = ticks + _currentIndex; +// var rounds = futureIndex / _wheelSize; +// var slot = futureIndex % _wheelSize; +// +// if (slot == 0) +// { +// slot = _wheelSize - 1; +// rounds--; +// } +// else +// { +// slot--; +// } +// +// var taskId = _scene.RuntimeIdFactory.Create; +// var task = _scheduledTaskPool.Rent(action, ref rounds, ref slot); +// task.Node = _wheel[slot].AddLast(task); +// _taskDictionary.Add(taskId, task); +// Console.WriteLine($"Schedule rounds:{rounds} slot:{slot} _currentIndex:{_currentIndex}"); +// return taskId; +// } +// +// public bool Remove(int taskId) +// { +// if (!_taskDictionary.TryGetValue(taskId, out var task)) +// { +// return false; +// } +// +// _taskDictionary.Remove(taskId); +// _wheel[task.FinalSlot].Remove(task.Node); +// _scheduledTaskPool.Return(task); +// Console.WriteLine("找到已经删除了任务"); +// return true; +// } +// +// public void Tick(object? state) +// { +// var currentWheel = _wheel[_currentIndex]; +// +// if (currentWheel.Count == 0) +// { +// AdvanceIndex(); +// return; +// } +// +// var currentNode = currentWheel.First; +// +// while (currentNode != null) +// { +// var nextNode = currentNode.Next; +// var task = currentNode.Value; +// +// if (task.Rounds <= 0 && task.FinalSlot == _currentIndex) +// { +// try +// { +// task.Action.Invoke(); +// } +// catch (Exception ex) +// { +// Log.Error($"Exception during task execution: {ex.Message}"); +// } +// } +// else +// { +// task.Rounds--; +// _tasksToReschedule.Enqueue(task); +// } +// +// currentWheel.Remove(currentNode); +// currentNode = nextNode; +// } +// +// RescheduleTasks(); +// AdvanceIndex(); +// } +// +// [MethodImpl(MethodImplOptions.AggressiveInlining)] +// private void AdvanceIndex() +// { +// _currentIndex = (_currentIndex + 1) % _wheelSize; +// if (_currentIndex == 0 && _upperLevelWheel != null) +// { +// _upperLevelWheel.Tick(null); +// } +// } +// +// [MethodImpl(MethodImplOptions.AggressiveInlining)] +// private void RescheduleTasks() +// { +// while (_tasksToReschedule.TryDequeue(out var task)) +// { +// _wheel[task.FinalSlot].AddLast(task); +// } +// } +// } +// } \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Entitas/Component/TimerComponent/TimerAction.cs b/Fantasy/Fantays.Console/Runtime/Core/Entitas/Component/TimerComponent/TimerAction.cs new file mode 100644 index 0000000..2a836ab --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Entitas/Component/TimerComponent/TimerAction.cs @@ -0,0 +1,27 @@ +using System; +using System.Runtime.InteropServices; + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +#pragma warning disable CS8625 +#pragma warning disable CS8618 + +namespace Fantasy.Timer +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct TimerAction + { + public long TimerId; + public long StartTime; + public long TriggerTime; + public readonly object Callback; + public readonly TimerType TimerType; + public TimerAction(long timerId, TimerType timerType, long startTime, long triggerTime, object callback) + { + TimerId = timerId; + Callback = callback; + TimerType = timerType; + StartTime = startTime; + TriggerTime = triggerTime; + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Entitas/Component/TimerComponent/TimerComponent.cs b/Fantasy/Fantays.Console/Runtime/Core/Entitas/Component/TimerComponent/TimerComponent.cs new file mode 100644 index 0000000..ffa7864 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Entitas/Component/TimerComponent/TimerComponent.cs @@ -0,0 +1,52 @@ +// ReSharper disable ForCanBeConvertedToForeach + +using Fantasy.Entitas; +using Fantasy.Entitas.Interface; + +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. +#if FANTASY_UNITY +using UnityEngine; +#endif +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +namespace Fantasy.Timer +{ + public sealed class TimerComponentUpdateSystem : UpdateSystem + { + protected override void Update(TimerComponent self) + { + self.Update(); + } + } + + /// + /// 时间调度组件 + /// + public sealed class TimerComponent : Entity + { + /// + /// 使用系统时间创建的计时器核心。 + /// + public TimerSchedulerNet Net { get; private set; } +#if FANTASY_UNITY + /// + /// 使用 Unity 时间创建的计时器核心。 + /// + public TimerSchedulerNetUnity Unity { get; private set; } +#endif + internal TimerComponent Initialize() + { + Net = new TimerSchedulerNet(Scene); +#if FANTASY_UNITY + Unity = new TimerSchedulerNetUnity(Scene); +#endif + return this; + } + public void Update() + { + Net.Update(); +#if FANTASY_UNITY + Unity.Update(); +#endif + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Entitas/Component/TimerComponent/TimerScheduler/TimerSchedulerNet.cs b/Fantasy/Fantays.Console/Runtime/Core/Entitas/Component/TimerComponent/TimerScheduler/TimerSchedulerNet.cs new file mode 100644 index 0000000..26581f8 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Entitas/Component/TimerComponent/TimerScheduler/TimerSchedulerNet.cs @@ -0,0 +1,390 @@ +using System; +using System.Collections.Generic; +using Fantasy.Async; +using Fantasy.DataStructure.Collection; +using Fantasy.Helper; +// ReSharper disable UnusedParameter.Global + +#pragma warning disable CS8602 // Dereference of a possibly null reference. +#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. + +namespace Fantasy.Timer +{ + /// + /// 基于系统事件的任务调度系统 + /// + public sealed class TimerSchedulerNet + { + private readonly Scene _scene; + private long _idGenerator; + private long _minTime; // 最小时间 + private readonly Queue _timeOutTime = new Queue(); + private readonly Queue _timeOutTimerIds = new Queue(); + private readonly Dictionary _timerActions = new Dictionary(); + private readonly SortedOneToManyList _timeId = new(); // 时间与计时器ID的有序一对多列表 + private long GetId => ++_idGenerator; + /// + /// 构造函数 + /// + /// 当前的Scene + public TimerSchedulerNet(Scene scene) + { + _scene = scene; + } + + private long Now() + { + return TimeHelper.Now; + } + + /// + /// 驱动方法,只有调用这个方法任务系统才会正常运转。 + /// + public void Update() + { + if (_timeId.Count == 0) + { + return; + } + + var currentTime = Now(); + + if (currentTime < _minTime) + { + return; + } + + // 遍历时间ID列表,查找超时的计时器任务 + foreach (var (key, _) in _timeId) + { + if (key > currentTime) + { + _minTime = key; + break; + } + + _timeOutTime.Enqueue(key); + } + + // 处理超时的计时器任务 + while (_timeOutTime.TryDequeue(out var time)) + { + var timerIds = _timeId[time]; + for (var i = 0; i < timerIds.Count; ++i) + { + _timeOutTimerIds.Enqueue(timerIds[i]); + } + + _timeId.Remove(time); + // _timeId.RemoveKey(time); + } + + if (_timeId.Count == 0) + { + _minTime = long.MaxValue; + } + + // 执行超时的计时器任务的回调操作 + while (_timeOutTimerIds.TryDequeue(out var timerId)) + { + if (!_timerActions.Remove(timerId, out var timerAction)) + { + continue; + } + + // 根据计时器类型执行不同的操作 + switch (timerAction.TimerType) + { + case TimerType.OnceWaitTimer: + { + var tcs = (FTask)timerAction.Callback; + tcs.SetResult(true); + break; + } + case TimerType.OnceTimer: + { + if (timerAction.Callback is not Action action) + { + Log.Error($"timerAction {timerAction.ToJson()}"); + break; + } + + action(); + break; + } + case TimerType.RepeatedTimer: + { + if (timerAction.Callback is not Action action) + { + Log.Error($"timerAction {timerAction.ToJson()}"); + break; + } + + timerAction.StartTime = Now(); + AddTimer(ref timerAction); + action(); + break; + } + } + } + } + + private void AddTimer(ref TimerAction timer) + { + var tillTime = timer.StartTime + timer.TriggerTime; + _timeId.Add(tillTime, timer.TimerId); + _timerActions.Add(timer.TimerId, timer); + + if (tillTime < _minTime) + { + _minTime = tillTime; + } + } + + /// + /// 异步等待指定时间。 + /// + /// 等待的时间长度。 + /// 取消令牌。 + /// 等待是否成功。 + public async FTask WaitAsync(long time, FCancellationToken cancellationToken = null) + { + if (time <= 0) + { + return true; + } + + var now = Now(); + var timerId = GetId; + var tcs = FTask.Create(); + var timerAction = new TimerAction(timerId, TimerType.OnceWaitTimer, now, time, tcs); + + void CancelActionVoid() + { + if (Remove(timerId)) + { + tcs.SetResult(false); + } + } + + bool result; + + try + { + cancellationToken?.Add(CancelActionVoid); + AddTimer(ref timerAction); + result = await tcs; + } + finally + { + cancellationToken?.Remove(CancelActionVoid); + } + + return result; + } + + /// + /// 异步等待直到指定时间。 + /// + /// 等待的目标时间。 + /// 取消令牌。 + /// 等待是否成功。 + public async FTask WaitTillAsync(long tillTime, FCancellationToken cancellationToken = null) + { + var now = Now(); + + if (now >= tillTime) + { + return true; + } + + var timerId = GetId; + var tcs = FTask.Create(); + var timerAction = new TimerAction(timerId, TimerType.OnceWaitTimer, now, tillTime - now, tcs); + + void CancelActionVoid() + { + if (Remove(timerId)) + { + tcs.SetResult(false); + } + } + + bool result; + + try + { + cancellationToken?.Add(CancelActionVoid); + AddTimer(ref timerAction); + result = await tcs; + } + finally + { + cancellationToken?.Remove(CancelActionVoid); + } + + return result; + } + + /// + /// 异步等待一帧时间。 + /// + /// 等待是否成功。 + public async FTask WaitFrameAsync() + { +#if FANTASY_NET + await WaitAsync(100); +#else + await WaitAsync(1); +#endif + } + + /// + /// 创建一个只执行一次的计时器,直到指定时间 + /// + /// 计时器执行的目标时间。 + /// 计时器回调方法。 + /// + public long OnceTimer(long time, Action action) + { + var now = Now(); + var timerId = GetId; + var timerAction = new TimerAction(timerId, TimerType.OnceTimer, now, time, action); + AddTimer(ref timerAction); + return timerId; + } + + /// + /// 创建一个只执行一次的计时器,直到指定时间。 + /// + /// 计时器执行的目标时间。 + /// 计时器回调方法。 + /// 计时器的 ID。 + public long OnceTillTimer(long tillTime, Action action) + { + var now = Now(); + + if (tillTime < now) + { + Log.Error($"new once time too small tillTime:{tillTime} Now:{now}"); + } + + var timerId = GetId; + var timerAction = new TimerAction(timerId, TimerType.OnceTimer, now, tillTime - now, action); + AddTimer(ref timerAction); + return timerId; + } + + /// + /// 创建一个只执行一次的计时器,用于发布指定类型的事件。 + /// + /// 事件类型。 + /// 计时器执行的延迟时间。 + /// 事件处理器类型。 + /// 计时器的 ID。 + public long OnceTimer(long time, T timerHandlerType) where T : struct + { + void OnceTimerVoid() + { + _scene.EventComponent.Publish(timerHandlerType); + } + + return OnceTimer(time, OnceTimerVoid); + } + + /// + /// 创建一个只执行一次的计时器,直到指定时间,用于发布指定类型的事件。 + /// + /// 事件类型。 + /// 计时器执行的目标时间。 + /// 事件处理器类型。 + /// 计时器的 ID。 + public long OnceTillTimer(long tillTime, T timerHandlerType) where T : struct + { + void OnceTillTimerVoid() + { + _scene.EventComponent.Publish(timerHandlerType); + } + + return OnceTillTimer(tillTime, OnceTillTimerVoid); + } + + /// + /// 创建一个帧任务 + /// + /// + /// + public long FrameTimer(Action action) + { +#if FANTASY_NET + return RepeatedTimerInner(100, action); +#else + return RepeatedTimerInner(0, action); +#endif + } + + /// + /// 创建一个重复执行的计时器。 + /// + /// 计时器重复间隔的时间。 + /// 计时器回调方法。 + /// 计时器的 ID。 + public long RepeatedTimer(long time, Action action) + { + if (time < 0) + { + Log.Error($"time too small: {time}"); + return 0; + } + + return RepeatedTimerInner(time, action); + } + + /// + /// 创建一个重复执行的计时器,用于发布指定类型的事件。 + /// + /// 事件类型。 + /// 计时器重复间隔的时间。 + /// 事件处理器类型。 + /// 计时器的 ID。 + public long RepeatedTimer(long time, T timerHandlerType) where T : struct + { + void RepeatedTimerVoid() + { + _scene.EventComponent.Publish(timerHandlerType); + } + + return RepeatedTimer(time, RepeatedTimerVoid); + } + + private long RepeatedTimerInner(long time, Action action) + { + var now = Now(); + var timerId = GetId; + var timerAction = new TimerAction(timerId, TimerType.RepeatedTimer, now, time, action); + AddTimer(ref timerAction); + return timerId; + } + + /// + /// 移除指定 ID 的计时器。 + /// + /// + /// + public bool Remove(ref long timerId) + { + var id = timerId; + timerId = 0; + return Remove(id); + } + + /// + /// 移除指定 ID 的计时器。 + /// + /// 计时器的 ID。 + public bool Remove(long timerId) + { + return timerId != 0 && _timerActions.Remove(timerId, out _); + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Entitas/Component/TimerComponent/TimerScheduler/TimerSchedulerNetUnity.cs b/Fantasy/Fantays.Console/Runtime/Core/Entitas/Component/TimerComponent/TimerScheduler/TimerSchedulerNetUnity.cs new file mode 100644 index 0000000..f00bfe4 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Entitas/Component/TimerComponent/TimerScheduler/TimerSchedulerNetUnity.cs @@ -0,0 +1,372 @@ +#if FANTASY_UNITY +using System; +using System.Collections.Generic; +using Fantasy.Async; +using Fantasy.DataStructure.Collection; +using Fantasy.Helper; +using UnityEngine; +namespace Fantasy.Timer +{ + public sealed class TimerSchedulerNetUnity + { + private readonly Scene _scene; + private long _idGenerator; + private long _minTime; // 最小时间 + private readonly Queue _timeOutTime = new Queue(); + private readonly Queue _timeOutTimerIds = new Queue(); + private readonly Dictionary _timerActions = new Dictionary(); + private readonly SortedOneToManyList _timeId = new(); // 时间与计时器ID的有序一对多列表 + private long GetId => ++_idGenerator; + public TimerSchedulerNetUnity(Scene scene) + { + _scene = scene; + } + + private long Now() + { + return (long)(Time.time * 1000); + } + + public void Update() + { + if (_timeId.Count == 0) + { + return; + } + + var currentTime = Now(); + + if (currentTime < _minTime) + { + return; + } + + // 遍历时间ID列表,查找超时的计时器任务 + foreach (var (key, _) in _timeId) + { + if (key > currentTime) + { + _minTime = key; + break; + } + + _timeOutTime.Enqueue(key); + } + + // 处理超时的计时器任务 + while (_timeOutTime.TryDequeue(out var time)) + { + var timerIds = _timeId[time]; + for (var i = 0; i < timerIds.Count; ++i) + { + _timeOutTimerIds.Enqueue(timerIds[i]); + } + + _timeId.Remove(time); + // _timeId.RemoveKey(time); + } + + if (_timeId.Count == 0) + { + _minTime = long.MaxValue; + } + + // 执行超时的计时器任务的回调操作 + while (_timeOutTimerIds.TryDequeue(out var timerId)) + { + if (!_timerActions.Remove(timerId, out var timerAction)) + { + continue; + } + + // 根据计时器类型执行不同的操作 + switch (timerAction.TimerType) + { + case TimerType.OnceWaitTimer: + { + var tcs = (FTask)timerAction.Callback; + tcs.SetResult(true); + break; + } + case TimerType.OnceTimer: + { + if (timerAction.Callback is not Action action) + { + Log.Error($"timerAction {timerAction.ToJson()}"); + break; + } + + action(); + break; + } + case TimerType.RepeatedTimer: + { + if (timerAction.Callback is not Action action) + { + Log.Error($"timerAction {timerAction.ToJson()}"); + break; + } + + timerAction.StartTime = Now(); + AddTimer(ref timerAction); + action(); + break; + } + } + } + } + + private void AddTimer(ref TimerAction timer) + { + var tillTime = timer.StartTime + timer.TriggerTime; + _timeId.Add(tillTime, timer.TimerId); + _timerActions.Add(timer.TimerId, timer); + + if (tillTime < _minTime) + { + _minTime = tillTime; + } + } + + /// + /// 异步等待指定时间。 + /// + /// 等待的时间长度。 + /// 可选的取消令牌。 + /// 等待是否成功。 + public async FTask WaitAsync(long time, FCancellationToken cancellationToken = null) + { + if (time <= 0) + { + return true; + } + + var now = Now(); + var timerId = GetId; + var tcs = FTask.Create(); + var timerAction = new TimerAction(timerId, TimerType.OnceWaitTimer, now, time, tcs); + + + void CancelActionVoid() + { + if (Remove(timerId)) + { + tcs.SetResult(false); + } + } + + bool result; + + try + { + cancellationToken?.Add(CancelActionVoid); + AddTimer(ref timerAction); + result =await tcs; + } + finally + { + cancellationToken?.Remove(CancelActionVoid); + } + + return result; + } + + /// + /// 异步等待直到指定时间。 + /// + /// 等待的目标时间。 + /// 可选的取消令牌。 + /// 等待是否成功。 + public async FTask WaitTillAsync(long tillTime, FCancellationToken cancellationToken = null) + { + var now = Now(); + + if (now >= tillTime) + { + return true; + } + + var timerId = GetId; + var tcs = FTask.Create(); + var timerAction = new TimerAction(timerId, TimerType.OnceWaitTimer, now, tillTime - now, tcs); + + void CancelActionVoid() + { + if (Remove(timerId)) + { + tcs.SetResult(false); + } + } + + bool result; + + try + { + cancellationToken?.Add(CancelActionVoid); + AddTimer(ref timerAction); + result = await tcs; + } + finally + { + cancellationToken?.Remove(CancelActionVoid); + } + + return result; + } + + /// + /// 异步等待一帧时间。 + /// + /// 等待是否成功。 + public async FTask WaitFrameAsync(FCancellationToken cancellationToken = null) + { + await WaitAsync(1, cancellationToken); + } + + /// + /// 创建一个只执行一次的计时器,直到指定时间 + /// + /// 计时器执行的目标时间。 + /// 计时器回调方法。 + /// + public long OnceTimer(long time, Action action) + { + var now = Now(); + var timerId = GetId; + var timerAction = new TimerAction(timerId, TimerType.OnceTimer, now, time, action); + AddTimer(ref timerAction); + return timerId; + } + + /// + /// 创建一个只执行一次的计时器,直到指定时间。 + /// + /// 计时器执行的目标时间。 + /// 计时器回调方法。 + /// 计时器的 ID。 + public long OnceTillTimer(long tillTime, Action action) + { + var now = Now(); + + if (tillTime < now) + { + Log.Error($"new once time too small tillTime:{tillTime} Now:{now}"); + } + + var timerId = GetId; + var timerAction = new TimerAction(timerId, TimerType.OnceTimer, now, tillTime - now, action); + AddTimer(ref timerAction); + return timerId; + } + + /// + /// 创建一个只执行一次的计时器,用于发布指定类型的事件。 + /// + /// 事件类型。 + /// 计时器执行的延迟时间。 + /// 事件处理器类型。 + /// 计时器的 ID。 + public long OnceTimer(long time, T timerHandlerType) where T : struct + { + void OnceTimerVoid() + { + _scene.EventComponent.Publish(timerHandlerType); + } + + return OnceTimer(time, OnceTimerVoid); + } + + /// + /// 创建一个只执行一次的计时器,直到指定时间,用于发布指定类型的事件。 + /// + /// 事件类型。 + /// 计时器执行的目标时间。 + /// 事件处理器类型。 + /// 计时器的 ID。 + public long OnceTillTimer(long tillTime, T timerHandlerType) where T : struct + { + void OnceTillTimerVoid() + { + _scene.EventComponent.Publish(timerHandlerType); + } + + return OnceTillTimer(tillTime, OnceTillTimerVoid); + } + + /// + /// 创建一个帧任务 + /// + /// + /// + public long FrameTimer(Action action) + { + return RepeatedTimerInner(1, action); + } + + /// + /// 创建一个重复执行的计时器。 + /// + /// 计时器重复间隔的时间。 + /// 计时器回调方法。 + /// 计时器的 ID。 + public long RepeatedTimer(long time, Action action) + { + if (time < 0) + { + Log.Error($"time too small: {time}"); + return 0; + } + + return RepeatedTimerInner(time, action); + } + + /// + /// 创建一个重复执行的计时器,用于发布指定类型的事件。 + /// + /// 事件类型。 + /// 计时器重复间隔的时间。 + /// 事件处理器类型。 + /// 计时器的 ID。 + public long RepeatedTimer(long time, T timerHandlerType) where T : struct + { + void RepeatedTimerVoid() + { + _scene.EventComponent.Publish(timerHandlerType); + } + + return RepeatedTimer(time, RepeatedTimerVoid); + } + + private long RepeatedTimerInner(long time, Action action) + { + var now = Now(); + var timerId = GetId; + var timerAction = new TimerAction(timerId, TimerType.RepeatedTimer, now, time, action); + AddTimer(ref timerAction); + return timerId; + } + + /// + /// 移除指定 ID 的计时器。 + /// + /// + /// + public bool Remove(ref long timerId) + { + var id = timerId; + timerId = 0; + return Remove(id); + } + + /// + /// 移除指定 ID 的计时器。 + /// + /// 计时器的 ID。 + public bool Remove(long timerId) + { + return timerId != 0 && _timerActions.Remove(timerId, out _); + } + } +} +#endif + diff --git a/Fantasy/Fantays.Console/Runtime/Core/Entitas/Component/TimerComponent/TimerType.cs b/Fantasy/Fantays.Console/Runtime/Core/Entitas/Component/TimerComponent/TimerType.cs new file mode 100644 index 0000000..6f16f80 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Entitas/Component/TimerComponent/TimerType.cs @@ -0,0 +1,25 @@ +namespace Fantasy.Timer +{ + /// + /// 枚举对象TimerType + /// + public enum TimerType + { + /// + /// None + /// + None, + /// + /// 一次等待定时器 + /// + OnceWaitTimer, + /// + /// 一次性定时器 + /// + OnceTimer, + /// + /// 重复定时器 + /// + RepeatedTimer + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Entitas/Entity.cs b/Fantasy/Fantays.Console/Runtime/Core/Entitas/Entity.cs new file mode 100644 index 0000000..0458cca --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Entitas/Entity.cs @@ -0,0 +1,1051 @@ +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; +using Fantasy.Entitas.Interface; +using Fantasy.Pool; +using MongoDB.Bson.Serialization.Attributes; +using Newtonsoft.Json; +using ProtoBuf; +// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract +// ReSharper disable MergeIntoPattern +// ReSharper disable SuspiciousTypeConversion.Global +// ReSharper disable NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract +// ReSharper disable CheckNamespace +#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. +#pragma warning disable CS8603 // Possible null reference return. +#pragma warning disable CS8602 // Dereference of a possibly null reference. +#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type. +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. + +namespace Fantasy.Entitas +{ + /// + /// 用来表示一个Entity + /// + public interface IEntity : IDisposable, IPool { } + + /// + /// Entity的抽象类,任何Entity必须继承这个接口才可以使用 + /// + public abstract partial class Entity : IEntity + { + #region Members + + /// + /// 获取一个值,表示实体是否支持对象池。 + /// + [BsonIgnore] + [JsonIgnore] + [ProtoIgnore] + [IgnoreDataMember] + private bool _isPool; + /// + /// 实体的Id + /// + [BsonId] + [BsonElement] + [BsonIgnoreIfDefault] + [BsonDefaultValue(0L)] + public long Id { get; protected set; } + /// + /// 实体的RunTimeId,其他系统可以通过这个Id发送Route消息,这个Id也可以理解为RouteId + /// + [BsonIgnore] + [IgnoreDataMember] + [ProtoIgnore] + public long RuntimeId { get; protected set; } + /// + /// 当前实体是否已经被销毁 + /// + [BsonIgnore] + [JsonIgnore] + [IgnoreDataMember] + [ProtoIgnore] + public bool IsDisposed => RuntimeId == 0; + /// + /// 当前实体所归属的Scene + /// + [BsonIgnore] + [JsonIgnore] + [IgnoreDataMember] + [ProtoIgnore] + public Scene Scene { get; protected set; } + /// + /// 实体的父实体 + /// + [BsonIgnore] + [JsonIgnore] + [IgnoreDataMember] + [ProtoIgnore] + public Entity Parent { get; protected set; } + /// + /// 实体的真实Type + /// + [BsonIgnore] + [JsonIgnore] + [IgnoreDataMember] + [ProtoIgnore] + public Type Type { get; protected set; } +#if FANTASY_NET + [BsonElement("t")] [BsonIgnoreIfNull] private EntityList _treeDb; + [BsonElement("m")] [BsonIgnoreIfNull] private EntityList _multiDb; +#endif + [BsonIgnore] [IgnoreDataMember] [ProtoIgnore] private EntitySortedDictionary _tree; + [BsonIgnore] [IgnoreDataMember] [ProtoIgnore] private EntitySortedDictionary _multi; + + /// + /// 获得父Entity + /// + /// 父实体的泛型类型 + /// + public T GetParent() where T : Entity, new() + { + return Parent as T; + } + + /// + /// 获取当前实体的RouteId。 + /// + public long RouteId => RuntimeId; + + #endregion + + #region Create + + /// + /// 创建一个实体 + /// + /// 所属的Scene + /// 实体的Type + /// 是否从对象池创建,如果选择的是,销毁的时候同样会进入对象池 + /// 是否执行实体事件 + /// + public static Entity Create(Scene scene, Type type, bool isPool, bool isRunEvent) + { + return Create(scene, type, scene.EntityIdFactory.Create, isPool, isRunEvent); + } + + /// + /// 创建一个实体 + /// + /// 所属的Scene + /// 实体的Type + /// 指定实体的Id + /// 是否从对象池创建,如果选择的是,销毁的时候同样会进入对象池 + /// 是否执行实体事件 + /// + public static Entity Create(Scene scene, Type type, long id, bool isPool, bool isRunEvent) + { + if (!typeof(Entity).IsAssignableFrom(type)) + { + throw new NotSupportedException($"{type.FullName} Type:{type.FullName} must inherit from Entity"); + } + + Entity entity = null; + + if (isPool) + { + entity = (Entity)scene.EntityPool.Rent(type); + } + else + { + if (!scene.TypeInstance.TryGetValue(type, out var createInstance)) + { + createInstance = CreateInstance.CreateIPool(type); + scene.TypeInstance[type] = createInstance; + } + + entity = (Entity)createInstance(); + } + + entity.Scene = scene; + entity.Type = type; + entity.SetIsPool(isPool); + entity.Id = id; + entity.RuntimeId = scene.RuntimeIdFactory.Create; + scene.AddEntity(entity); + + if (isRunEvent) + { + scene.EntityComponent.Awake(entity); + scene.EntityComponent.StartUpdate(entity); + } + + return entity; + } + + /// + /// 创建一个实体 + /// + /// 所属的Scene + /// 是否从对象池创建,如果选择的是,销毁的时候同样会进入对象池 + /// 是否执行实体事件 + /// 要创建的实体泛型类型 + /// + public static T Create(Scene scene, bool isPool, bool isRunEvent) where T : Entity, new() + { + return Create(scene, scene.EntityIdFactory.Create, isPool, isRunEvent); + } + + /// + /// 创建一个实体 + /// + /// 所属的Scene + /// 指定实体的Id + /// 是否从对象池创建,如果选择的是,销毁的时候同样会进入对象池 + /// 是否执行实体事件 + /// 要创建的实体泛型类型 + /// + public static T Create(Scene scene, long id, bool isPool, bool isRunEvent) where T : Entity, new() + { + var entity = isPool ? scene.EntityPool.Rent() : new T(); + entity.Scene = scene; + entity.Type = typeof(T); + entity.SetIsPool(isPool); + entity.Id = id; + entity.RuntimeId = scene.RuntimeIdFactory.Create; + scene.AddEntity(entity); + + if (isRunEvent) + { + scene.EntityComponent.Awake(entity); + scene.EntityComponent.StartUpdate(entity); + } + + return entity; + } + + #endregion + + #region AddComponent + + /// + /// 添加一个组件到当前实体上 + /// + /// 是否从对象池里创建 + /// 要添加组件的泛型类型 + /// 返回添加到实体上组件的实例 + public T AddComponent(bool isPool = true) where T : Entity, new() + { + var id = SupportedMultiEntityChecker.IsSupported ? Scene.EntityIdFactory.Create : Id; + var entity = Create(Scene, id, isPool, false); + AddComponent(entity); + Scene.EntityComponent.Awake(entity); + Scene.EntityComponent.StartUpdate(entity); + return entity; + } + + /// + /// 添加一个组件到当前实体上 + /// + /// 要添加组件的Id + /// 是否从对象池里创建 + /// 要添加组件的泛型类型 + /// 返回添加到实体上组件的实例 + public T AddComponent(long id, bool isPool = true) where T : Entity, new() + { + var entity = Create(Scene, id, isPool, false); + AddComponent(entity); + Scene.EntityComponent.Awake(entity); + Scene.EntityComponent.StartUpdate(entity); + return entity; + } + + /// + /// 添加一个组件到当前实体上 + /// + /// 要添加的实体实例 + public void AddComponent(Entity component) + { + if (this == component) + { + Log.Error("Cannot add oneself to one's own components"); + return; + } + + if (component.IsDisposed) + { + Log.Error($"component is Disposed {component.Type.FullName}"); + return; + } + + var type = component.Type; + component.Parent?.RemoveComponent(component, false); + + if (component is ISupportedMultiEntity) + { + _multi ??= Scene.EntitySortedDictionaryPool.Rent(); + _multi.Add(component.Id, component); +#if FANTASY_NET + if (component is ISupportedDataBase) + { + _multiDb ??= Scene.EntityListPool.Rent(); + _multiDb.Add(component); + } +#endif + } + else + { +#if FANTASY_NET + if (component is ISupportedSingleCollection && component.Id != Id) + { + Log.Error($"component type :{type.FullName} for implementing ISupportedSingleCollection, it is required that the Id must be the same as the parent"); + } +#endif + var typeHashCode = Scene.EntityComponent.GetHashCode(type);; + + if (_tree == null) + { + _tree = Scene.EntitySortedDictionaryPool.Rent(); + } + else if (_tree.ContainsKey(typeHashCode)) + { + Log.Error($"type:{type.FullName} If you want to add multiple components of the same type, please implement IMultiEntity"); + return; + } + + _tree.Add(typeHashCode, component); +#if FANTASY_NET + if (component is ISupportedDataBase) + { + _treeDb ??= Scene.EntityListPool.Rent(); + _treeDb.Add(component); + } +#endif + } + + component.Parent = this; + component.Scene = Scene; + } + + /// + /// 添加一个组件到当前实体上 + /// + /// 要添加的实体实例 + /// 要添加组件的泛型类型 + public void AddComponent(T component) where T : Entity + { + var type = typeof(T); + + if (type == typeof(Entity)) + { + Log.Error("Cannot add a generic Entity type as a component. Specify a more specific type."); + return; + } + + if (this == component) + { + Log.Error("Cannot add oneself to one's own components"); + return; + } + + if (component.IsDisposed) + { + Log.Error($"component is Disposed {type.FullName}"); + return; + } + + component.Parent?.RemoveComponent(component, false); + + if (SupportedMultiEntityChecker.IsSupported) + { + _multi ??= Scene.EntitySortedDictionaryPool.Rent(); + _multi.Add(component.Id, component); +#if FANTASY_NET + if (SupportedDataBaseChecker.IsSupported) + { + _multiDb ??= Scene.EntityListPool.Rent(); + _multiDb.Add(component); + } +#endif + } + else + { +#if FANTASY_NET + if (SupportedSingleCollectionChecker.IsSupported && component.Id != Id) + { + Log.Error($"component type :{type.FullName} for implementing ISupportedSingleCollection, it is required that the Id must be the same as the parent"); + } +#endif + var typeHashCode = Scene.EntityComponent.GetHashCode(type); + + if (_tree == null) + { + _tree = Scene.EntitySortedDictionaryPool.Rent(); + } + else if (_tree.ContainsKey(typeHashCode)) + { + Log.Error($"type:{type.FullName} If you want to add multiple components of the same type, please implement IMultiEntity"); + return; + } + + _tree.Add(typeHashCode, component); +#if FANTASY_NET + if (SupportedDataBaseChecker.IsSupported) + { + _treeDb ??= Scene.EntityListPool.Rent(); + _treeDb.Add(component); + } +#endif + } + + component.Parent = this; + component.Scene = Scene; + } + + /// + /// 添加一个组件到当前实体上 + /// + /// 组件的类型 + /// 是否在对象池创建 + /// + public Entity AddComponent(Type type, bool isPool = true) + { + var id = typeof(ISupportedMultiEntity).IsAssignableFrom(type) ? Scene.EntityIdFactory.Create : Id; + var entity = Entity.Create(Scene, type, id, isPool, false); + AddComponent(entity); + Scene.EntityComponent.Awake(entity); + Scene.EntityComponent.StartUpdate(entity); + return entity; + } + + #endregion + + #region HasComponent + + /// + /// 当前实体上是否有指定类型的组件 + /// + /// + /// + public bool HasComponent() where T : Entity, new() + { + return HasComponent(typeof(T)); + } + + /// + /// 当前实体上是否有指定类型的组件 + /// + /// + /// + public bool HasComponent(Type type) + { + if (_tree == null) + { + return false; + } + + return _tree.ContainsKey(Scene.EntityComponent.GetHashCode(type)); + } + + /// + /// 当前实体上是否有指定类型的组件 + /// + /// + /// + /// + public bool HasComponent(long id) where T : Entity, ISupportedMultiEntity, new() + { + if (_multi == null) + { + return false; + } + + return _multi.ContainsKey(id); + } + + #endregion + + #region GetComponent + + /// + /// 当前实体上查找一个字实体 + /// + /// 要查找实体泛型类型 + /// 查找的实体实例 + public T GetComponent() where T : Entity, new() + { + if (_tree == null) + { + return null; + } + + var typeHashCode = Scene.EntityComponent.GetHashCode(typeof(T)); + return _tree.TryGetValue(typeHashCode, out var component) ? (T)component : null; + } + + /// + /// 当前实体上查找一个字实体 + /// + /// 要查找实体类型 + /// 查找的实体实例 + public Entity GetComponent(Type type) + { + if (_tree == null) + { + return null; + } + + var typeHashCode = Scene.EntityComponent.GetHashCode(type); + return _tree.TryGetValue(typeHashCode, out var component) ? component : null; + } + + /// + /// 当前实体上查找一个字实体 + /// + /// 要查找实体的Id + /// 要查找实体泛型类型 + /// 查找的实体实例 + public T GetComponent(long id) where T : Entity, ISupportedMultiEntity, new() + { + if (_multi == null) + { + return default; + } + + return _multi.TryGetValue(id, out var entity) ? (T)entity : default; + } + + /// + /// 当前实体上查找一个字实体,如果没有就创建一个新的并添加到当前实体上 + /// + /// 是否从对象池创建 + /// 要查找或添加实体泛型类型 + /// 查找的实体实例 + public T GetOrAddComponent(bool isPool = true) where T : Entity, new() + { + return GetComponent() ?? AddComponent(isPool); + } + + #endregion + + #region RemoveComponent + + /// + /// 当前实体下删除一个实体 + /// + /// 是否执行删除实体的Dispose方法 + /// 实体的泛型类型 + /// + public void RemoveComponent(bool isDispose = true) where T : Entity, new() + { + if (SupportedMultiEntityChecker.IsSupported) + { + throw new NotSupportedException($"{typeof(T).FullName} message:Cannot delete components that implement the ISupportedMultiEntity interface"); + } + + if (_tree == null) + { + return; + } + + var type = typeof(T); + var typeHashCode = Scene.EntityComponent.GetHashCode(type); + if (!_tree.TryGetValue(typeHashCode, out var component)) + { + return; + } +#if FANTASY_NET + if (_treeDb != null && SupportedDataBaseChecker.IsSupported) + { + _treeDb.Remove(component); + + if (_treeDb.Count == 0) + { + Scene.EntityListPool.Return(_treeDb); + _treeDb = null; + } + } +#endif + _tree.Remove(typeHashCode); + + if (_tree.Count == 0) + { + Scene.EntitySortedDictionaryPool.Return(_tree); + _tree = null; + } + + if (isDispose) + { + component.Dispose(); + } + } + + /// + /// 当前实体下删除一个实体 + /// + /// 要删除的实体Id + /// 是否执行删除实体的Dispose方法 + /// 实体的泛型类型 + public void RemoveComponent(long id, bool isDispose = true) where T : Entity, ISupportedMultiEntity, new() + { + if (_multi == null) + { + return; + } + + if (!_multi.TryGetValue(id, out var component)) + { + return; + } +#if FANTASY_NET + if (SupportedDataBaseChecker.IsSupported) + { + _multiDb.Remove(component); + if (_multiDb.Count == 0) + { + Scene.EntityListPool.Return(_multiDb); + _multiDb = null; + } + } +#endif + _multi.Remove(component.Id); + if (_multi.Count == 0) + { + Scene.EntitySortedDictionaryPool.Return(_multi); + _multi = null; + } + + if (isDispose) + { + component.Dispose(); + } + } + + /// + /// 当前实体下删除一个实体 + /// + /// 要删除的实体实例 + /// 是否执行删除实体的Dispose方法 + public void RemoveComponent(Entity component, bool isDispose = true) + { + if (this == component) + { + return; + } + + if (component is ISupportedMultiEntity) + { + if (_multi != null) + { + if (!_multi.ContainsKey(component.Id)) + { + return; + } +#if FANTASY_NET + if (component is ISupportedDataBase) + { + _multiDb.Remove(component); + if (_multiDb.Count == 0) + { + Scene.EntityListPool.Return(_multiDb); + _multiDb = null; + } + } +#endif + _multi.Remove(component.Id); + if (_multi.Count == 0) + { + Scene.EntitySortedDictionaryPool.Return(_multi); + _multi = null; + } + } + } + else if (_tree != null) + { + var typeHashCode = Scene.EntityComponent.GetHashCode(component.Type); + if (!_tree.ContainsKey(typeHashCode)) + { + return; + } +#if FANTASY_NET + if (_treeDb != null && component is ISupportedDataBase) + { + _treeDb.Remove(component); + + if (_treeDb.Count == 0) + { + Scene.EntityListPool.Return(_treeDb); + _treeDb = null; + } + } +#endif + _tree.Remove(typeHashCode); + + if (_tree.Count == 0) + { + Scene.EntitySortedDictionaryPool.Return(_tree); + _tree = null; + } + } + + if (isDispose) + { + component.Dispose(); + } + } + + /// + /// 当前实体下删除一个实体 + /// + /// 要删除的实体实例 + /// 是否执行删除实体的Dispose方法 + /// 实体的泛型类型 + public void RemoveComponent(T component, bool isDispose = true) where T : Entity + { + if (this == component) + { + return; + } + + if (typeof(T) == typeof(Entity)) + { + Log.Error("Cannot remove a generic Entity type as a component. Specify a more specific type."); + return; + } + + if (SupportedMultiEntityChecker.IsSupported) + { + if (_multi != null) + { + if (!_multi.ContainsKey(component.Id)) + { + return; + } +#if FANTASY_NET + if (SupportedDataBaseChecker.IsSupported) + { + _multiDb.Remove(component); + if (_multiDb.Count == 0) + { + Scene.EntityListPool.Return(_multiDb); + _multiDb = null; + } + } +#endif + _multi.Remove(component.Id); + if (_multi.Count == 0) + { + Scene.EntitySortedDictionaryPool.Return(_multi); + _multi = null; + } + } + } + else if (_tree != null) + { + var typeHashCode = Scene.EntityComponent.GetHashCode(typeof(T)); + if (!_tree.ContainsKey(typeHashCode)) + { + return; + } +#if FANTASY_NET + if (_treeDb != null && SupportedDataBaseChecker.IsSupported) + { + _treeDb.Remove(component); + + if (_treeDb.Count == 0) + { + Scene.EntityListPool.Return(_treeDb); + _treeDb = null; + } + } +#endif + _tree.Remove(typeHashCode); + + if (_tree.Count == 0) + { + Scene.EntitySortedDictionaryPool.Return(_tree); + _tree = null; + } + } + + if (isDispose) + { + component.Dispose(); + } + } + + #endregion + + #region Deserialize + + /// + /// 反序列化当前实体,因为在数据库加载过来的或通过协议传送过来的实体并没有跟当前Scene做关联。 + /// 所以必须要执行一下这个反序列化的方法才可以使用。 + /// + /// Scene + /// 是否是重新生成实体的Id,如果是数据库加载过来的一般是不需要的 + public void Deserialize(Scene scene, bool resetId = false) + { + if (RuntimeId != 0) + { + return; + } + + try + { + Scene = scene; + Type ??= GetType(); + RuntimeId = Scene.RuntimeIdFactory.Create; + if (resetId) + { + Id = RuntimeId; + } +#if FANTASY_NET + if (_treeDb != null && _treeDb.Count > 0) + { + _tree = Scene.EntitySortedDictionaryPool.Rent(); + foreach (var entity in _treeDb) + { + entity.Parent = this; + entity.Type = entity.GetType(); + var typeHashCode = Scene.EntityComponent.GetHashCode(entity.Type); + _tree.Add(typeHashCode, entity); + entity.Deserialize(scene, resetId); + } + } + + if (_multiDb != null && _multiDb.Count > 0) + { + _multi = Scene.EntitySortedDictionaryPool.Rent(); + foreach (var entity in _multiDb) + { + entity.Parent = this; + entity.Deserialize(scene, resetId); + _multi.Add(entity.Id, entity); + } + } +#endif + scene.AddEntity(this); + scene.EntityComponent.Deserialize(this); + } + catch (Exception e) + { + if (RuntimeId != 0) + { + scene.RemoveEntity(RuntimeId); + } + + Log.Error(e); + } + } + + #endregion + + #region ForEach +#if FANTASY_NET + /// + /// 查询当前实体下支持数据库分表存储实体 + /// + [BsonIgnore] + [JsonIgnore] + [IgnoreDataMember] + [ProtoIgnore] + public IEnumerable ForEachSingleCollection + { + get + { + foreach (var (_, treeEntity) in _tree) + { + if (treeEntity is not ISupportedSingleCollection) + { + continue; + } + + yield return treeEntity; + } + } + } + /// + /// 查询当前实体下支持传送实体 + /// + [BsonIgnore] + [JsonIgnore] + [IgnoreDataMember] + [ProtoIgnore] + public IEnumerable ForEachTransfer + { + get + { + if (_tree != null) + { + foreach (var (_, treeEntity) in _tree) + { + if (treeEntity is ISupportedTransfer) + { + yield return treeEntity; + } + } + } + + if (_multiDb != null) + { + foreach (var treeEntity in _multiDb) + { + if (treeEntity is not ISupportedTransfer) + { + continue; + } + + yield return treeEntity; + } + } + } + } +#endif + /// + /// 查询当前实体下的实现了ISupportedMultiEntity接口的实体 + /// + [BsonIgnore] + [JsonIgnore] + [IgnoreDataMember] + [ProtoIgnore] + public IEnumerable ForEachMultiEntity + { + get + { + if (_multi == null) + { + yield break; + } + + foreach (var (_, supportedMultiEntity) in _multi) + { + yield return supportedMultiEntity; + } + } + } + /// + /// 查找当前实体下的所有实体,不包括实现ISupportedMultiEntity接口的实体 + /// + [BsonIgnore] + [JsonIgnore] + [IgnoreDataMember] + [ProtoIgnore] + public IEnumerable ForEachEntity + { + get + { + if (_tree == null) + { + yield break; + } + + foreach (var (_, entity) in _tree) + { + yield return entity; + } + } + } + #endregion + + #region Dispose + + /// + /// 销毁当前实体,销毁后会自动销毁当前实体下的所有实体。 + /// + public virtual void Dispose() + { + if (IsDisposed) + { + return; + } + + var scene = Scene; + var runTimeId = RuntimeId; + RuntimeId = 0; + + if (_tree != null) + { + foreach (var (_, entity) in _tree) + { + entity.Dispose(); + } + + _tree.Clear(); + scene.EntitySortedDictionaryPool.Return(_tree); + _tree = null; + } + + if (_multi != null) + { + foreach (var (_, entity) in _multi) + { + entity.Dispose(); + } + + _multi.Clear(); + scene.EntitySortedDictionaryPool.Return(_multi); + _multi = null; + } +#if FANTASY_NET + if (_treeDb != null) + { + foreach (var entity in _treeDb) + { + entity.Dispose(); + } + + _treeDb.Clear(); + scene.EntityListPool.Return(_treeDb); + _treeDb = null; + } + + if (_multiDb != null) + { + foreach (var entity in _multiDb) + { + entity.Dispose(); + } + + _multiDb.Clear(); + scene.EntityListPool.Return(_multiDb); + _multiDb = null; + } +#endif + scene.EntityComponent.Destroy(this); + + if (Parent != null && Parent != this && !Parent.IsDisposed) + { + Parent.RemoveComponent(this, false); + Parent = null; + } + + Id = 0; + Scene = null; + Parent = null; + scene.RemoveEntity(runTimeId); + + if (IsPool()) + { + scene.EntityPool.Return(Type, this); + } + + Type = null; + } + + #endregion + + #region Pool + + /// + /// 获取一个值,该值指示当前实例是否为对象池中的实例。 + /// + /// + public bool IsPool() + { + return _isPool; + } + + /// + /// 设置一个值,该值指示当前实例是否为对象池中的实例。 + /// + /// + public void SetIsPool(bool isPool) + { + _isPool = isPool; + } + + #endregion + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Entitas/EntityPool.cs b/Fantasy/Fantays.Console/Runtime/Core/Entitas/EntityPool.cs new file mode 100644 index 0000000..9a6441c --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Entitas/EntityPool.cs @@ -0,0 +1,66 @@ +using System.Collections.Generic; +using Fantasy.Pool; + +#pragma warning disable CS8714 // The type cannot be used as type parameter in the generic type or method. Nullability of type argument doesn't match 'notnull' constraint. + +namespace Fantasy.Entitas +{ + internal sealed class EntityPool : PoolCore + { + public EntityPool() : base(4096) { } + } + + internal sealed class EntityList : List, IPool where T : Entity + { + private bool _isPool; + /// + /// 获取一个值,该值指示当前实例是否为对象池中的实例。 + /// + /// + public bool IsPool() + { + return _isPool; + } + + /// + /// 设置一个值,该值指示当前实例是否为对象池中的实例。 + /// + /// + public void SetIsPool(bool isPool) + { + _isPool = isPool; + } + } + + internal sealed class EntityListPool : PoolCore> where T : Entity + { + public EntityListPool() : base(4096) { } + } + + internal sealed class EntitySortedDictionary : SortedDictionary, IPool where TN : Entity + { + private bool _isPool; + /// + /// 获取一个值,该值指示当前实例是否为对象池中的实例。 + /// + /// + public bool IsPool() + { + return _isPool; + } + + /// + /// 设置一个值,该值指示当前实例是否为对象池中的实例。 + /// + /// + public void SetIsPool(bool isPool) + { + _isPool = isPool; + } + } + + internal sealed class EntitySortedDictionaryPool : PoolCore> where TN : Entity + { + public EntitySortedDictionaryPool() : base(4096) { } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Entitas/EntityReference.cs b/Fantasy/Fantays.Console/Runtime/Core/Entitas/EntityReference.cs new file mode 100644 index 0000000..dd1a3fd --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Entitas/EntityReference.cs @@ -0,0 +1,59 @@ +// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. +#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. +#pragma warning disable CS8603 // Possible null reference return. +namespace Fantasy.Entitas +{ + /// + /// 实体引用检查组件 + /// + /// + public struct EntityReference where T : Entity + { + private T _entity; + private readonly long _runTimeId; + + private EntityReference(T t) + { + if (t == null) + { + _entity = null; + _runTimeId = 0; + return; + } + + _entity = t; + _runTimeId = t.RuntimeId; + } + + /// + /// 将一个实体转换为EntityReference + /// + /// 实体泛型类型 + /// 返回一个EntityReference + public static implicit operator EntityReference(T t) + { + return new EntityReference(t); + } + + /// + /// 将一个EntityReference转换为实体 + /// + /// 实体泛型类型 + /// 当实体已经被销毁过会返回null + public static implicit operator T(EntityReference v) + { + if (v._entity == null) + { + return null; + } + + if (v._entity.RuntimeId != v._runTimeId) + { + v._entity = null; + } + + return v._entity; + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Entitas/Interface/Supported/ISingleCollectionRoot.cs b/Fantasy/Fantays.Console/Runtime/Core/Entitas/Interface/Supported/ISingleCollectionRoot.cs new file mode 100644 index 0000000..84de7a4 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Entitas/Interface/Supported/ISingleCollectionRoot.cs @@ -0,0 +1,17 @@ +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +namespace Fantasy.Entitas.Interface +{ + /// + /// Entity保存到数据库的时候会根据子组件设置分离存储特性分表存储在不同的集合表中 + /// + public interface ISingleCollectionRoot { } + public static class SingleCollectionRootChecker where T : Entity + { + public static bool IsSupported { get; } + + static SingleCollectionRootChecker() + { + IsSupported = typeof(ISingleCollectionRoot).IsAssignableFrom(typeof(T)); + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Entitas/Interface/Supported/ISupportedDataBase.cs b/Fantasy/Fantays.Console/Runtime/Core/Entitas/Interface/Supported/ISupportedDataBase.cs new file mode 100644 index 0000000..f505241 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Entitas/Interface/Supported/ISupportedDataBase.cs @@ -0,0 +1,19 @@ +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +namespace Fantasy.Entitas.Interface +{ + /// + /// Entity支持数据库 + /// + // ReSharper disable once InconsistentNaming + public interface ISupportedDataBase { } + + public static class SupportedDataBaseChecker where T : Entity + { + public static bool IsSupported { get; } + + static SupportedDataBaseChecker() + { + IsSupported = typeof(ISupportedDataBase).IsAssignableFrom(typeof(T)); + } + } +} diff --git a/Fantasy/Fantays.Console/Runtime/Core/Entitas/Interface/Supported/ISupportedMultiEntity.cs b/Fantasy/Fantays.Console/Runtime/Core/Entitas/Interface/Supported/ISupportedMultiEntity.cs new file mode 100644 index 0000000..ee6aeb7 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Entitas/Interface/Supported/ISupportedMultiEntity.cs @@ -0,0 +1,20 @@ +using System; +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + +namespace Fantasy.Entitas.Interface +{ + /// + /// 支持再一个组件里添加多个同类型组件 + /// + public interface ISupportedMultiEntity : IDisposable { } + + public static class SupportedMultiEntityChecker where T : Entity + { + public static bool IsSupported { get; } + + static SupportedMultiEntityChecker() + { + IsSupported = typeof(ISupportedMultiEntity).IsAssignableFrom(typeof(T)); + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Entitas/Interface/Supported/ISupportedSingleCollection.cs b/Fantasy/Fantays.Console/Runtime/Core/Entitas/Interface/Supported/ISupportedSingleCollection.cs new file mode 100644 index 0000000..302e697 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Entitas/Interface/Supported/ISupportedSingleCollection.cs @@ -0,0 +1,47 @@ +using System; +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +namespace Fantasy.Entitas.Interface +{ + // Entity是单一集合、保存到数据库的时候不会跟随父组件保存在一个集合里、会单独保存在一个集合里 + // 需要配合SingleCollectionAttribute一起使用、如在Entity类头部定义SingleCollectionAttribute(typeOf(Unit)) + // SingleCollectionAttribute用来定义这个Entity是属于哪个Entity的子集 + /// + /// 定义实体支持单一集合存储的接口。当实体需要单独存储在一个集合中,并且在保存到数据库时不会与父组件一起保存在同一个集合中时,应实现此接口。 + /// + public interface ISupportedSingleCollection { } + public static class SupportedSingleCollectionChecker where T : Entity + { + public static bool IsSupported { get; } + + static SupportedSingleCollectionChecker() + { + IsSupported = typeof(ISupportedSingleCollection).IsAssignableFrom(typeof(T)); + } + } + /// + /// 表示用于指定实体的单一集合存储属性。此属性用于配合 接口使用, + /// 用于定义实体属于哪个父实体的子集合,以及在数据库中使用的集合名称。 + /// + [AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = false)] + public class SingleCollectionAttribute : Attribute + { + /// + /// 获取父实体的类型,指示此实体是属于哪个父实体的子集合。 + /// + public readonly Type RootType; + /// + /// 获取在数据库中使用的集合名称。 + /// + public readonly string CollectionName; + /// + /// 初始化 类的新实例,指定父实体类型和集合名称。 + /// + /// 父实体的类型。 + /// 在数据库中使用的集合名称。 + public SingleCollectionAttribute(Type rootType, string collectionName) + { + RootType = rootType; + CollectionName = collectionName; + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Entitas/Interface/Supported/ISupportedTransfer.cs b/Fantasy/Fantays.Console/Runtime/Core/Entitas/Interface/Supported/ISupportedTransfer.cs new file mode 100644 index 0000000..a3ae4a9 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Entitas/Interface/Supported/ISupportedTransfer.cs @@ -0,0 +1,19 @@ +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +#if FANTASY_NET +namespace Fantasy.Entitas.Interface +{ + /// + /// Entity支持传送 + /// + public interface ISupportedTransfer { } + public static class SupportedTransferChecker where T : Entity + { + public static bool IsSupported { get; } + + static SupportedTransferChecker() + { + IsSupported = typeof(ISupportedTransfer).IsAssignableFrom(typeof(T)); + } + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Entitas/Interface/System/IAwakeSystem.cs b/Fantasy/Fantays.Console/Runtime/Core/Entitas/Interface/System/IAwakeSystem.cs new file mode 100644 index 0000000..f651701 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Entitas/Interface/System/IAwakeSystem.cs @@ -0,0 +1,32 @@ +using System; +using Fantasy.Async; + +namespace Fantasy.Entitas.Interface +{ + internal interface IAwakeSystem : IEntitiesSystem { } + /// + /// 实体的Awake事件的抽象接口 + /// + /// 实体的泛型类型 + public abstract class AwakeSystem : IAwakeSystem where T : Entity + { + /// + /// 实体的类型 + /// + /// + public Type EntitiesType() => typeof(T); + /// + /// 事件的抽象方法,需要自己实现这个方法 + /// + /// 触发事件的实体实例 + protected abstract void Awake(T self); + /// + /// 框架内部调用的触发Awake的方法。 + /// + /// 触发事件的实体实例 + public void Invoke(Entity self) + { + Awake((T) self); + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Entitas/Interface/System/ICustomEntitiesSystem.cs b/Fantasy/Fantays.Console/Runtime/Core/Entitas/Interface/System/ICustomEntitiesSystem.cs new file mode 100644 index 0000000..da42e58 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Entitas/Interface/System/ICustomEntitiesSystem.cs @@ -0,0 +1,59 @@ +using System; + +namespace Fantasy.Entitas.Interface +{ + /// + /// 自定义组件事件系统接口 + /// 如果需要自定义组件事件系统,请继承此接口。 + /// 这个接口内部使用。不对外开放。 + /// + internal interface ICustomEntitiesSystem + { + /// + /// 事件类型 + /// 用于触发这个组件事件关键因素。 + /// + int CustomEventType { get; } + /// + /// 实体的类型 + /// + /// + Type EntitiesType(); + /// + /// 框架内部调用的触发事件方法 + /// + /// + void Invoke(Entity entity); + } + + /// + /// 自定义组件事件系统抽象类 + /// 如果需要自定义组件事件系统,请继承此抽象类。 + /// + /// + public abstract class CustomSystem : ICustomEntitiesSystem where T : Entity + { + /// + /// 这个1表示是一个自定义事件类型,执行这个事件是时候需要用到这个1. + /// + public abstract int CustomEventType { get; } + /// + /// 事件的抽象方法,需要自己实现这个方法 + /// + /// 触发事件的实体实例 + protected abstract void Custom(T self); + /// + /// 实体的类型 + /// + /// + public abstract Type EntitiesType(); + /// + /// 框架内部调用的触发Awake的方法。 + /// + /// 触发事件的实体实例 + public void Invoke(Entity self) + { + Custom((T) self); + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Entitas/Interface/System/IDeserializeSystem.cs b/Fantasy/Fantays.Console/Runtime/Core/Entitas/Interface/System/IDeserializeSystem.cs new file mode 100644 index 0000000..9c38ab6 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Entitas/Interface/System/IDeserializeSystem.cs @@ -0,0 +1,32 @@ +using System; +using Fantasy.Async; + +namespace Fantasy.Entitas.Interface +{ + internal interface IDeserializeSystem : IEntitiesSystem { } + /// + /// 实体的反序列化事件的抽象接口 + /// + /// 实体的泛型数据 + public abstract class DeserializeSystem : IDeserializeSystem where T : Entity + { + /// + /// 实体的类型 + /// + /// + public Type EntitiesType() => typeof(T); + /// + /// 事件的抽象方法,需要自己实现这个方法 + /// + /// 触发事件的实体实例 + protected abstract void Deserialize(T self); + /// + /// 框架内部调用的触发Deserialize的方法 + /// + /// 触发事件的实体实例 + public void Invoke(Entity self) + { + Deserialize((T) self); + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Entitas/Interface/System/IDestroySystem.cs b/Fantasy/Fantays.Console/Runtime/Core/Entitas/Interface/System/IDestroySystem.cs new file mode 100644 index 0000000..531ebbe --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Entitas/Interface/System/IDestroySystem.cs @@ -0,0 +1,32 @@ +using System; +using Fantasy.Async; + +namespace Fantasy.Entitas.Interface +{ + internal interface IDestroySystem : IEntitiesSystem { } + /// + /// 实体销毁事件的抽象接口 + /// + /// + public abstract class DestroySystem : IDestroySystem where T : Entity + { + /// + /// 实体的类型 + /// + /// + public Type EntitiesType() => typeof(T); + /// + /// 事件的抽象方法,需要自己实现这个方法 + /// + /// 触发事件的实体实例 + protected abstract void Destroy(T self); + /// + /// 框架内部调用的触发Destroy的方法 + /// + /// + public void Invoke(Entity self) + { + Destroy((T) self); + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Entitas/Interface/System/IEntitiesSystem.cs b/Fantasy/Fantays.Console/Runtime/Core/Entitas/Interface/System/IEntitiesSystem.cs new file mode 100644 index 0000000..555d21a --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Entitas/Interface/System/IEntitiesSystem.cs @@ -0,0 +1,22 @@ +using System; +using Fantasy.Async; + +namespace Fantasy.Entitas.Interface +{ + /// + /// ECS事件系统的核心接口,任何事件都是要继承这个接口 + /// + public interface IEntitiesSystem + { + /// + /// 实体的类型 + /// + /// + Type EntitiesType(); + /// + /// 框架内部调用的触发事件方法 + /// + /// + void Invoke(Entity entity); + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Entitas/Interface/System/IFrameUpdateSystem.cs b/Fantasy/Fantays.Console/Runtime/Core/Entitas/Interface/System/IFrameUpdateSystem.cs new file mode 100644 index 0000000..c50061b --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Entitas/Interface/System/IFrameUpdateSystem.cs @@ -0,0 +1,31 @@ +using System; + +namespace Fantasy.Entitas.Interface +{ + internal interface IFrameUpdateSystem : IEntitiesSystem { } + /// + /// 帧更新时间的抽象接口 + /// + /// + public abstract class FrameUpdateSystem : IFrameUpdateSystem where T : Entity + { + /// + /// 实体的类型 + /// + /// + public Type EntitiesType() => typeof(T); + /// + /// 事件的抽象方法,需要自己实现这个方法 + /// + /// 触发事件的实体实例 + protected abstract void FrameUpdate(T self); + /// + /// 框架内部调用的触发FrameUpdate的方法 + /// + /// + public void Invoke(Entity self) + { + FrameUpdate((T) self); + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Entitas/Interface/System/IUpdateSystem.cs b/Fantasy/Fantays.Console/Runtime/Core/Entitas/Interface/System/IUpdateSystem.cs new file mode 100644 index 0000000..4b34ac8 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Entitas/Interface/System/IUpdateSystem.cs @@ -0,0 +1,31 @@ +using System; + +namespace Fantasy.Entitas.Interface +{ + internal interface IUpdateSystem : IEntitiesSystem { } + /// + /// Update事件的抽象接口 + /// + /// + public abstract class UpdateSystem : IUpdateSystem where T : Entity + { + /// + /// 实体的类型 + /// + /// + public Type EntitiesType() => typeof(T); + /// + /// 事件的抽象方法,需要自己实现这个方法 + /// + /// 触发事件的实体实例 + protected abstract void Update(T self); + /// + /// 框架内部调用的触发Update的方法 + /// + /// 触发事件的实体实例 + public void Invoke(Entity self) + { + Update((T) self); + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/FTask/Builder/AsyncFTaskCompletedMethodBuilder.cs b/Fantasy/Fantays.Console/Runtime/Core/FTask/Builder/AsyncFTaskCompletedMethodBuilder.cs new file mode 100644 index 0000000..60b0d08 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/FTask/Builder/AsyncFTaskCompletedMethodBuilder.cs @@ -0,0 +1,52 @@ +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.ExceptionServices; +using System.Runtime.InteropServices; +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + +namespace Fantasy.Async +{ + [StructLayout(LayoutKind.Auto)] + public struct AsyncFTaskCompletedMethodBuilder + { + public FTaskCompleted Task => default; + + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static AsyncFTaskCompletedMethodBuilder Create() + { + return new AsyncFTaskCompletedMethodBuilder(); + } + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Start(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine + { + stateMachine.MoveNext(); + } + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetException(Exception exception) + { + ExceptionDispatchInfo.Capture(exception).Throw(); + } + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetResult() { } + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void AwaitOnCompleted(ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : INotifyCompletion where TStateMachine : IAsyncStateMachine + { + awaiter.OnCompleted(stateMachine.MoveNext); + } + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void AwaitUnsafeOnCompleted(ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : ICriticalNotifyCompletion where TStateMachine : IAsyncStateMachine + { + awaiter.UnsafeOnCompleted(stateMachine.MoveNext); + } + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetStateMachine(IAsyncStateMachine stateMachine) { } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/FTask/Builder/AsyncFTaskMethodBuilder.cs b/Fantasy/Fantays.Console/Runtime/Core/FTask/Builder/AsyncFTaskMethodBuilder.cs new file mode 100644 index 0000000..6c072cd --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/FTask/Builder/AsyncFTaskMethodBuilder.cs @@ -0,0 +1,140 @@ +using System; +using System.Collections.Concurrent; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +// ReSharper disable MemberCanBePrivate.Global +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +#pragma warning disable CS8604 // Possible null reference argument. + +namespace Fantasy.Async +{ + [StructLayout(LayoutKind.Auto)] + public readonly struct AsyncFTaskMethodBuilder + { + public FTask Task + { + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; + } + + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static AsyncFTaskMethodBuilder Create() + { + return new AsyncFTaskMethodBuilder(FTask.Create()); + } + + public AsyncFTaskMethodBuilder(FTask fTask) + { + Task = fTask; + } + + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Start(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine + { + stateMachine.MoveNext(); + } + + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetResult() + { + Task.SetResult(); + } + + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetException(Exception exception) + { + Task.SetException(exception); + } + + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void AwaitOnCompleted(ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : INotifyCompletion where TStateMachine : IAsyncStateMachine + { + // 通常在异步方法中遇到 await 关键字时调用,并且需要将执行恢复到调用 await 之前的同步上下文。 + awaiter.OnCompleted(stateMachine.MoveNext); + } + + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void AwaitUnsafeOnCompleted(ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : ICriticalNotifyCompletion where TStateMachine : IAsyncStateMachine + { + // 通常在你不需要恢复到原始同步上下文时调用,这意味着你不关心在什么线程上恢复执行。 + awaiter.UnsafeOnCompleted(stateMachine.MoveNext); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetStateMachine(IAsyncStateMachine stateMachine) + { + // 用于设置和保存异步方法的状态机实例。 + // 编译器在生成异步方法时要求其存在。 + // 编译器生成的代码已经足够处理状态机的管理,所以这里没有什么特殊要求所以保持空实现。 + } + } + + [StructLayout(LayoutKind.Auto)] + public readonly struct AsyncFTaskMethodBuilder + { + public FTask Task + { + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; + } + + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static AsyncFTaskMethodBuilder Create() + { + return new AsyncFTaskMethodBuilder(FTask.Create()); + } + + public AsyncFTaskMethodBuilder(FTask fTask) + { + Task = fTask; + } + + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Start(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine + { + stateMachine.MoveNext(); + } + + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetResult(T value) + { + Task.SetResult(value); + } + + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetException(Exception exception) + { + Task.SetException(exception); + } + + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void AwaitOnCompleted(ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : INotifyCompletion where TStateMachine : IAsyncStateMachine + { + awaiter.OnCompleted(stateMachine.MoveNext); + } + + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void AwaitUnsafeOnCompleted(ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : ICriticalNotifyCompletion where TStateMachine : IAsyncStateMachine + { + awaiter.UnsafeOnCompleted(stateMachine.MoveNext); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetStateMachine(IAsyncStateMachine stateMachine) { } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/FTask/Builder/AsyncFVoidMethodBuilder.cs b/Fantasy/Fantays.Console/Runtime/Core/FTask/Builder/AsyncFVoidMethodBuilder.cs new file mode 100644 index 0000000..b9f7bc0 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/FTask/Builder/AsyncFVoidMethodBuilder.cs @@ -0,0 +1,62 @@ +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.ExceptionServices; +using System.Runtime.InteropServices; +#pragma warning disable CS8603 // Possible null reference return. + +namespace Fantasy.Async +{ + [StructLayout(LayoutKind.Auto)] + internal struct AsyncFVoidMethodBuilder + { + public FVoid Task + { + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => default; + } + + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static AsyncFVoidMethodBuilder Create() + { + return default; + } + + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Start(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine + { + stateMachine.MoveNext(); + } + + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetResult() { } + + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetException(Exception exception) + { + ExceptionDispatchInfo.Capture(exception).Throw(); + } + + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void AwaitOnCompleted(ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : INotifyCompletion where TStateMachine : IAsyncStateMachine + { + awaiter.OnCompleted(stateMachine.MoveNext); + } + + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void AwaitUnsafeOnCompleted(ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : ICriticalNotifyCompletion where TStateMachine : IAsyncStateMachine + { + awaiter.UnsafeOnCompleted(stateMachine.MoveNext); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetStateMachine(IAsyncStateMachine stateMachine) { } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/FTask/FCancellationToken/FCancellationToken.cs b/Fantasy/Fantays.Console/Runtime/Core/FTask/FCancellationToken/FCancellationToken.cs new file mode 100644 index 0000000..90913e3 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/FTask/FCancellationToken/FCancellationToken.cs @@ -0,0 +1,121 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; + +namespace Fantasy.Async +{ + /// + /// 用于FTask取消的CancellationToken + /// + public sealed class FCancellationToken : IDisposable + { + private bool _isDispose; + private bool _isCancel; + private readonly HashSet _actions = new HashSet(); + /// + /// 当前CancellationToken是否已经取消过了 + /// + public bool IsCancel => _isDispose || _isCancel; + /// + /// 添加一个取消要执行的Action + /// + /// + public void Add(Action action) + { + if (_isDispose) + { + return; + } + + _actions.Add(action); + } + /// + /// 移除一个取消要执行的Action + /// + /// + public void Remove(Action action) + { + if (_isDispose) + { + return; + } + + _actions.Remove(action); + } + /// + /// 取消CancellationToken + /// + public void Cancel() + { + if (IsCancel) + { + return; + } + + _isCancel = true; + + foreach (var action in _actions) + { + try + { + action.Invoke(); + } + catch (Exception e) + { + Console.WriteLine(e); + } + } + + _actions.Clear(); + } + /// + /// 销毁掉CancellationToken,会执行Cancel方法。 + /// + public void Dispose() + { + if (_isDispose) + { + return; + } + + if (!IsCancel) + { + Cancel(); + _isCancel = true; + } + + _isDispose = true; + + if (Caches.Count > 2000) + { + return; + } + + Caches.Enqueue(this); + } + + #region Static + + private static readonly ConcurrentQueue Caches = new ConcurrentQueue(); + + /// + /// 获取一个新的CancellationToken + /// + public static FCancellationToken ToKen + { + get + { + if (!Caches.TryDequeue(out var fCancellationToken)) + { + fCancellationToken = new FCancellationToken(); + } + + fCancellationToken._isCancel = false; + fCancellationToken._isDispose = false; + return fCancellationToken; + } + } + + #endregion + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/FTask/FTask.Extension/FTask.Factory.cs b/Fantasy/Fantays.Console/Runtime/Core/FTask/FTask.Extension/FTask.Factory.cs new file mode 100644 index 0000000..80c7fc8 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/FTask/FTask.Extension/FTask.Factory.cs @@ -0,0 +1,115 @@ +#if !FANTASY_WEBGL +using System.Collections.Concurrent; +#endif +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.CompilerServices; +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. +#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. + +namespace Fantasy.Async +{ + /// + /// 一个异步任务 + /// + public partial class FTask + { + private bool _isPool; +#if FANTASY_WEBGL + private static readonly Queue Caches = new Queue(); +#else + private static readonly ConcurrentQueue Caches = new ConcurrentQueue(); +#endif + /// + /// 创建一个空的任务 + /// + public static FTaskCompleted CompletedTask => new FTaskCompleted(); + + private FTask() { } + + /// + /// 创建一个任务 + /// + /// 是否从对象池中创建 + /// + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static FTask Create(bool isPool = true) + { + if (!isPool) + { + return new FTask(); + } + + if (!Caches.TryDequeue(out var fTask)) + { + fTask = new FTask(); + } + + fTask._isPool = true; + return fTask; + } + + private void Return() + { + if (!_isPool || Caches.Count > 2000) + { + return; + } + + _callBack = null; + _status = STaskStatus.Pending; + Caches.Enqueue(this); + } + } + + /// + /// 一个异步任务 + /// + /// 任务的泛型类型 + public partial class FTask + { + private bool _isPool; +#if FANTASY_WEBGL + private static readonly Queue> Caches = new Queue>(); +#else + private static readonly ConcurrentQueue> Caches = new ConcurrentQueue>(); +#endif + /// + /// 创建一个任务 + /// + /// 是否从对象池中创建 + /// + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static FTask Create(bool isPool = true) + { + if (!isPool) + { + return new FTask(); + } + + if (!Caches.TryDequeue(out var fTask)) + { + fTask = new FTask(); + } + + fTask._isPool = true; + return fTask; + } + + private FTask() { } + + private void Return() + { + if (!_isPool || Caches.Count > 2000) + { + return; + } + + _callBack = null; + _status = STaskStatus.Pending; + Caches.Enqueue(this); + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/FTask/FTask.Extension/FTask.Tools.cs b/Fantasy/Fantays.Console/Runtime/Core/FTask/FTask.Extension/FTask.Tools.cs new file mode 100644 index 0000000..a42edec --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/FTask/FTask.Extension/FTask.Tools.cs @@ -0,0 +1,345 @@ +// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract + +using System; +using System.Collections.Generic; +#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. +namespace Fantasy.Async +{ + public partial class FTask + { + #region NetTimer + + /// + /// 异步等待指定时间 + /// + /// + /// + /// + /// + public static FTask Wait(Scene scene, long time, FCancellationToken cancellationToken = null) + { + return scene.TimerComponent.Net.WaitAsync(time, cancellationToken); + } + + /// + /// 异步等待直到指定时间 + /// + /// + /// + /// + /// + public static FTask WaitTill(Scene scene, long time, FCancellationToken cancellationToken = null) + { + return scene.TimerComponent.Net.WaitTillAsync(time, cancellationToken); + } + + /// + /// 异步等待一帧时间 + /// + /// + /// + public static FTask WaitFrame(Scene scene) + { + return scene.TimerComponent.Net.WaitFrameAsync(); + } + + /// + /// 创建一个只执行一次的计时器,直到指定时间 + /// + /// + /// + /// + /// + public static long OnceTimer(Scene scene, long time, Action action) + { + return scene.TimerComponent.Net.OnceTimer(time, action); + } + + /// + /// 创建一个只执行一次的计时器,直到指定时间。 + /// + /// + /// + /// + /// + public static long OnceTillTimer(Scene scene, long time, Action action) + { + return scene.TimerComponent.Net.OnceTillTimer(time, action); + } + + /// + /// 创建一个只执行一次的计时器,用于发布指定类型的事件。 + /// + /// + /// + /// + /// + /// + public static long OnceTimer(Scene scene, long time, T timerHandlerType) where T : struct + { + return scene.TimerComponent.Net.OnceTimer(time, timerHandlerType); + } + + /// + /// 创建一个只执行一次的计时器,直到指定时间,用于发布指定类型的事件。 + /// + /// + /// + /// + /// + /// + public static long OnceTillTimer(Scene scene, long tillTime, T timerHandlerType) where T : struct + { + return scene.TimerComponent.Net.OnceTillTimer(tillTime, timerHandlerType); + } + + /// + /// 创建一个重复执行的计时器。 + /// + /// + /// + /// + /// + public static long RepeatedTimer(Scene scene, long time, Action action) + { + return scene.TimerComponent.Net.RepeatedTimer(time, action); + } + + /// + /// 创建一个重复执行的计时器,用于发布指定类型的事件。 + /// + /// + /// + /// + /// + /// + public static long RepeatedTimer(Scene scene, long time, T timerHandlerType) where T : struct + { + return scene.TimerComponent.Net.RepeatedTimer(time, timerHandlerType); + } + + /// + /// 移除指定 ID 的计时器。 + /// + /// + /// + /// + public static bool RemoveTimer(Scene scene, ref long timerId) + { + return scene.TimerComponent.Net.Remove(ref timerId); + } + + #endregion + + #region Unity + +#if FANTASY_UNITY + /// + /// 异步等待指定时间。(使用Unity的Time时间) + /// + /// + /// + /// + /// + public static FTask UnityWait(Scene scene, long time, FCancellationToken cancellationToken = null) + { + return scene.TimerComponent.Unity.WaitAsync(time, cancellationToken); + } + + /// + /// 异步等待直到指定时间。(使用Unity的Time时间) + /// + /// + /// + /// + /// + public static FTask UnityWaitTill(Scene scene, long time, FCancellationToken cancellationToken = null) + { + return scene.TimerComponent.Unity.WaitTillAsync(time, cancellationToken); + } + + /// + /// 异步等待一帧时间。(使用Unity的Time时间) + /// + /// + /// + public static FTask UnityWaitFrame(Scene scene) + { + return scene.TimerComponent.Unity.WaitFrameAsync(); + } + + /// + /// 创建一个只执行一次的计时器,直到指定时间。(使用Unity的Time时间) + /// + /// + /// + /// + /// + public static long UnityOnceTimer(Scene scene, long time, Action action) + { + return scene.TimerComponent.Unity.OnceTimer(time, action); + } + + /// + /// 创建一个只执行一次的计时器,直到指定时间。(使用Unity的Time时间) + /// + /// + /// + /// + /// + public static long UnityOnceTillTimer(Scene scene, long time, Action action) + { + return scene.TimerComponent.Unity.OnceTillTimer(time, action); + } + + /// + /// 创建一个只执行一次的计时器,用于发布指定类型的事件。(使用Unity的Time时间) + /// + /// + /// + /// + /// + /// + public static long UnityOnceTimer(Scene scene, long time, T timerHandlerType) where T : struct + { + return scene.TimerComponent.Unity.OnceTimer(time, timerHandlerType); + } + + /// + /// 创建一个只执行一次的计时器,直到指定时间,用于发布指定类型的事件。(使用Unity的Time时间) + /// + /// + /// + /// + /// + /// + public static long UnityOnceTillTimer(Scene scene, long tillTime, T timerHandlerType) where T : struct + { + return scene.TimerComponent.Unity.OnceTillTimer(tillTime, timerHandlerType); + } + + /// + /// 创建一个重复执行的计时器。(使用Unity的Time时间) + /// + /// + /// + /// + /// + public static long UnityRepeatedTimer(Scene scene, long time, Action action) + { + return scene.TimerComponent.Unity.RepeatedTimer(time, action); + } + + /// + /// 创建一个重复执行的计时器,用于发布指定类型的事件。(使用Unity的Time时间) + /// + /// + /// + /// + /// + /// + public static long UnityRepeatedTimer(Scene scene, long time, T timerHandlerType) where T : struct + { + return scene.TimerComponent.Unity.RepeatedTimer(time, timerHandlerType); + } + + /// + /// 移除指定 ID 的计时器。(使用Unity的Time时间) + /// + /// + /// + /// + public static bool UnityRemoveTimer(Scene scene, ref long timerId) + { + return scene.TimerComponent.Unity.Remove(ref timerId); + } +#endif + + #endregion + + /// + /// 创建并运行一个异步任务 + /// + /// + /// + public static FTask Run(Func factory) + { + return factory(); + } + + /// + /// 创建并运行一个带有结果的异步任务 + /// + /// + /// + /// + public static FTask Run(Func> factory) + { + return factory(); + } + + /// + /// 等待所有任务完成 + /// + /// + public static async FTask WaitAll(List tasks) + { + if (tasks.Count <= 0) + { + return; + } + + var count = tasks.Count; + var sTaskCompletionSource = Create(); + + foreach (var task in tasks) + { + RunSTask(task).Coroutine(); + } + + await sTaskCompletionSource; + + async FVoid RunSTask(FTask task) + { + await task; + count--; + if (count <= 0) + { + sTaskCompletionSource.SetResult(); + } + } + } + /// + /// 等待其中一个任务完成 + /// + /// + public static async FTask WaitAny(List tasks) + { + if (tasks.Count <= 0) + { + return; + } + + var count = 1; + var sTaskCompletionSource = Create(); + + foreach (var task in tasks) + { + RunSTask(task).Coroutine(); + } + + await sTaskCompletionSource; + + async FVoid RunSTask(FTask task) + { + await task; + count--; + if (count == 0) + { + sTaskCompletionSource.SetResult(); + } + } + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/FTask/Task/FTask.cs b/Fantasy/Fantays.Console/Runtime/Core/FTask/Task/FTask.cs new file mode 100644 index 0000000..ce925f2 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/FTask/Task/FTask.cs @@ -0,0 +1,263 @@ +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.ExceptionServices; +// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract +// ReSharper disable ConditionalAccessQualifierIsNonNullableAccordingToAPIContract +// ReSharper disable CheckNamespace +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +#pragma warning disable CS8602 // Dereference of a possibly null reference. +#pragma warning disable CS8603 // Possible null reference return. +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. +#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. + +namespace Fantasy.Async +{ + public enum STaskStatus : byte + { + Pending = 0, // The operation has not yet completed. + Succeeded = 1, // The operation completed successfully. + Faulted = 2 // The operation completed with an error. + } + + [AsyncMethodBuilder(typeof(AsyncFTaskMethodBuilder))] + public sealed partial class FTask : ICriticalNotifyCompletion + { + private Action _callBack; + private ExceptionDispatchInfo _exception; + private STaskStatus _status = STaskStatus.Pending; + public bool IsCompleted + { + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _status != STaskStatus.Pending; + } + + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public FTask GetAwaiter() => this; + + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private async FVoid InnerCoroutine() + { + await this; + } + + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Coroutine() + { + InnerCoroutine().Coroutine(); + } + + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void GetResult() + { + switch (_status) + { + case STaskStatus.Succeeded: + { + Return(); + return; + } + case STaskStatus.Faulted: + { + Return(); + + if (_exception == null) + { + return; + } + + var exception = _exception; + _exception = null; + exception.Throw(); + return; + } + default: + { + throw new NotSupportedException("Direct call to getResult is not allowed"); + } + } + } + + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetResult() + { + if (_status != STaskStatus.Pending) + { + throw new InvalidOperationException("The task has been completed"); + } + + _status = STaskStatus.Succeeded; + + if (_callBack == null) + { + return; + } + + var callBack = _callBack; + _callBack = null; + callBack.Invoke(); + } + + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void OnCompleted(Action action) + { + UnsafeOnCompleted(action); + } + + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void UnsafeOnCompleted(Action action) + { + if (_status != STaskStatus.Pending) + { + action?.Invoke(); + return; + } + + _callBack = action; + } + + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetException(Exception exception) + { + if (_status != STaskStatus.Pending) + { + throw new InvalidOperationException("The task has been completed"); + } + + _status = STaskStatus.Faulted; + _exception = ExceptionDispatchInfo.Capture(exception); + _callBack?.Invoke(); + } + } + + [AsyncMethodBuilder(typeof(AsyncFTaskMethodBuilder<>))] + public sealed partial class FTask : ICriticalNotifyCompletion + { + private T _value; + private Action _callBack; + private ExceptionDispatchInfo _exception; + private STaskStatus _status = STaskStatus.Pending; + public bool IsCompleted + { + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _status != STaskStatus.Pending; + } + + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public FTask GetAwaiter() => this; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [DebuggerHidden] + private async FVoid InnerCoroutine() + { + await this; + } + + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Coroutine() + { + InnerCoroutine().Coroutine(); + } + + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public T GetResult() + { + switch (_status) + { + case STaskStatus.Succeeded: + { + var value = _value; + Return(); + return value; + } + case STaskStatus.Faulted: + { + Return(); + + if (_exception == null) + { + return default; + } + + var exception = _exception; + _exception = null; + exception.Throw(); + return default; + } + default: + { + throw new NotSupportedException("Direct call to getResult is not allowed"); + } + } + } + + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetResult(T value) + { + if (_status != STaskStatus.Pending) + { + throw new InvalidOperationException("The task has been completed"); + } + + _value = value; + _status = STaskStatus.Succeeded; + + if (_callBack == null) + { + return; + } + + var callBack = _callBack; + _callBack = null; + callBack.Invoke(); + } + + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void OnCompleted(Action action) + { + UnsafeOnCompleted(action); + } + + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void UnsafeOnCompleted(Action action) + { + if (_status != STaskStatus.Pending) + { + action?.Invoke(); + return; + } + + _callBack = action; + } + + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetException(Exception exception) + { + if (_status != STaskStatus.Pending) + { + throw new InvalidOperationException("The task has been completed"); + } + + _status = STaskStatus.Faulted; + _exception = ExceptionDispatchInfo.Capture(exception); + _callBack?.Invoke(); + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/FTask/Task/FTaskCompleted.cs b/Fantasy/Fantays.Console/Runtime/Core/FTask/Task/FTaskCompleted.cs new file mode 100644 index 0000000..48b9e36 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/FTask/Task/FTaskCompleted.cs @@ -0,0 +1,31 @@ +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + +namespace Fantasy.Async +{ + [StructLayout(LayoutKind.Auto)] + [AsyncMethodBuilder(typeof(AsyncFTaskCompletedMethodBuilder))] + public struct FTaskCompleted : ICriticalNotifyCompletion + { + [DebuggerHidden] + public bool IsCompleted => true; + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public FTaskCompleted GetAwaiter() + { + return this; + } + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void GetResult() { } + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void OnCompleted(Action continuation) { } + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void UnsafeOnCompleted(Action continuation) { } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/FTask/Task/FVoid.cs b/Fantasy/Fantays.Console/Runtime/Core/FTask/Task/FVoid.cs new file mode 100644 index 0000000..f10ca7c --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/FTask/Task/FVoid.cs @@ -0,0 +1,29 @@ +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Fantasy.Async +{ + [StructLayout(LayoutKind.Auto)] + [AsyncMethodBuilder(typeof(AsyncFVoidMethodBuilder))] + internal struct FVoid : ICriticalNotifyCompletion + { + public bool IsCompleted + { + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => true; + } + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Coroutine() { } + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void OnCompleted(Action continuation) { } + + [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void UnsafeOnCompleted(Action continuation) { } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Helper/ByteHelper.cs b/Fantasy/Fantays.Console/Runtime/Core/Helper/ByteHelper.cs new file mode 100644 index 0000000..9d3d6e0 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Helper/ByteHelper.cs @@ -0,0 +1,388 @@ +using System; +using System.Buffers; +using System.IO; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; +using Fantasy.Async; + +namespace Fantasy.Helper +{ + /// + /// 提供字节操作辅助方法的静态类。 + /// + public static class ByteHelper + { + private static readonly string[] Suffix = { "Byte", "KB", "MB", "GB", "TB" }; + + /// + /// 从指定的文件流中读取一个 64 位整数。 + /// + public static long ReadInt64(FileStream stream) + { + var buffer = new byte[8]; +#if FANTASY_NET + stream.ReadExactly(buffer, 0, 8); +#else + stream.Read(buffer, 0, 8); +#endif + return BitConverter.ToInt64(buffer, 0); + } + + /// + /// 从指定的文件流中读取一个 32 位整数。 + /// + public static int ReadInt32(FileStream stream) + { + var buffer = new byte[4]; +#if FANTASY_NET + stream.ReadExactly(buffer, 0, 4); +#else + stream.Read(buffer, 0, 4); +#endif + return BitConverter.ToInt32(buffer, 0); + } + + /// + /// 从指定的内存流中读取一个 64 位整数。 + /// + public static long ReadInt64(MemoryStream stream) + { + var buffer = new byte[8]; +#if FANTASY_NET + stream.ReadExactly(buffer, 0, 8); +#else + stream.Read(buffer, 0, 8); +#endif + return BitConverter.ToInt64(buffer, 0); + } + + /// + /// 从指定的内存流中读取一个 32 位整数。 + /// + public static int ReadInt32(MemoryStream stream) + { + var buffer = new byte[4]; +#if FANTASY_NET + stream.ReadExactly(buffer, 0, 4); +#else + stream.Read(buffer, 0, 4); +#endif + return BitConverter.ToInt32(buffer, 0); + } + + /// + /// 将字节转换为十六进制字符串表示。 + /// + public static string ToHex(this byte b) + { + return b.ToString("X2"); + } + + /// + /// 将字节数组转换为十六进制字符串表示。 + /// + public static string ToHex(this byte[] bytes) + { + var stringBuilder = new StringBuilder(); + foreach (var b in bytes) + { + stringBuilder.Append(b.ToString("X2")); + } + + return stringBuilder.ToString(); + } + + /// + /// 将字节数组按指定格式转换为十六进制字符串表示。 + /// + public static string ToHex(this byte[] bytes, string format) + { + var stringBuilder = new StringBuilder(); + foreach (var b in bytes) + { + stringBuilder.Append(b.ToString(format)); + } + + return stringBuilder.ToString(); + } + + /// + /// 将字节数组的指定范围按十六进制格式转换为字符串表示。 + /// + public static string ToHex(this byte[] bytes, int offset, int count) + { + var stringBuilder = new StringBuilder(); + for (var 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 ToStr(this byte[] bytes, int index, int count) + { + return Encoding.Default.GetString(bytes, index, count); + } + + /// + /// 将字节数组转换为 UTF-8 编码的字符串表示。 + /// + public static string Utf8ToStr(this byte[] bytes) + { + return Encoding.UTF8.GetString(bytes); + } + + /// + /// 将字节数组的指定范围按 UTF-8 编码转换为字符串表示。 + /// + public static string Utf8ToStr(this byte[] bytes, int index, int count) + { + return Encoding.UTF8.GetString(bytes, index, count); + } + + /// + /// 将无符号整数写入字节数组的指定偏移位置。 + /// + public static void WriteTo(this byte[] bytes, int offset, uint num) + { + bytes[offset] = (byte)(num & 0xff); + bytes[offset + 1] = (byte)((num & 0xff00) >> 8); + bytes[offset + 2] = (byte)((num & 0xff0000) >> 16); + bytes[offset + 3] = (byte)((num & 0xff000000) >> 24); + } + + /// + /// 将有符号整数写入字节数组的指定偏移位置。 + /// + public static void WriteTo(this byte[] bytes, int offset, int num) + { + bytes[offset] = (byte)(num & 0xff); + bytes[offset + 1] = (byte)((num & 0xff00) >> 8); + bytes[offset + 2] = (byte)((num & 0xff0000) >> 16); + bytes[offset + 3] = (byte)((num & 0xff000000) >> 24); + } + + /// + /// 将字节写入字节数组的指定偏移位置。 + /// + public static void WriteTo(this byte[] bytes, int offset, byte num) + { + bytes[offset] = num; + } + + /// + /// 将有符号短整数写入字节数组的指定偏移位置。 + /// + public static void WriteTo(this byte[] bytes, int offset, short num) + { + bytes[offset] = (byte)(num & 0xff); + bytes[offset + 1] = (byte)((num & 0xff00) >> 8); + } + + /// + /// 将无符号短整数写入字节数组的指定偏移位置。 + /// + public static void WriteTo(this byte[] bytes, int offset, ushort num) + { + bytes[offset] = (byte)(num & 0xff); + bytes[offset + 1] = (byte)((num & 0xff00) >> 8); + } + + /// + /// 将字节数转换为可读的速度表示。 + /// + /// 字节数 + /// 可读的速度表示 + public static string ToReadableSpeed(this long byteCount) + { + var i = 0; + double dblSByte = byteCount; + if (byteCount <= 1024) + { + return $"{dblSByte:0.##}{Suffix[i]}"; + } + + for (i = 0; byteCount / 1024 > 0; i++, byteCount /= 1024) + { + dblSByte = byteCount / 1024.0; + } + + return $"{dblSByte:0.##}{Suffix[i]}"; + } + + /// + /// 将字节数转换为可读的速度表示。 + /// + /// 字节数 + /// 可读的速度表示 + public static string ToReadableSpeed(this ulong byteCount) + { + var i = 0; + double dblSByte = byteCount; + + if (byteCount <= 1024) + { + return $"{dblSByte:0.##}{Suffix[i]}"; + } + + for (i = 0; byteCount / 1024 > 0; i++, byteCount /= 1024) + { + dblSByte = byteCount / 1024.0; + } + + return $"{dblSByte:0.##}{Suffix[i]}"; + } + + /// + /// 合并两个字节数组。 + /// + /// 第一个字节数组 + /// 第二个字节数组 + /// 合并后的字节数组 + public static byte[] MergeBytes(byte[] bytes, byte[] otherBytes) + { + var result = new byte[bytes.Length + otherBytes.Length]; + bytes.CopyTo(result, 0); + otherBytes.CopyTo(result, bytes.Length); + return result; + } + + /// + /// 根据int值获取字节数组。 + /// + /// + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void GetBytes(this int value, byte[] buffer) + { + if (buffer.Length < 4) + { + throw new ArgumentException("Buffer too small."); + } + +#if FANTASY_NET || FANTASY_CONSOLE + MemoryMarshal.Write(buffer.AsSpan(), in value); +#endif +#if FANTASY_UNITY + MemoryMarshal.Write(buffer.AsSpan(), ref value); +#endif + } + + /// + /// 根据int值获取字节数组。 + /// + /// + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteBytes(this MemoryStream memoryStream, int value) + { + using var memoryOwner = MemoryPool.Shared.Rent(4); + var memorySpan = memoryOwner.Memory.Span; +#if FANTASY_NET + MemoryMarshal.Write(memorySpan, in value); +#endif +#if FANTASY_UNITY + MemoryMarshal.Write(memorySpan, ref value); +#endif + memoryStream.Write(memorySpan); + } + + /// + /// 根据uint值获取字节数组。 + /// + /// + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void GetBytes(ref this uint value, byte[] buffer) + { + if (buffer.Length < 4) + { + throw new ArgumentException("Buffer too small."); + } + +#if FANTASY_NET + MemoryMarshal.Write(buffer.AsSpan(), in value); +#endif +#if FANTASY_UNITY + MemoryMarshal.Write(buffer.AsSpan(), ref value); +#endif + } + + /// + /// 根据uint值获取字节数组。 + /// + /// + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteBytes(this MemoryStream memoryStream, uint value) + { + using var memoryOwner = MemoryPool.Shared.Rent(4); + var memorySpan = memoryOwner.Memory.Span; +#if FANTASY_NET + MemoryMarshal.Write(memorySpan, in value); +#endif +#if FANTASY_UNITY + MemoryMarshal.Write(memorySpan, ref value); +#endif + memoryStream.Write(memorySpan); + } + + /// + /// 根据int值获取字节数组。 + /// + /// + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void GetBytes(this long value, byte[] buffer) + { + if (buffer.Length < 8) + { + throw new ArgumentException("Buffer too small."); + } +#if FANTASY_NET + MemoryMarshal.Write(buffer.AsSpan(), in value); +#endif +#if FANTASY_UNITY + MemoryMarshal.Write(buffer.AsSpan(), ref value); +#endif + } + + /// + /// 根据uint值获取字节数组。 + /// + /// + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteBytes(this MemoryStream memoryStream, long value) + { + using var memoryOwner = MemoryPool.Shared.Rent(8); + var memorySpan = memoryOwner.Memory.Span; +#if FANTASY_NET + MemoryMarshal.Write(memorySpan, in value); +#endif +#if FANTASY_UNITY + MemoryMarshal.Write(memorySpan, ref value); +#endif + memoryStream.Write(memorySpan); + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Helper/Download/ADownload.cs b/Fantasy/Fantays.Console/Runtime/Core/Helper/Download/ADownload.cs new file mode 100644 index 0000000..4846d51 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Helper/Download/ADownload.cs @@ -0,0 +1,50 @@ +#if FANTASY_UNITY +using System; +using Fantasy.Async; +using UnityEngine.Networking; + +namespace Fantasy.Unity.Download +{ + public abstract class AUnityDownload : IDisposable + { + private long _timeId; + private ulong _totalDownloadedBytes; + private Download _download; + protected UnityWebRequest UnityWebRequest; + private FCancellationToken _cancellationToken; + private Scene Scene; + + protected AUnityDownload(Scene scene,Download download) + { + Scene = scene; + _download = download; + _download.Tasks.Add(this); + } + + protected UnityWebRequestAsyncOperation Start(UnityWebRequest unityWebRequest, bool monitor) + { + UnityWebRequest = unityWebRequest; + _timeId = Scene.TimerComponent.Unity.RepeatedTimer(33, Update); + return UnityWebRequest.SendWebRequest(); + } + + private void Update() + { + var downloadSpeed = UnityWebRequest.downloadedBytes - _totalDownloadedBytes; + _download.DownloadSpeed += downloadSpeed; + _download.TotalDownloadedBytes += downloadSpeed; + _totalDownloadedBytes = UnityWebRequest.downloadedBytes; + } + + public virtual void Dispose() + { + Update(); + _totalDownloadedBytes = 0; + UnityWebRequest?.Dispose(); + _download.Tasks.Remove(this); + Scene.TimerComponent.Unity.Remove(ref _timeId); + _download = null; + } + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Helper/Download/ADownload.cs.meta b/Fantasy/Fantays.Console/Runtime/Core/Helper/Download/ADownload.cs.meta new file mode 100644 index 0000000..70e1e3c --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Helper/Download/ADownload.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: c5743903d34d474a818b5c2bafa31459 +timeCreated: 1726021902 \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Helper/Download/Download.cs b/Fantasy/Fantays.Console/Runtime/Core/Helper/Download/Download.cs new file mode 100644 index 0000000..a53a42a --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Helper/Download/Download.cs @@ -0,0 +1,72 @@ +#if FANTASY_UNITY +using System.Collections.Generic; +using System.Linq; +using Fantasy.Async; +using UnityEngine; + +namespace Fantasy.Unity.Download +{ + public sealed class Download + { + public Scene Scene; + public ulong DownloadSpeed; + public ulong TotalDownloadedBytes; + public readonly HashSet Tasks = new(); + + public static Download Create(Scene scene) => new Download(scene); + + private Download(Scene scene) + { + Scene = scene; + } + + public void Clear() + { + DownloadSpeed = 0; + TotalDownloadedBytes = 0; + + if (Tasks.Count <= 0) + { + return; + } + + foreach (var aUnityDownload in Tasks.ToArray()) + { + aUnityDownload.Dispose(); + } + + Tasks.Clear(); + } + + public FTask DownloadAssetBundle(string url, bool monitor = false, FCancellationToken cancellationToken = null) + { + return new DownloadAssetBundle(Scene, this).StartDownload(url, monitor, cancellationToken); + } + + public FTask DownloadAudioClip(string url, AudioType audioType, bool monitor = false, FCancellationToken cancellationToken = null) + { + return new DownloadAudioClip(Scene, this).StartDownload(url, audioType, monitor, cancellationToken); + } + + public FTask DownloadSprite(string url, bool monitor = false, FCancellationToken cancellationToken = null) + { + return new DownloadSprite(Scene, this).StartDownload(url, monitor, cancellationToken); + } + + public FTask DownloadTexture(string url, bool monitor = false, FCancellationToken cancellationToken = null) + { + return new DownloadTexture(Scene, this).StartDownload(url, monitor, cancellationToken); + } + + public FTask DownloadText(string url, bool monitor = false, FCancellationToken cancellationToken = null) + { + return new DownloadText(Scene, this).StartDownload(url, monitor, cancellationToken); + } + + public FTask DownloadByte(string url, bool monitor = false, FCancellationToken cancellationToken = null) + { + return new DownloadByte(Scene, this).StartDownload(url, monitor, cancellationToken); + } + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Helper/Download/Download.cs.meta b/Fantasy/Fantays.Console/Runtime/Core/Helper/Download/Download.cs.meta new file mode 100644 index 0000000..41e84be --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Helper/Download/Download.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 5715816370e84842aaab799c9776a5e4 +timeCreated: 1726023436 \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Helper/Download/DownloadAssetBundle.cs b/Fantasy/Fantays.Console/Runtime/Core/Helper/Download/DownloadAssetBundle.cs new file mode 100644 index 0000000..9d8f5a4 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Helper/Download/DownloadAssetBundle.cs @@ -0,0 +1,54 @@ +#if FANTASY_UNITY +using System; +using Fantasy.Async; +using UnityEngine; +using UnityEngine.Networking; + +namespace Fantasy.Unity.Download +{ + public sealed class DownloadAssetBundle : AUnityDownload + { + public DownloadAssetBundle(Scene scene, Download download) : base(scene, download) + { + } + + public FTask StartDownload(string url, bool monitor, FCancellationToken cancellationToken = null) + { + var task = FTask.Create(false); + var unityWebRequestAsyncOperation = Start(UnityWebRequestAssetBundle.GetAssetBundle(Uri.EscapeUriString(url)), monitor); + + if (cancellationToken != null) + { + cancellationToken.Add(() => + { + Dispose(); + task.SetResult(null); + }); + } + + unityWebRequestAsyncOperation.completed += operation => + { + try + { + if (UnityWebRequest.result == UnityWebRequest.Result.Success) + { + var assetBundle = DownloadHandlerAssetBundle.GetContent(UnityWebRequest); + task.SetResult(assetBundle); + } + else + { + Log.Error(UnityWebRequest.error); + task.SetResult(null); + } + } + finally + { + Dispose(); + } + }; + + return task; + } + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Helper/Download/DownloadAssetBundle.cs.meta b/Fantasy/Fantays.Console/Runtime/Core/Helper/Download/DownloadAssetBundle.cs.meta new file mode 100644 index 0000000..c6bf29a --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Helper/Download/DownloadAssetBundle.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 07cbb9a010ed4fe1b90919f81847b9ea +timeCreated: 1726023471 \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Helper/Download/DownloadAudioClip.cs b/Fantasy/Fantays.Console/Runtime/Core/Helper/Download/DownloadAudioClip.cs new file mode 100644 index 0000000..29b3dc5 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Helper/Download/DownloadAudioClip.cs @@ -0,0 +1,54 @@ +#if FANTASY_UNITY +using System; +using Fantasy.Async; +using UnityEngine; +using UnityEngine.Networking; + +namespace Fantasy.Unity.Download +{ + public sealed class DownloadAudioClip : AUnityDownload + { + public DownloadAudioClip(Scene scene, Download download) : base(scene, download) + { + } + + public FTask StartDownload(string url, AudioType audioType, bool monitor, FCancellationToken cancellationToken = null) + { + var task = FTask.Create(false); + var unityWebRequestAsyncOperation = Start(UnityWebRequestMultimedia.GetAudioClip(Uri.EscapeUriString(url), audioType), monitor); + + if (cancellationToken != null) + { + cancellationToken.Add(() => + { + Dispose(); + task.SetResult(null); + }); + } + + unityWebRequestAsyncOperation.completed += operation => + { + try + { + if (UnityWebRequest.result == UnityWebRequest.Result.Success) + { + var audioClip = DownloadHandlerAudioClip.GetContent(UnityWebRequest); + task.SetResult(audioClip); + } + else + { + Log.Error(UnityWebRequest.error); + task.SetResult(null); + } + } + finally + { + Dispose(); + } + }; + + return task; + } + } +} +#endif diff --git a/Fantasy/Fantays.Console/Runtime/Core/Helper/Download/DownloadAudioClip.cs.meta b/Fantasy/Fantays.Console/Runtime/Core/Helper/Download/DownloadAudioClip.cs.meta new file mode 100644 index 0000000..6440f22 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Helper/Download/DownloadAudioClip.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 66d3739ec33845148534e6ecaf134b73 +timeCreated: 1726023476 \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Helper/Download/DownloadByte.cs b/Fantasy/Fantays.Console/Runtime/Core/Helper/Download/DownloadByte.cs new file mode 100644 index 0000000..6c934d9 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Helper/Download/DownloadByte.cs @@ -0,0 +1,52 @@ +#if FANTASY_UNITY +using Fantasy.Async; +using UnityEngine.Networking; + +namespace Fantasy.Unity.Download +{ + public sealed class DownloadByte : AUnityDownload + { + public DownloadByte(Scene scene, Download download) : base(scene, download) + { + } + + public FTask StartDownload(string url, bool monitor, FCancellationToken cancellationToken = null) + { + var task = FTask.Create(false); + var unityWebRequestAsyncOperation = Start(UnityWebRequest.Get(url), monitor); + + if (cancellationToken != null) + { + cancellationToken.Add(() => + { + Dispose(); + task.SetResult(null); + }); + } + + unityWebRequestAsyncOperation.completed += operation => + { + try + { + if (UnityWebRequest.result == UnityWebRequest.Result.Success) + { + var bytes = UnityWebRequest.downloadHandler.data; + task.SetResult(bytes); + } + else + { + Log.Error(UnityWebRequest.error); + task.SetResult(null); + } + } + finally + { + Dispose(); + } + }; + + return task; + } + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Helper/Download/DownloadByte.cs.meta b/Fantasy/Fantays.Console/Runtime/Core/Helper/Download/DownloadByte.cs.meta new file mode 100644 index 0000000..0aa0487 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Helper/Download/DownloadByte.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: ae87f3ea9f4e4c9ebabedf45b0bb83b1 +timeCreated: 1726023481 \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Helper/Download/DownloadSprite.cs b/Fantasy/Fantays.Console/Runtime/Core/Helper/Download/DownloadSprite.cs new file mode 100644 index 0000000..802a967 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Helper/Download/DownloadSprite.cs @@ -0,0 +1,55 @@ +#if FANTASY_UNITY +using System; +using Fantasy.Async; +using UnityEngine; +using UnityEngine.Networking; + +namespace Fantasy.Unity.Download +{ + public sealed class DownloadSprite : AUnityDownload + { + public DownloadSprite(Scene scene, Download download) : base(scene, download) + { + } + + public FTask StartDownload(string url, bool monitor, FCancellationToken cancellationToken = null) + { + var task = FTask.Create(false); + var unityWebRequestAsyncOperation = Start(UnityWebRequestTexture.GetTexture(Uri.EscapeUriString(url)), monitor); + + if (cancellationToken != null) + { + cancellationToken.Add(() => + { + Dispose(); + task.SetResult(null); + }); + } + + unityWebRequestAsyncOperation.completed += operation => + { + try + { + if (UnityWebRequest.result == UnityWebRequest.Result.Success) + { + var texture = DownloadHandlerTexture.GetContent(UnityWebRequest); + var sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), Vector2.one * 5, 1f); + task.SetResult(sprite); + } + else + { + Log.Error(UnityWebRequest.error); + task.SetResult(null); + } + } + finally + { + Dispose(); + } + }; + + return task; + } + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Helper/Download/DownloadSprite.cs.meta b/Fantasy/Fantays.Console/Runtime/Core/Helper/Download/DownloadSprite.cs.meta new file mode 100644 index 0000000..354e3e3 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Helper/Download/DownloadSprite.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 0c2a0f442e974169b7d8b7a5878fe0e6 +timeCreated: 1726023487 \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Helper/Download/DownloadText.cs b/Fantasy/Fantays.Console/Runtime/Core/Helper/Download/DownloadText.cs new file mode 100644 index 0000000..50d8329 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Helper/Download/DownloadText.cs @@ -0,0 +1,52 @@ +#if FANTASY_UNITY +using Fantasy.Async; +using UnityEngine.Networking; + +namespace Fantasy.Unity.Download +{ + public sealed class DownloadText : AUnityDownload + { + public DownloadText(Scene scene, Download download) : base(scene, download) + { + } + + public FTask StartDownload(string url, bool monitor, FCancellationToken cancellationToken = null) + { + var task = FTask.Create(false); + var unityWebRequestAsyncOperation = Start(UnityWebRequest.Get(url), monitor); + + if (cancellationToken != null) + { + cancellationToken.Add(() => + { + Dispose(); + task.SetResult(null); + }); + } + + unityWebRequestAsyncOperation.completed += operation => + { + try + { + if (UnityWebRequest.result == UnityWebRequest.Result.Success) + { + var text = UnityWebRequest.downloadHandler.text; + task.SetResult(text); + } + else + { + Log.Error(UnityWebRequest.error); + task.SetResult(null); + } + } + finally + { + Dispose(); + } + }; + + return task; + } + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Helper/Download/DownloadText.cs.meta b/Fantasy/Fantays.Console/Runtime/Core/Helper/Download/DownloadText.cs.meta new file mode 100644 index 0000000..35892a9 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Helper/Download/DownloadText.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 4284aafa8572453cb75920d2d58e9c50 +timeCreated: 1726023491 \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Helper/Download/DownloadTexture.cs b/Fantasy/Fantays.Console/Runtime/Core/Helper/Download/DownloadTexture.cs new file mode 100644 index 0000000..f66533d --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Helper/Download/DownloadTexture.cs @@ -0,0 +1,54 @@ +#if FANTASY_UNITY +using System; +using Fantasy.Async; +using UnityEngine; +using UnityEngine.Networking; + +namespace Fantasy.Unity.Download +{ + public sealed class DownloadTexture : AUnityDownload + { + public DownloadTexture(Scene scene, Download download) : base(scene, download) + { + } + + public FTask StartDownload(string url, bool monitor, FCancellationToken cancellationToken = null) + { + var task = FTask.Create(false); + var unityWebRequestAsyncOperation = Start(UnityWebRequestTexture.GetTexture(Uri.EscapeUriString(url)), monitor); + + if (cancellationToken != null) + { + cancellationToken.Add(() => + { + Dispose(); + task.SetResult(null); + }); + } + + unityWebRequestAsyncOperation.completed += operation => + { + try + { + if (UnityWebRequest.result == UnityWebRequest.Result.Success) + { + var texture = DownloadHandlerTexture.GetContent(UnityWebRequest); + task.SetResult(texture); + } + else + { + Log.Error(UnityWebRequest.error); + task.SetResult(null); + } + } + finally + { + Dispose(); + } + }; + + return task; + } + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Helper/Download/DownloadTexture.cs.meta b/Fantasy/Fantays.Console/Runtime/Core/Helper/Download/DownloadTexture.cs.meta new file mode 100644 index 0000000..7b48ea6 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Helper/Download/DownloadTexture.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 5eaa6023378844ebb51e4b80425d8a4e +timeCreated: 1726023496 \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Helper/EncryptHelper.cs b/Fantasy/Fantays.Console/Runtime/Core/Helper/EncryptHelper.cs new file mode 100644 index 0000000..be950bc --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Helper/EncryptHelper.cs @@ -0,0 +1,63 @@ +using System.IO; +using System.Security.Cryptography; +using System.Text; + +namespace Fantasy.Helper +{ + /// + /// 提供计算 MD5 散列值的辅助方法。 + /// + public static partial class EncryptHelper + { + private static readonly SHA256 Sha256Hash = SHA256.Create(); + + /// + /// 计算指定字节数组的Sha256。 + /// + /// + /// + public static byte[] ComputeSha256Hash(byte[] bytes) + { +#if FANTASY_UNITY + using var sha256Hash = SHA256.Create(); + return sha256Hash.ComputeHash(bytes); +#else + return SHA256.HashData(bytes); +#endif + } + + /// + /// 计算指定文件的 MD5 散列值。 + /// + /// 要计算散列值的文件路径。 + /// 表示文件的 MD5 散列值的字符串。 + public static string FileMD5(string filePath) + { + using var file = new FileStream(filePath, FileMode.Open); + return FileMD5(file); + } + + /// + /// 计算给定文件流的 MD5 散列值。 + /// + /// 要计算散列值的文件流。 + /// 表示文件流的 MD5 散列值的字符串。 + public static string FileMD5(FileStream fileStream) + { + var md5 = MD5.Create(); + return md5.ComputeHash(fileStream).ToHex("x2"); + } + + /// + /// 计算给定字节数组的 MD5 散列值。 + /// + /// 要计算散列值的字节数组。 + /// 表示字节数组的 MD5 散列值的字符串。 + public static string BytesMD5(byte[] bytes) + { + var md5 = MD5.Create(); + bytes = md5.ComputeHash(bytes); + return bytes.ToHex("x2"); + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Helper/FileHelper.cs b/Fantasy/Fantays.Console/Runtime/Core/Helper/FileHelper.cs new file mode 100644 index 0000000..66ff5ae --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Helper/FileHelper.cs @@ -0,0 +1,186 @@ +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading.Tasks; + +namespace Fantasy.Helper +{ + /// + /// 文件操作助手类,提供了各种文件操作方法。 + /// + public static partial class FileHelper + { + /// + /// 获取相对路径的完整路径。 + /// + /// 相对路径。 + /// 完整路径。 + public static string GetFullPath(string relativePath) + { + return Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), relativePath)); + } + + /// + /// 获取相对路径的完整路径。 + /// + /// 相对于指定的目录的相对路径。 + /// 指定的目录 + /// 完整路径。 + public static string GetFullPath(string relativePath, string srcDir) + { + return Path.GetFullPath(Path.Combine(srcDir, relativePath)); + } + + /// + /// 获取相对路径的的文本信息。 + /// + /// + /// + public static async Task GetTextByRelativePath(string relativePath) + { + var fullPath = GetFullPath(relativePath); + return await File.ReadAllTextAsync(fullPath, Encoding.UTF8); + } + + /// + /// 获取绝对路径的的文本信息。 + /// + /// + /// + public static async Task GetText(string fullPath) + { + return await File.ReadAllTextAsync(fullPath, Encoding.UTF8); + } + + /// + /// 根据文件夹路径创建文件夹,如果文件夹不存在会自动创建文件夹。 + /// + /// + public static void CreateDirectory(string directoryPath) + { + if (directoryPath.LastIndexOf('/') != directoryPath.Length - 1) + { + directoryPath += "/"; + } + + var directoriesByFilePath = GetDirectoriesByFilePath(directoryPath); + + foreach (var dir in directoriesByFilePath) + { + if (Directory.Exists(dir)) + { + continue; + } + + Directory.CreateDirectory(dir); + } + } + + /// + /// 将文件复制到目标路径,如果目标目录不存在会自动创建目录。 + /// + /// 源文件路径。 + /// 目标文件路径。 + /// 是否覆盖已存在的目标文件。 + public static void Copy(string sourceFile, string destinationFile, bool overwrite) + { + CreateDirectory(destinationFile); + File.Copy(sourceFile, destinationFile, overwrite); + } + + /// + /// 获取文件路径内的所有文件夹路径。 + /// + /// 文件路径。 + /// 文件夹路径列表。 + public static IEnumerable GetDirectoriesByFilePath(string filePath) + { + var dir = ""; + var fileDirectories = filePath.Split('/'); + + for (var i = 0; i < fileDirectories.Length - 1; i++) + { + dir = $"{dir}{fileDirectories[i]}/"; + yield return dir; + } + + if (fileDirectories.Length == 1) + { + yield return filePath; + } + } + + /// + /// 将文件夹内的所有内容复制到目标位置。 + /// + /// 源文件夹路径。 + /// 目标文件夹路径。 + /// 是否覆盖已存在的文件。 + public static void CopyDirectory(string sourceDirectory, string destinationDirectory, bool overwrite) + { + // 创建目标文件夹 + + if (!Directory.Exists(destinationDirectory)) + { + Directory.CreateDirectory(destinationDirectory); + } + + // 获取当前文件夹中的所有文件 + + var files = Directory.GetFiles(sourceDirectory); + + // 拷贝文件到目标文件夹 + + foreach (var file in files) + { + var fileName = Path.GetFileName(file); + var destinationPath = Path.Combine(destinationDirectory, fileName); + File.Copy(file, destinationPath, overwrite); + } + + // 获取源文件夹中的所有子文件夹 + + var directories = Directory.GetDirectories(sourceDirectory); + + // 递归方式拷贝文件夹 + + foreach (var directory in directories) + { + var directoryName = Path.GetFileName(directory); + var destinationPath = Path.Combine(destinationDirectory, directoryName); + CopyDirectory(directory, destinationPath, overwrite); + } + } + + /// + /// 获取目录下的所有文件 + /// + /// 文件夹路径。 + /// 需要查找的文件通配符 + /// 查找的类型 + /// + public static string[] GetDirectoryFile(string folderPath, string searchPattern, SearchOption searchOption) + { + return Directory.GetFiles(folderPath, searchPattern, searchOption); + } + + /// + /// 清空文件夹内的所有文件。 + /// + /// 文件夹路径。 + public static void ClearDirectoryFile(string folderPath) + { + if (!Directory.Exists(folderPath)) + { + return; + } + + var files = Directory.GetFiles(folderPath); + + foreach (var file in files) + { + File.Delete(file); + } + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Helper/HashCodeHelper.cs b/Fantasy/Fantays.Console/Runtime/Core/Helper/HashCodeHelper.cs new file mode 100644 index 0000000..7f2a5af --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Helper/HashCodeHelper.cs @@ -0,0 +1,229 @@ +using System.Security.Cryptography; +using System.Text; +// ReSharper disable InconsistentNaming + +namespace Fantasy.Helper +{ + /// + /// HashCode算法帮助类 + /// + public static partial class HashCodeHelper + { + private static readonly SHA256 Sha256Hash = SHA256.Create(); + + /// + /// 计算两个字符串的HashCode + /// + /// + /// + /// + public static int GetHashCode(string a, string b) + { + var hash = 17; + hash = hash * 31 + a.GetHashCode(); + hash = hash * 31 + b.GetHashCode(); + return hash; + } +#if FANTASY_NET || !FANTASY_WEBGL + /// + /// 使用bkdr算法生成一个long的值 + /// + /// + /// + public static unsafe long GetBKDRHashCode(string str) + { + ulong hash = 0; + // 如果要修改这个种子、建议选择一个质数来做种子 + const uint seed = 13131; // 31 131 1313 13131 131313 etc.. + fixed (char* p = str) + { + for (var i = 0; i < str.Length; i++) + { + var c = p[i]; + var high = (byte)(c >> 8); + var low = (byte)(c & byte.MaxValue); + hash = hash * seed + high; + hash = hash * seed + low; + } + } + return (long)hash; + } + + /// + /// 使用MurmurHash3算法生成一个uint的值 + /// + /// + /// + public static unsafe uint MurmurHash3(string str) + { + const uint seed = 0xc58f1a7b; + uint hash = seed; + uint c1 = 0xcc9e2d51; + uint c2 = 0x1b873593; + + fixed (char* p = str) + { + var current = p; + + for (var i = 0; i < str.Length; i++) + { + var k1 = (uint)(*current); + k1 *= c1; + k1 = (k1 << 15) | (k1 >> (32 - 15)); + k1 *= c2; + + hash ^= k1; + hash = (hash << 13) | (hash >> (32 - 13)); + hash = hash * 5 + 0xe6546b64; + + current++; + } + } + + hash ^= (uint)str.Length; + hash ^= hash >> 16; + hash *= 0x85ebca6b; + hash ^= hash >> 13; + hash *= 0xc2b2ae35; + hash ^= hash >> 16; + return hash; + } + + /// + /// 使用MurmurHash3算法生成一个long的值 + /// + /// + /// + public static unsafe long ComputeHash64(string str) + { + const ulong seed = 0xc58f1a7bc58f1a7bUL; // 64-bit seed + var hash = seed; + var c1 = 0x87c37b91114253d5UL; + var c2 = 0x4cf5ad432745937fUL; + + fixed (char* p = str) + { + var current = p; + + for (var i = 0; i < str.Length; i++) + { + var k1 = (ulong)(*current); + k1 *= c1; + k1 = (k1 << 31) | (k1 >> (64 - 31)); + k1 *= c2; + + hash ^= k1; + hash = (hash << 27) | (hash >> (64 - 27)); + hash = hash * 5 + 0x52dce729; + + current++; + } + } + + hash ^= (ulong)str.Length; + hash ^= hash >> 33; + hash *= 0xff51afd7ed558ccdUL; + hash ^= hash >> 33; + hash *= 0xc4ceb9fe1a85ec53UL; + hash ^= hash >> 33; + return (long)hash; + } +#endif +#if FANTASY_WEBGL + /// + /// 使用bkdr算法生成一个long的值 + /// + /// + /// + public static long GetBKDRHashCode(string str) + { + long hash = 0; + // 如果要修改这个种子、建议选择一个质数来做种子 + const uint seed = 13131; // 31 131 1313 13131 131313 etc.. + foreach (var c in str) + { + var high = (byte)(c >> 8); + var low = (byte)(c & byte.MaxValue); + hash = hash * seed + high; + hash = hash * seed + low; + } + return hash; + } + /// + /// 使用MurmurHash3算法生成一个uint的值 + /// + /// + /// + public static uint MurmurHash3(string str) + { + const uint seed = 0xc58f1a7b; + uint hash = seed; + uint c1 = 0xcc9e2d51; + uint c2 = 0x1b873593; + + foreach (var t in str) + { + var k1 = (uint)(t); + k1 *= c1; + k1 = (k1 << 15) | (k1 >> (32 - 15)); + k1 *= c2; + + hash ^= k1; + hash = (hash << 13) | (hash >> (32 - 13)); + hash = hash * 5 + 0xe6546b64; + } + + hash ^= (uint)str.Length; + hash ^= hash >> 16; + hash *= 0x85ebca6b; + hash ^= hash >> 13; + hash *= 0xc2b2ae35; + hash ^= hash >> 16; + return hash; + } + + /// + /// 使用MurmurHash3算法生成一个long的值 + /// + /// + /// + public static long ComputeHash64(string str) + { + const ulong seed = 0xc58f1a7bc58f1a7bUL; // 64-bit seed + var hash = seed; + var c1 = 0x87c37b91114253d5UL; + var c2 = 0x4cf5ad432745937fUL; + + foreach (var t in str) + { + var k1 = (ulong)(t); + k1 *= c1; + k1 = (k1 << 31) | (k1 >> (64 - 31)); + k1 *= c2; + + hash ^= k1; + hash = (hash << 27) | (hash >> (64 - 27)); + hash = hash * 5 + 0x52dce729; + } + + hash ^= (ulong)str.Length; + hash ^= hash >> 33; + hash *= 0xff51afd7ed558ccdUL; + hash ^= hash >> 33; + hash *= 0xc4ceb9fe1a85ec53UL; + hash ^= hash >> 33; + return (long)hash; + } +#endif + /// + /// 根据字符串计算一个Hash值 + /// + /// + /// + public static int ComputeSha256HashAsInt(string rawData) + { + var bytes = Sha256Hash.ComputeHash(Encoding.UTF8.GetBytes(rawData)); + return (bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]; + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Helper/HttpClient/HttpClientHelper.cs b/Fantasy/Fantays.Console/Runtime/Core/Helper/HttpClient/HttpClientHelper.cs new file mode 100644 index 0000000..3954a8c --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Helper/HttpClient/HttpClientHelper.cs @@ -0,0 +1,144 @@ +#if !FANTASY_WEBGL +using System; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using Fantasy.Async; +using Fantasy.Helper; +using Fantasy.Pool; +#pragma warning disable CS8603 // Possible null reference return. + +namespace Fantasy.Http +{ + /// + /// HTTP帮助类 + /// + public static partial class HttpClientHelper + { + private static readonly HttpClient Client = new HttpClient(new HttpClientHandler + { + ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => true + }); + + /// + /// 用Post方式请求string数据 + /// + /// + /// + /// + /// + public static async FTask CallNotDeserializeByPost(string url, HttpContent content) + { + var response = await Client.PostAsync(url, content); + + if (response.StatusCode != HttpStatusCode.OK) + { + throw new Exception($"Unable to connect to server url {(object)url} HttpStatusCode:{(object)response.StatusCode}"); + } + + return await response.Content.ReadAsStringAsync(); + } + + /// + /// 用Get方式请求string数据 + /// + /// + /// + /// + public static async FTask CallNotDeserializeByGet(string url) + { + var response = await Client.GetAsync(url); + + if (response.StatusCode != HttpStatusCode.OK) + { + throw new Exception($"Unable to connect to server url {(object)url} HttpStatusCode:{(object)response.StatusCode}"); + } + + return await response.Content.ReadAsStringAsync(); + } + + /// + /// 用Post方式请求JSON数据,并自动把JSON转换为对象。 + /// + /// + /// + /// + /// + public static async FTask CallByPost(string url, HttpContent content) + { + return await Deserialize(url, await Client.PostAsync(url, content)); + } + + /// + /// 用Post方式请求JSON数据,并自动把JSON转换为对象。 + /// + /// + /// + /// + /// + public static async FTask CallByPost(string url, HttpMethod method) + { + return await Deserialize(url, await Client.SendAsync(new HttpRequestMessage(method, url))); + } + + /// + /// 用Get方式请求JSON数据,并自动把JSON转换为对象。 + /// + /// + /// + /// + public static async FTask CallByGet(string url) + { + return await Deserialize(url, await Client.GetAsync(url)); + } + + /// + /// 用Post方式请求JSON数据,并自动把JSON转换为对象。 + /// + /// + /// + /// + /// + /// + /// + /// + /// + public static async FTask Call(string url, int id, AuthenticationHeaderValue authentication, string method, params object[] @params) where TRequest : class, IJsonRpcRequest, new() + { + var request = Pool.Rent(); + using var httpClientPool = HttpClientPool.Create(); + var client = httpClientPool.Client; + client.DefaultRequestHeaders.Authorization = authentication; + + try + { + request.Init(method, id, @params); + var content = new StringContent(request.ToJson(), Encoding.UTF8, "application/json"); + var response = await Deserialize(url, await client.PostAsync(url, content)); + return response; + } + catch (Exception e) + { + Log.Error(e); + } + finally + { + Pool.Return(request); + } + + return default; + } + + private static async FTask Deserialize(string url, HttpResponseMessage response) + { + if (response.StatusCode != HttpStatusCode.OK) + { + throw new Exception($"Unable to connect to server url {(object)url} HttpStatusCode:{(object)response.StatusCode}"); + } + + return (await response.Content.ReadAsStringAsync()).Deserialize(); + } + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Helper/HttpClient/HttpClientHelper.cs.meta b/Fantasy/Fantays.Console/Runtime/Core/Helper/HttpClient/HttpClientHelper.cs.meta new file mode 100644 index 0000000..70e3bd5 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Helper/HttpClient/HttpClientHelper.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: f8005f3a1a1945a2929442f82832e765 +timeCreated: 1726023741 \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Helper/HttpClient/HttpClientPool.cs b/Fantasy/Fantays.Console/Runtime/Core/Helper/HttpClient/HttpClientPool.cs new file mode 100644 index 0000000..de421ff --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Helper/HttpClient/HttpClientPool.cs @@ -0,0 +1,44 @@ +#if !FANTASY_WEBGL +using System; +using System.Collections.Generic; +using System.Net.Http; +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. + +namespace Fantasy.Http +{ + internal class HttpClientPool : IDisposable + { + private bool IsDispose { get; set; } + public HttpClient Client { get; private set; } + private static readonly Queue Pools = new Queue(); + private static readonly HttpClientHandler ClientHandler = new HttpClientHandler + { + ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => true + }; + + public static HttpClientPool Create() + { + if (Pools.TryDequeue(out var httpClientPool)) + { + httpClientPool.IsDispose = false; + return httpClientPool; + } + + httpClientPool = new HttpClientPool(); + httpClientPool.Client = new HttpClient(ClientHandler); + return httpClientPool; + } + + public void Dispose() + { + if (IsDispose) + { + return; + } + + IsDispose = true; + Pools.Enqueue(this); + } + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Helper/HttpClient/HttpClientPool.cs.meta b/Fantasy/Fantays.Console/Runtime/Core/Helper/HttpClient/HttpClientPool.cs.meta new file mode 100644 index 0000000..a1ac3bf --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Helper/HttpClient/HttpClientPool.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: a78c441357d244d5ba490a13c89e1c50 +timeCreated: 1726023895 \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Helper/HttpClient/IJsonRpcRequest.cs b/Fantasy/Fantays.Console/Runtime/Core/Helper/HttpClient/IJsonRpcRequest.cs new file mode 100644 index 0000000..ae756c5 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Helper/HttpClient/IJsonRpcRequest.cs @@ -0,0 +1,20 @@ +using Fantasy.Pool; + +#if !FANTASY_WEBGL +namespace Fantasy.Http +{ + /// + /// 一个JsonRPC的接口 + /// + public interface IJsonRpcRequest : IPool + { + /// + /// 用于初始化这个Json对象 + /// + /// + /// + /// + void Init(string method, int id, params object[] @params); + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Helper/HttpClient/IJsonRpcRequest.cs.meta b/Fantasy/Fantays.Console/Runtime/Core/Helper/HttpClient/IJsonRpcRequest.cs.meta new file mode 100644 index 0000000..9e01783 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Helper/HttpClient/IJsonRpcRequest.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 72a03580c619417b9f8f92d99938e371 +timeCreated: 1726023900 \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Helper/JsonHelper.cs b/Fantasy/Fantays.Console/Runtime/Core/Helper/JsonHelper.cs new file mode 100644 index 0000000..2b10fab --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Helper/JsonHelper.cs @@ -0,0 +1,57 @@ +using System; +using Newtonsoft.Json; +#pragma warning disable CS8603 + +namespace Fantasy.Helper +{ + /// + /// 提供操作 JSON 数据的辅助方法。 + /// + public static partial class JsonHelper + { + /// + /// 将对象序列化为 JSON 字符串。 + /// + /// 要序列化的对象类型。 + /// 要序列化的对象。 + /// 表示序列化对象的 JSON 字符串。 + public static string ToJson(this T t) + { + return JsonConvert.SerializeObject(t); + } + + /// + /// 反序列化 JSON 字符串为指定类型的对象。 + /// + /// 要反序列化的 JSON 字符串。 + /// 目标对象的类型。 + /// 是否使用反射进行反序列化(默认为 true)。 + /// 反序列化后的对象。 + public static object Deserialize(this string json, Type type, bool reflection = true) + { + return JsonConvert.DeserializeObject(json, type); + } + + /// + /// 反序列化 JSON 字符串为指定类型的对象。 + /// + /// 目标对象的类型。 + /// 要反序列化的 JSON 字符串。 + /// 反序列化后的对象。 + public static T Deserialize(this string json) + { + return JsonConvert.DeserializeObject(json); + } + + /// + /// 克隆对象,通过将对象序列化为 JSON,然后再进行反序列化。 + /// + /// 要克隆的对象类型。 + /// 要克隆的对象。 + /// 克隆后的对象。 + public static T Clone(T t) + { + return t.ToJson().Deserialize(); + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Helper/NetworkHelper.cs b/Fantasy/Fantays.Console/Runtime/Core/Helper/NetworkHelper.cs new file mode 100644 index 0000000..6a35a61 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Helper/NetworkHelper.cs @@ -0,0 +1,443 @@ +#if !FANTASY_WEBGL +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.NetworkInformation; +using System.Net.Sockets; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +#pragma warning disable CS8603 // Possible null reference return. + +// ReSharper disable InconsistentNaming + +namespace Fantasy.Helper +{ + /// + /// 提供网络操作相关的帮助方法。 + /// + public static partial class NetworkHelper + { + /// + /// 根据字符串获取一个IPEndPoint + /// + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static IPEndPoint GetIPEndPoint(string address) + { + try + { + var addressSplit = address.Split(':'); + if (addressSplit.Length != 2) + { + throw new FormatException("Invalid format"); + } + + var ipString = addressSplit[0]; + var portString = addressSplit[1]; + + if (!IPAddress.TryParse(ipString, out var ipAddress)) + { + throw new FormatException("Invalid IP address"); + } + + if (!int.TryParse(portString, out var port) || port < 0 || port > 65535) + { + throw new FormatException("Invalid port number"); + } + + return new IPEndPoint(ipAddress, port); + } + catch (Exception e) + { + Log.Error($"Error parsing IP and Port:{e.Message}"); + return null; + } + } + + /// + /// 克隆一个IPEndPoint + /// + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static IPEndPoint Clone(this EndPoint endPoint) + { + var ip = (IPEndPoint)endPoint; + return new IPEndPoint(ip.Address, ip.Port); + } + + /// + /// 比较两个IPEndPoint是否相等 + /// + /// + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IPEndPointEquals(this EndPoint endPoint, IPEndPoint ipEndPoint) + { + var ip = (IPEndPoint)endPoint; + return ip.Address.Equals(ipEndPoint.Address) && ip.Port == ipEndPoint.Port; + } + + /// + /// 比较两个IPEndPoint是否相等 + /// + /// + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IPEndPointEquals(this IPEndPoint endPoint, IPEndPoint ipEndPoint) + { + return endPoint.Address.Equals(ipEndPoint.Address) && endPoint.Port == ipEndPoint.Port; + } + +#if !FANTASY_WEBGL + /// + /// 将SocketAddress写入到Byte[]中 + /// + /// + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe void SocketAddressToByte(this SocketAddress socketAddress, byte[] buffer, int offset) + { + if (socketAddress == null) + { + throw new ArgumentNullException(nameof(socketAddress), "The SocketAddress cannot be null."); + } + + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer), "The buffer cannot be null."); + } + + if (buffer.Length < socketAddress.Size + offset + 8) + { + throw new ArgumentException("The buffer length is insufficient. It must be at least the size of the SocketAddress plus 8 bytes.", nameof(buffer)); + } + + fixed (byte* pBuffer = buffer) + { + var pOffsetBuffer = pBuffer + offset; + var addressFamilyValue = (int)socketAddress.Family; + var socketAddressSizeValue = socketAddress.Size; + Buffer.MemoryCopy(&addressFamilyValue, pOffsetBuffer, buffer.Length - offset, sizeof(int)); + Buffer.MemoryCopy(&socketAddressSizeValue, pOffsetBuffer + 4, buffer.Length - offset -4, sizeof(int)); + for (var i = 0; i < socketAddress.Size - 2; i++) + { + pOffsetBuffer[8 + i] = socketAddress[i + 2]; + } + } + } + + /// + /// 将byre[]转换为SocketAddress + /// + /// + /// + /// + /// + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe int ByteToSocketAddress(byte[] buffer, int offset, out SocketAddress socketAddress) + { + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer), "The buffer cannot be null."); + } + + if (buffer.Length < 8) + { + throw new ArgumentException("Buffer length is insufficient. It must be at least 8 bytes.", nameof(buffer)); + } + + try + { + fixed (byte* pBuffer = buffer) + { + var pOffsetBuffer = pBuffer + offset; + var addressFamily = (AddressFamily)Marshal.ReadInt32((IntPtr)pOffsetBuffer); + var socketAddressSize = Marshal.ReadInt32((IntPtr)(pOffsetBuffer + 4)); + + if (buffer.Length < offset + 8 + socketAddressSize) + { + throw new ArgumentException("Buffer length is insufficient for the given SocketAddress size.", nameof(buffer)); + } + + socketAddress = new SocketAddress(addressFamily, socketAddressSize); + + for (var i = 0; i < socketAddressSize - 2; i++) + { + socketAddress[i + 2] = *(pOffsetBuffer + 8 + i); + } + + return 8 + offset + socketAddressSize; + } + } + catch (ArgumentNullException ex) + { + throw new InvalidOperationException("An argument provided to the method is null.", ex); + } + catch (ArgumentException ex) + { + throw new InvalidOperationException("An argument provided to the method is invalid.", ex); + } + catch (Exception ex) + { + throw new InvalidOperationException("An unexpected error occurred while processing the buffer.", ex); + } + } + + /// + /// 将ReadOnlyMemory转换为SocketAddress + /// + /// + /// + /// + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe int ByteToSocketAddress(ReadOnlyMemory buffer, int offset, out SocketAddress socketAddress) + { + if (buffer.Length < 8) + { + throw new ArgumentException("Buffer length is insufficient. It must be at least 8 bytes.", nameof(buffer)); + } + + try + { + fixed (byte* pBuffer = buffer.Span) + { + var pOffsetBuffer = pBuffer + offset; + var addressFamily = (AddressFamily)Marshal.ReadInt32((IntPtr)pOffsetBuffer); + var socketAddressSize = Marshal.ReadInt32((IntPtr)(pOffsetBuffer + 4)); + + if (buffer.Length < offset + 8 + socketAddressSize) + { + throw new ArgumentException("Buffer length is insufficient for the given SocketAddress size.", nameof(buffer)); + } + + socketAddress = new SocketAddress(addressFamily, socketAddressSize); + + for (var i = 0; i < socketAddressSize - 2; i++) + { + socketAddress[i + 2] = *(pOffsetBuffer + 8 + i); + } + + return 8 + offset + socketAddressSize; + } + } + catch (ArgumentNullException ex) + { + throw new InvalidOperationException("An argument provided to the method is null.", ex); + } + catch (ArgumentException ex) + { + throw new InvalidOperationException("An argument provided to the method is invalid.", ex); + } + catch (Exception ex) + { + throw new InvalidOperationException("An unexpected error occurred while processing the buffer.", ex); + } + } + + /// + /// 根据SocketAddress获得IPEndPoint + /// + /// + /// + /// + public static unsafe IPEndPoint GetIPEndPoint(this SocketAddress socketAddress) + { + switch (socketAddress.Family) + { + case AddressFamily.InterNetwork: + { + var ipBytes = new byte[4]; + for (var i = 0; i < 4; i++) + { + ipBytes[i] = socketAddress[4 + i]; + } + var port = (socketAddress[2] << 8) + socketAddress[3]; + var ip = new IPAddress(ipBytes); + return new IPEndPoint(ip, port); + } + case AddressFamily.InterNetworkV6: + { + var ipBytes = new byte[16]; + Span socketAddressSpan = stackalloc byte[28]; + + for (var i = 0; i < 28; i++) + { + socketAddressSpan[i] = socketAddress[i]; + } + + fixed (byte* pSocketAddress = socketAddressSpan) + { + for (var i = 0; i < 16; i++) + { + ipBytes[i] = *(pSocketAddress + 8 + i); + } + + var port = (*(pSocketAddress + 2) << 8) + *(pSocketAddress + 3); + var scopeId = Marshal.ReadInt64((IntPtr)(pSocketAddress + 24)); + var ip = new IPAddress(ipBytes, scopeId); + return new IPEndPoint(ip, port); + } + } + default: + { + throw new NotSupportedException("Address family not supported."); + } + } + } +#endif + /// + /// 获取本机所有网络适配器的IP地址。 + /// + /// IP地址数组。 + public static string[] GetAddressIPs() + { + var list = new List(); + foreach (var networkInterface in NetworkInterface.GetAllNetworkInterfaces()) + { + // 仅考虑以太网类型的网络适配器 + if (networkInterface.NetworkInterfaceType != NetworkInterfaceType.Ethernet) + { + continue; + } + + foreach (var add in networkInterface.GetIPProperties().UnicastAddresses) + { + list.Add(add.Address.ToString()); + } + } + + return list.ToArray(); + } + + /// + /// 将主机名和端口号转换为 实例。 + /// + /// 主机名。 + /// 端口号。 + /// 实例。 + public static IPEndPoint ToIPEndPoint(string host, int port) + { + return new IPEndPoint(IPAddress.Parse(host), port); + } + + /// + /// 将地址字符串转换为 实例。 + /// + /// 地址字符串,格式为 "主机名:端口号"。 + /// 实例。 + public static IPEndPoint ToIPEndPoint(string address) + { + var index = address.LastIndexOf(':'); + var host = address.Substring(0, index); + var p = address.Substring(index + 1); + var port = int.Parse(p); + return ToIPEndPoint(host, port); + } + + /// + /// 将 实例转换为字符串表示形式。 + /// + /// 实例。 + /// 表示 的字符串。 + public static string IPEndPointToStr(this IPEndPoint self) + { + return $"{self.Address}:{self.Port}"; + } + + /// + /// 针对 Windows 平台设置UDP连接重置选项。 + /// + /// 要设置选项的 实例。 + public static void SetSioUdpConnReset(this Socket socket) + { + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return; + } + + /* + 目前这个问题只有Windows下才会出现。 + 服务器端在发送数据时捕获到了一个异常, + 这个异常导致原因应该是远程客户端的UDP监听已停止导致数据发送出错。 + 按理说UDP是无连接的,报这个异常是不合理的 + 这个异常让整UDP的服务监听也停止了。 + 这样就因为一个客户端的数据发送无法到达而导致了服务挂了,所有客户端都无法与服务器通信了 + 想详细了解看下https://blog.csdn.net/sunzhen6251/article/details/124168805*/ + const uint IOC_IN = 0x80000000; + const uint IOC_VENDOR = 0x18000000; + const int SIO_UDP_CONNRESET = unchecked((int) (IOC_IN | IOC_VENDOR | 12)); + + socket.IOControl(SIO_UDP_CONNRESET, new[] {Convert.ToByte(false)}, null); + } + + /// + /// 将 Socket 缓冲区大小设置为操作系统限制。 + /// + /// 要设置缓冲区大小的 Socket。 + public static void SetSocketBufferToOsLimit(this Socket socket) + { + socket.SetReceiveBufferToOSLimit(); + socket.SetSendBufferToOSLimit(); + } + + /// + /// 将 Socket 接收缓冲区大小设置为操作系统限制。 + /// 尝试增加接收缓冲区大小的次数 = 默认 + 最大增加 100 MB。 + /// + /// 要设置接收缓冲区大小的 Socket。 + /// 每次增加的步长大小。 + /// 尝试增加缓冲区大小的次数。 + public static void SetReceiveBufferToOSLimit(this Socket socket, int stepSize = 1024, int attempts = 100_000) + { + // setting a too large size throws a socket exception. + // so let's keep increasing until we encounter it. + for (int i = 0; i < attempts; ++i) + { + // increase in 1 KB steps + try + { + socket.ReceiveBufferSize += stepSize; + } + catch (SocketException) + { + break; + } + } + } + + /// + /// 将 Socket 发送缓冲区大小设置为操作系统限制。 + /// 尝试增加发送缓冲区大小的次数 = 默认 + 最大增加 100 MB。 + /// + /// 要设置发送缓冲区大小的 Socket。 + /// 每次增加的步长大小。 + /// 尝试增加缓冲区大小的次数。 + public static void SetSendBufferToOSLimit(this Socket socket, int stepSize = 1024, int attempts = 100_000) + { + // setting a too large size throws a socket exception. + // so let's keep increasing until we encounter it. + for (var i = 0; i < attempts; ++i) + { + // increase in 1 KB steps + try + { + socket.SendBufferSize += stepSize; + } + catch (SocketException) + { + break; + } + } + } + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Helper/RandomHelper.cs b/Fantasy/Fantays.Console/Runtime/Core/Helper/RandomHelper.cs new file mode 100644 index 0000000..c90a16c --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Helper/RandomHelper.cs @@ -0,0 +1,311 @@ +#if FANTASY_NET || !FANTASY_WEBGL +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using Fantasy.LowLevel; +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. + +namespace Fantasy.Helper +{ + /// + /// 随机数操作助手类,提供各种随机数生成和操作方法。 + /// + public static partial class RandomHelper + { + [ThreadStatic] + private static Random _random; + + /// + /// 生成一个随机的无符号 64 位整数。 + /// + /// 无符号 64 位整数。 + public static ulong RandUInt64() + { + var byte8 = new FixedBytes8().AsSpan(); + var random = _random ??= new Random(); + random.NextBytes(byte8); + return BitConverter.ToUInt64(byte8); + } + + /// + /// 生成一个随机的 64 位整数。 + /// + /// 64 位整数。 + public static long RandInt64() + { + var byte8 = new FixedBytes8().AsSpan(); + var random = _random ??= new Random(); + random.NextBytes(byte8); + return BitConverter.ToInt64(byte8); + } + + /// + /// 生成一个随机的无符号 32 位整数。 + /// + /// 无符号 32 位整数。 + public static uint RandUInt32() + { + var random = _random ??= new Random(); + return (uint) random.Next(); + } + + /// + /// 生成一个随机的无符号 16 位整数。 + /// + /// 无符号 16 位整数。 + public static ushort RandUInt16() + { + var byte2 = new FixedBytes2().AsSpan(); + var random = _random ??= new Random(); + random.NextBytes(byte2); + return BitConverter.ToUInt16(byte2); + } + + /// + /// 在指定范围内生成一个随机整数(包含下限,不包含上限)。 + /// + /// 下限。 + /// 上限。 + /// 生成的随机整数。 + public static int RandomNumber(int lower, int upper) + { + var random = _random ??= new Random(); + return random.Next(lower, upper); + } + + /// + /// 生成一个随机的布尔值。 + /// + /// 随机的布尔值。 + public static bool RandomBool() + { + var random = _random ??= new Random(); + return (random.Next() & 1) == 0; + } + + /// + /// 从数组中随机选择一个元素。 + /// + /// 数组元素的类型。 + /// 要选择的数组。 + /// 随机选择的数组元素。 + public static T RandomArray(this T[] array) + { + return array[RandomNumber(0, array.Count())]; + } + + /// + /// 从列表中随机选择一个元素。 + /// + /// 列表元素的类型。 + /// 要选择的列表。 + /// 随机选择的列表元素。 + public static T RandomArray(this List array) + { + return array[RandomNumber(0, array.Count())]; + } + + /// + /// 打乱列表中元素的顺序。 + /// + /// 列表元素的类型。 + /// 要打乱顺序的列表。 + public static void BreakRank(List arr) + { + if (arr == null || arr.Count < 2) + { + return; + } + + var random = _random ??= new Random(); + for (var i = 0; i < arr.Count / 2; i++) + { + var index = random.Next(0, arr.Count); + (arr[index], arr[arr.Count - index - 1]) = (arr[arr.Count - index - 1], arr[index]); + } + } + + /// + /// 生成一个介于 0 和 1 之间的随机单精度浮点数。 + /// + /// 随机单精度浮点数。 + public static float RandFloat01() + { + var random = _random ??= new Random(); + var value = random.NextDouble(); + return (float) value; + } + + private static int Rand(int n) + { + var rd = new Random(); + // 注意,返回值是左闭右开,所以maxValue要加1 + return rd.Next(1, n + 1); + } + + /// + /// 根据权重随机选择一个索引。 + /// + /// 权重数组,每个元素表示相应索引的权重。 + /// 随机选择的索引值。 + public static int RandomByWeight(int[] weights) + { + var sum = weights.Sum(); + var numberRand = Rand(sum); + var sumTemp = 0; + for (var i = 0; i < weights.Length; i++) + { + sumTemp += weights[i]; + if (numberRand <= sumTemp) + { + return i; + } + } + + return -1; + } + + /// + /// 根据固定概率随机选择一个索引,即某个数值上限内随机多少次。 + /// + /// 概率数组,每个元素表示相应索引的概率。 + /// 随机选择的索引值。 + public static int RandomByFixedProbability(int[] args) + { + var random = _random ??= new Random(); + var argCount = args.Length; + var sum = args.Sum(); + var value = random.NextDouble() * sum; + while (sum > value) + { + sum -= args[argCount - 1]; + argCount--; + } + + return argCount; + } + + /// + /// 返回随机数。 + /// + /// 是否包含负数。 + /// 返回一个随机的单精度浮点数。 + public static float NextFloat(bool containNegative = false) + { + var random = _random ??= new Random(); + float f; + var buffer = new FixedBytes4().AsSpan(); + if (containNegative) + { + do + { + random.NextBytes(buffer); + f = BitConverter.ToSingle(buffer); + } while ((f >= float.MinValue && f < float.MaxValue) == false); + + return f; + } + + do + { + random.NextBytes(buffer); + f = BitConverter.ToSingle(buffer); + } while ((f >= 0 && f < float.MaxValue) == false); + + return f; + } + + /// + /// 返回一个小于所指定最大值的非负随机数。 + /// + /// 要生成的随机数的上限(随机数不能取该上限值)。 maxValue 必须大于或等于零。 + /// 大于等于零且小于 maxValue 的单精度浮点数,即:返回值的范围通常包括零但不包括 maxValue。 不过,如果 maxValue 等于零,则返回 maxValue。 + public static float NextFloat(float maxValue) + { + if (maxValue.Equals(0)) + { + return maxValue; + } + + if (maxValue < 0) + { + throw new ArgumentOutOfRangeException("“maxValue”必须大于 0。", "maxValue"); + } + + var random = _random ??= new Random(); + float f; + var buffer = new FixedBytes4().AsSpan(); + + do + { + random.NextBytes(buffer); + f = BitConverter.ToSingle(buffer); + } while ((f >= 0 && f < maxValue) == false); + + return f; + } + + /// + /// 返回一个指定范围内的随机数。 + /// + /// 返回的随机数的下界(随机数可取该下界值)。 + /// 返回的随机数的上界(随机数不能取该上界值)。 maxValue 必须大于或等于 minValue。 + /// 一个大于等于 minValue 且小于 maxValue 的单精度浮点数,即:返回的值范围包括 minValue 但不包括 maxValue。 如果 minValue 等于 maxValue,则返回 minValue。 + public static float NextFloat(float minValue, float maxValue) + { + if (minValue.Equals(maxValue)) + { + return minValue; + } + + if (minValue > maxValue) + { + throw new ArgumentOutOfRangeException("“minValue”不能大于 maxValue。", "minValue"); + } + + var random = _random ??= new Random(); + var buffer = new FixedBytes4().AsSpan(); + float f; + + do + { + random.NextBytes(buffer); + f = BitConverter.ToSingle(buffer); + } while ((f >= minValue && f < maxValue) == false); + + return f; + } + + /// + /// 在指定的矩形区域内随机生成一个二维向量位置。 + /// + /// X轴最小值。 + /// X轴最大值。 + /// Y轴最小值。 + /// Y轴最大值。 + /// 随机生成的二维向量位置。 + public static Vector2 NextVector2(float minX, float maxX, float minY, float maxY) + { + return new Vector2(NextFloat(minX, maxX), NextFloat(minY, maxY)); + } + + /// + /// 生成指定长度的随机数字代码。 + /// + /// 数字代码的长度。 + /// 生成的随机数字代码。 + public static string RandomNumberCode(int len = 6) + { + int num = 0; + for (int i = 0; i < len; i++) + { + int number = RandomNumber(0, 10); + num = num * 10 + number; + } + + return num.ToString(); + } + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Helper/RandomHelperWebgl.cs b/Fantasy/Fantays.Console/Runtime/Core/Helper/RandomHelperWebgl.cs new file mode 100644 index 0000000..0820acf --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Helper/RandomHelperWebgl.cs @@ -0,0 +1,295 @@ +#if FANTASY_WEBGL +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; + +namespace Fantasy.Helper +{ + /// + /// 随机数操作助手类,提供各种随机数生成和操作方法。 + /// + public static partial class RandomHelper + { + private static readonly Random Random = new Random(); + private static readonly byte[] Byte8 = new byte[8]; + private static readonly byte[] Byte2 = new byte[2]; + + /// + /// 生成一个随机的无符号 64 位整数。 + /// + /// 无符号 64 位整数。 + public static ulong RandUInt64() + { + Random.NextBytes(Byte8); + return BitConverter.ToUInt64(Byte8, 0); + } + + /// + /// 生成一个随机的 64 位整数。 + /// + /// 64 位整数。 + public static long RandInt64() + { + Random.NextBytes(Byte8); + return BitConverter.ToInt64(Byte8, 0); + } + + /// + /// 生成一个随机的无符号 32 位整数。 + /// + /// 无符号 32 位整数。 + public static uint RandUInt32() + { + return (uint) Random.Next(); + } + + /// + /// 生成一个随机的无符号 16 位整数。 + /// + /// 无符号 16 位整数。 + public static ushort RandUInt16() + { + Random.NextBytes(Byte2); + return BitConverter.ToUInt16(Byte2, 0); + } + + /// + /// 在指定范围内生成一个随机整数(包含下限,不包含上限)。 + /// + /// 下限。 + /// 上限。 + /// 生成的随机整数。 + public static int RandomNumber(int lower, int upper) + { + return Random.Next(lower, upper); + } + + /// + /// 生成一个随机的布尔值。 + /// + /// 随机的布尔值。 + public static bool RandomBool() + { + return Random.Next(2) == 0; + } + + /// + /// 从数组中随机选择一个元素。 + /// + /// 数组元素的类型。 + /// 要选择的数组。 + /// 随机选择的数组元素。 + public static T RandomArray(this T[] array) + { + return array[RandomNumber(0, array.Count())]; + } + + /// + /// 从列表中随机选择一个元素。 + /// + /// 列表元素的类型。 + /// 要选择的列表。 + /// 随机选择的列表元素。 + public static T RandomArray(this List array) + { + return array[RandomNumber(0, array.Count())]; + } + + /// + /// 打乱列表中元素的顺序。 + /// + /// 列表元素的类型。 + /// 要打乱顺序的列表。 + public static void BreakRank(List arr) + { + if (arr == null || arr.Count < 2) + { + return; + } + + for (var i = 0; i < arr.Count / 2; i++) + { + var index = Random.Next(0, arr.Count); + (arr[index], arr[arr.Count - index - 1]) = (arr[arr.Count - index - 1], arr[index]); + } + } + + /// + /// 生成一个介于 0 和 1 之间的随机单精度浮点数。 + /// + /// 随机单精度浮点数。 + public static float RandFloat01() + { + var value = Random.NextDouble(); + return (float) value; + } + + private static int Rand(int n) + { + var rd = new Random(); + // 注意,返回值是左闭右开,所以maxValue要加1 + return rd.Next(1, n + 1); + } + + /// + /// 根据权重随机选择一个索引。 + /// + /// 权重数组,每个元素表示相应索引的权重。 + /// 随机选择的索引值。 + public static int RandomByWeight(int[] weights) + { + var sum = weights.Sum(); + var numberRand = Rand(sum); + var sumTemp = 0; + for (var i = 0; i < weights.Length; i++) + { + sumTemp += weights[i]; + if (numberRand <= sumTemp) + { + return i; + } + } + + return -1; + } + + /// + /// 根据固定概率随机选择一个索引,即某个数值上限内随机多少次。 + /// + /// 概率数组,每个元素表示相应索引的概率。 + /// 随机选择的索引值。 + public static int RandomByFixedProbability(int[] args) + { + var argCount = args.Length; + var sum = args.Sum(); + var random = Random.NextDouble() * sum; + while (sum > random) + { + sum -= args[argCount - 1]; + argCount--; + } + + return argCount; + } + + /// + /// 返回随机数。 + /// + /// 是否包含负数。 + /// 返回一个随机的单精度浮点数。 + public static float NextFloat(bool containNegative = false) + { + float f; + var buffer = new byte[4]; + if (containNegative) + { + do + { + Random.NextBytes(buffer); + f = BitConverter.ToSingle(buffer, 0); + } while ((f >= float.MinValue && f < float.MaxValue) == false); + + return f; + } + + do + { + Random.NextBytes(buffer); + f = BitConverter.ToSingle(buffer, 0); + } while ((f >= 0 && f < float.MaxValue) == false); + + return f; + } + + /// + /// 返回一个小于所指定最大值的非负随机数。 + /// + /// 要生成的随机数的上限(随机数不能取该上限值)。 maxValue 必须大于或等于零。 + /// 大于等于零且小于 maxValue 的单精度浮点数,即:返回值的范围通常包括零但不包括 maxValue。 不过,如果 maxValue 等于零,则返回 maxValue。 + public static float NextFloat(float maxValue) + { + if (maxValue.Equals(0)) + { + return maxValue; + } + + if (maxValue < 0) + { + throw new ArgumentOutOfRangeException("“maxValue”必须大于 0。", "maxValue"); + } + + float f; + var buffer = new byte[4]; + + do + { + Random.NextBytes(buffer); + f = BitConverter.ToSingle(buffer, 0); + } while ((f >= 0 && f < maxValue) == false); + + return f; + } + + /// + /// 返回一个指定范围内的随机数。 + /// + /// 返回的随机数的下界(随机数可取该下界值)。 + /// 返回的随机数的上界(随机数不能取该上界值)。 maxValue 必须大于或等于 minValue。 + /// 一个大于等于 minValue 且小于 maxValue 的单精度浮点数,即:返回的值范围包括 minValue 但不包括 maxValue。 如果 minValue 等于 maxValue,则返回 minValue。 + public static float NextFloat(float minValue, float maxValue) + { + if (minValue.Equals(maxValue)) + { + return minValue; + } + + if (minValue > maxValue) + { + throw new ArgumentOutOfRangeException("“minValue”不能大于 maxValue。", "minValue"); + } + + float f; + var buffer = new byte[4]; + + do + { + Random.NextBytes(buffer); + f = BitConverter.ToSingle(buffer, 0); + } while ((f >= minValue && f < maxValue) == false); + + return f; + } + + /// + /// 在指定的矩形区域内随机生成一个二维向量位置。 + /// + /// X轴最小值。 + /// X轴最大值。 + /// Y轴最小值。 + /// Y轴最大值。 + /// 随机生成的二维向量位置。 + public static Vector2 NextVector2(float minX, float maxX, float minY, float maxY) + { + return new Vector2(NextFloat(minX, maxX), NextFloat(minY, maxY)); + } + + /// + /// 生成指定长度的随机数字代码。 + /// + /// 数字代码的长度。 + /// 生成的随机数字代码。 + public static string RandomNumberCode(int len = 6) + { + int num = 0; + for (int i = 0; i < len; i++) + { + int number = RandomNumber(0, 10); + num = num * 10 + number; + } + + return num.ToString(); + } + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Helper/SocketHelper.cs b/Fantasy/Fantays.Console/Runtime/Core/Helper/SocketHelper.cs new file mode 100644 index 0000000..15027c5 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Helper/SocketHelper.cs @@ -0,0 +1,74 @@ +#if !FANTASY_WEBGL +using System.Net; +using System.Net.Sockets; +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +namespace Fantasy.Helper +{ + /// + /// Socket帮助类 + /// + public static partial class SocketHelper + { + // always pass the same IPEndPointNonAlloc instead of allocating a new + // one each time. + // + // use IPEndPointNonAlloc.temp to get the latest SocketAdddress written + // by ReceiveFrom_Internal! + // + // IMPORTANT: .temp will be overwritten in next call! + // hash or manually copy it if you need to store it, e.g. + // when adding a new connection. + public static int ReceiveFrom_NonAlloc( + this Socket socket, + byte[] buffer, + int offset, + int size, + SocketFlags socketFlags, + EndPoint remoteEndPoint) + { + // call ReceiveFrom with IPEndPointNonAlloc. + // need to wrap this in ReceiveFrom_NonAlloc because it's not + // obvious that IPEndPointNonAlloc.Create does NOT create a new + // IPEndPoint. it saves the result in IPEndPointNonAlloc.temp! +#if FANTASY_UNITY + EndPoint casted = remoteEndPoint; + return socket.ReceiveFrom(buffer, offset, size, socketFlags, ref casted); +#else + return socket.ReceiveFrom(buffer, offset, size, socketFlags, ref remoteEndPoint); +#endif + } + + // same as above, different parameters + public static int ReceiveFrom_NonAlloc(this Socket socket, byte[] buffer, ref EndPoint remoteEndPoint) + { +#if UNITY + EndPoint casted = remoteEndPoint; + return socket.ReceiveFrom(buffer, ref casted); +#else + return socket.ReceiveFrom(buffer, ref remoteEndPoint); +#endif + + } + + // SendTo allocates too: + // https://github.com/mono/mono/blob/f74eed4b09790a0929889ad7fc2cf96c9b6e3757/mcs/class/System/System.Net.Sockets/Socket.cs#L2240 + // -> the allocation is in EndPoint.Serialize() + // NOTE: technically this function isn't necessary. + // could just pass IPEndPointNonAlloc. + // still good for strong typing. + //public static int SendTo_NonAlloc( + // this Socket socket, + // byte[] buffer, + // int offset, + // int size, + // SocketFlags socketFlags, + // IPEndPointNonAlloc remoteEndPoint) + //{ + // EndPoint casted = remoteEndPoint; + // return socket.SendTo(buffer, offset, size, socketFlags, casted); + //} + } +} +#endif + + diff --git a/Fantasy/Fantays.Console/Runtime/Core/Helper/TimeHelper.cs b/Fantasy/Fantays.Console/Runtime/Core/Helper/TimeHelper.cs new file mode 100644 index 0000000..b0eb60a --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Helper/TimeHelper.cs @@ -0,0 +1,83 @@ +using System; +#if FANTASY_UNITY +using UnityEngine; +#endif + +namespace Fantasy.Helper +{ + /// + /// 提供与时间相关的帮助方法。 + /// + public static partial class TimeHelper + { + /// + /// 一小时的毫秒值。 + /// + public const long Hour = 3600000; + /// + /// 一分钟的毫秒值。 + /// + public const long Minute = 60000; + /// + /// 一天的毫秒值。 + /// + public const long OneDay = 86400000; + // 1970年1月1日的Ticks + private const long Epoch = 621355968000000000L; + /// + /// 获取当前时间的毫秒数,从1970年1月1日开始计算。 + /// + public static long Now => (DateTime.UtcNow.Ticks - Epoch) / 10000; +#if FANTASY_UNITY || FANTASY_CONSOLE + /// + /// 与服务器时间的偏差。 + /// + public static long TimeDiff; + /// + /// 获取当前服务器时间的毫秒数,加上与服务器时间的偏差。 + /// + public static long ServerNow => Now + TimeDiff; +#if FANTASY_UNITY + /// + /// 获取当前Unity运行的总时间的毫秒数。 + /// + public static long UnityNow => (long) (Time.time * 1000); +#endif +#endif + /// + /// 根据时间获取时间戳 + /// + public static long Transition(DateTime dateTime) + { + return (dateTime.ToUniversalTime().Ticks - Epoch) / 10000; + } + + /// + /// 根据时间获取 时间戳 + /// + public static long TransitionToSeconds(DateTime dateTime) + { + return (dateTime.ToUniversalTime().Ticks - Epoch) / 10000000; + } + + /// + /// 将毫秒数转换为日期时间。 + /// + /// 要转换的毫秒数。 + /// 转换后的日期时间。 + public static DateTime Transition(this long timeStamp) + { + return new DateTime(Epoch + timeStamp * 10000, DateTimeKind.Utc).ToUniversalTime(); + } + + /// + /// 将毫秒数转换为本地时间的日期时间。 + /// + /// 要转换的毫秒数。 + /// 转换后的本地时间的日期时间。 + public static DateTime TransitionLocal(this long timeStamp) + { + return new DateTime(Epoch + timeStamp * 10000, DateTimeKind.Utc).ToLocalTime(); + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Helper/UnityWebRequest/UnityWebRequestHelper.cs b/Fantasy/Fantays.Console/Runtime/Core/Helper/UnityWebRequest/UnityWebRequestHelper.cs new file mode 100644 index 0000000..dbc0334 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Helper/UnityWebRequest/UnityWebRequestHelper.cs @@ -0,0 +1,244 @@ +#if FANTASY_UNITY +using System; +using Fantasy.Async; +using UnityEngine; +using UnityEngine.Networking; + +namespace Fantasy.Unity +{ + /// + /// UnityWebRequest的帮助类 + /// + public static class UnityWebRequestHelper + { + /// + /// 获取一个文本 + /// + /// + /// + /// + public static FTask GetText(string url, FCancellationToken cancellationToken = null) + { + var task = FTask.Create(false); + var unityWebRequest = UnityWebRequest.Get(url); + var unityWebRequestAsyncOperation = unityWebRequest.SendWebRequest(); + + if (cancellationToken != null) + { + cancellationToken.Add(() => + { + unityWebRequest.Abort(); + task.SetResult(null); + }); + } + + unityWebRequestAsyncOperation.completed += operation => + { + if (unityWebRequest.result == UnityWebRequest.Result.Success) + { + var text = unityWebRequest.downloadHandler.text; + task.SetResult(text); + } + else + { + Log.Error(unityWebRequest.error); + task.SetResult(null); + } + }; + + return task; + } + + /// + /// 获取一个Sprite + /// + /// + /// + /// + public static FTask GetSprite(string url, FCancellationToken cancellationToken = null) + { + var task = FTask.Create(false); + var unityWebRequest = UnityWebRequestTexture.GetTexture(Uri.EscapeUriString(url)); + var unityWebRequestAsyncOperation = unityWebRequest.SendWebRequest(); + + if (cancellationToken != null) + { + cancellationToken.Add(() => + { + unityWebRequest.Abort(); + task.SetResult(null); + }); + } + + unityWebRequestAsyncOperation.completed += operation => + { + if (unityWebRequest.result == UnityWebRequest.Result.Success) + { + var texture = DownloadHandlerTexture.GetContent(unityWebRequest); + var sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), Vector2.one * 5, 1f); + task.SetResult(sprite); + } + else + { + Log.Error(unityWebRequest.error); + task.SetResult(null); + } + }; + + return task; + } + + /// + /// 获取一个Texture + /// + /// + /// + /// + public static FTask GetTexture(string url, FCancellationToken cancellationToken = null) + { + var task = FTask.Create(false); + var unityWebRequest = UnityWebRequestTexture.GetTexture(Uri.EscapeUriString(url)); + var unityWebRequestAsyncOperation = unityWebRequest.SendWebRequest(); + + if (cancellationToken != null) + { + cancellationToken.Add(() => + { + unityWebRequest.Abort(); + task.SetResult(null); + }); + } + + unityWebRequestAsyncOperation.completed += operation => + { + if (unityWebRequest.result == UnityWebRequest.Result.Success) + { + var texture = DownloadHandlerTexture.GetContent(unityWebRequest); + task.SetResult(texture); + } + else + { + Log.Error(unityWebRequest.error); + task.SetResult(null); + } + }; + + return task; + } + + /// + /// 获取Bytes + /// + /// + /// + /// + public static FTask GetBytes(string url, FCancellationToken cancellationToken = null) + { + var task = FTask.Create(false); + var unityWebRequest = UnityWebRequest.Get(url); + var unityWebRequestAsyncOperation = unityWebRequest.SendWebRequest(); + + if (cancellationToken != null) + { + cancellationToken.Add(() => + { + unityWebRequest.Abort(); + task.SetResult(null); + }); + } + + unityWebRequestAsyncOperation.completed += operation => + { + if (unityWebRequest.result == UnityWebRequest.Result.Success) + { + var bytes = unityWebRequest.downloadHandler.data; + task.SetResult(bytes); + } + else + { + Log.Error(unityWebRequest.error); + task.SetResult(null); + } + }; + + return task; + } + + /// + /// 获取AssetBundle + /// + /// + /// + /// + public static FTask GetAssetBundle(string url, FCancellationToken cancellationToken = null) + { + var task = FTask.Create(false); + var unityWebRequest = UnityWebRequestAssetBundle.GetAssetBundle(Uri.EscapeUriString(url)); + var unityWebRequestAsyncOperation = unityWebRequest.SendWebRequest(); + + if (cancellationToken != null) + { + cancellationToken.Add(() => + { + unityWebRequest.Abort(); + task.SetResult(null); + }); + } + + unityWebRequestAsyncOperation.completed += operation => + { + if (unityWebRequest.result == UnityWebRequest.Result.Success) + { + var assetBundle = DownloadHandlerAssetBundle.GetContent(unityWebRequest); + task.SetResult(assetBundle); + return; + } + + Log.Error(unityWebRequest.error); + task.SetResult(null); + }; + + return task; + } + + /// + /// 获取AudioClip + /// + /// + /// + /// + /// + public static FTask GetAudioClip(string url, AudioType audioType, FCancellationToken cancellationToken = null) + { + var task = FTask.Create(false); + var unityWebRequest = UnityWebRequestMultimedia.GetAudioClip(Uri.EscapeUriString(url), audioType); + var unityWebRequestAsyncOperation = unityWebRequest.SendWebRequest(); + + if (cancellationToken != null) + { + cancellationToken.Add(() => + { + unityWebRequest.Abort(); + task.SetResult(null); + }); + } + + unityWebRequestAsyncOperation.completed += operation => + { + if (unityWebRequest.result == UnityWebRequest.Result.Success) + { + var audioClip = DownloadHandlerAudioClip.GetContent(unityWebRequest); + task.SetResult(audioClip); + } + else + { + Log.Error(unityWebRequest.error); + task.SetResult(null); + } + }; + + return task; + } + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Helper/UnityWebRequest/UnityWebRequestHelper.cs.meta b/Fantasy/Fantays.Console/Runtime/Core/Helper/UnityWebRequest/UnityWebRequestHelper.cs.meta new file mode 100644 index 0000000..53b7c3c --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Helper/UnityWebRequest/UnityWebRequestHelper.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 4a679bf05117455388666f6d8cc35d7d +timeCreated: 1726022012 \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Helper/WebSocketHelper.cs b/Fantasy/Fantays.Console/Runtime/Core/Helper/WebSocketHelper.cs new file mode 100644 index 0000000..822db06 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Helper/WebSocketHelper.cs @@ -0,0 +1,38 @@ +using System; +using System.Runtime.CompilerServices; + +namespace Fantasy.Helper +{ + /// + /// WebSocket帮助类 + /// + public static partial class WebSocketHelper + { + /// + /// 根据字符串获取WebSocket的连接地址 + /// + /// 目标服务器地址格式为:127.0.0.1:2000 + /// 目标服务器是否为加密连接也就是https + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string GetWebSocketAddress(string address, bool isHttps) + { + var addressSplit = address.Split(':'); + if (addressSplit.Length != 2) + { + throw new FormatException("Invalid format"); + } + + var ipString = addressSplit[0]; + var portString = addressSplit[1]; + + if (!int.TryParse(portString, out var port) || port < 0 || port > 65535) + { + throw new FormatException("Invalid port number"); + } + + return isHttps ? $"wss://{ipString}:{portString}" : $"ws://{ipString}:{portString}"; + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Helper/WinPeriod.cs b/Fantasy/Fantays.Console/Runtime/Core/Helper/WinPeriod.cs new file mode 100644 index 0000000..a1a5aca --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Helper/WinPeriod.cs @@ -0,0 +1,24 @@ +using System.Runtime.InteropServices; + +namespace Fantasy.Helper +{ + /// + /// 精度设置 + /// + public static partial class WinPeriod + { + // 一般默认的精度不止1毫秒(不同操作系统有所不同),需要调用timeBeginPeriod与timeEndPeriod来设置精度 + [DllImport("winmm")] + private static extern void timeBeginPeriod(int t); + /// + /// 针对Windows平台设置精度 + /// + public static void Initialize() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + timeBeginPeriod(1); + } + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/IdFactory/Default/EntityIdStruct.cs b/Fantasy/Fantays.Console/Runtime/Core/IdFactory/Default/EntityIdStruct.cs new file mode 100644 index 0000000..ef30b18 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/IdFactory/Default/EntityIdStruct.cs @@ -0,0 +1,136 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Fantasy.Helper; + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + +namespace Fantasy.IdFactory +{ + /// + /// 表示一个唯一实体的ID。 + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + internal struct EntityIdStruct + { + // EntityId:39 + 16 + 18 = 64 + // +-------------------+-----------------------------+------------------------------------+ + // | time(30) 最大34年 | SceneId(16) 最多65535个Scene | sequence(18) 每秒每个进程能生产262143个 + // +-------------------+-----------------------------+------------------------------------+ + public uint Time { get; set; } + public uint SceneId { get; set; } + public uint Sequence { get; set; } + + public const uint MaskSequence = 0x3FFFF; + public const uint MaskSceneId = 0xFFFF; + public const uint MaskTime = 0x3FFFFFFF; + + /// + /// WorldEntityIdStruct(如果超过下面参数的设定该ID会失效)。 + /// + /// time不能超过1073741823 + /// sceneId不能超过65535 + /// sequence不能超过262143 + public EntityIdStruct(uint time, uint sceneId, uint sequence) + { + // 因为都是在配置表里拿到参数、所以这个不做边界判定、能节省一点点性能。 + Time = time; + SceneId = sceneId; + Sequence = sequence; + } + + public static implicit operator long(EntityIdStruct entityIdStruct) + { + ulong result = 0; + result |= entityIdStruct.Sequence; + result |= (ulong)entityIdStruct.SceneId << 18; + result |= (ulong)entityIdStruct.Time << 34; + return (long)result; + } + + public static implicit operator EntityIdStruct(long entityId) + { + var result = (ulong) entityId; + var entityIdStruct = new EntityIdStruct + { + Sequence = (uint)(result & MaskSequence) + }; + result >>= 18; + entityIdStruct.SceneId = (uint)(result & MaskSceneId); + result >>= 16; + entityIdStruct.Time = (uint)(result & MaskTime); + return entityIdStruct; + } + } + + public sealed class EntityIdFactory : IEntityIdFactory + { + private readonly ushort _sceneId; + + private uint _lastTime; + private uint _lastSequence; + private static readonly long Epoch1970 = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).Ticks / 10000; + private static readonly long EpochThisYear = new DateTime(2025, 1, 1, 0, 0, 0, DateTimeKind.Utc).Ticks / 10000 - Epoch1970; + + private EntityIdFactory() { } + + public EntityIdFactory(uint sceneId) + { + switch (sceneId) + { + case > 65535: + { + throw new NotSupportedException($"sceneId:{sceneId} cannot be greater than 255255"); + } + default: + { + _sceneId = (ushort)sceneId; + break; + } + } + } + + public long Create + { + get + { + var time = (uint)((TimeHelper.Now - EpochThisYear) / 1000); + + if (time > _lastTime) + { + _lastTime = time; + _lastSequence = 0; + } + else if (++_lastSequence > EntityIdStruct.MaskSequence - 1) + { + _lastTime++; + _lastSequence = 0; + } + + return new EntityIdStruct(time, _sceneId, _lastSequence); + } + } + } + + public sealed class EntityIdFactoryTool : IIdFactoryTool + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint GetTime(ref long entityId) + { + var result = (ulong)entityId >> 34; + return (uint)(result & EntityIdStruct.MaskTime); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint GetSceneId(ref long entityId) + { + var result = (ulong)entityId >> 18; + return (uint)(result & EntityIdStruct.MaskSceneId); + } + + public byte GetWorldId(ref long entityId) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/IdFactory/Default/RuntimeIdStruct.cs b/Fantasy/Fantays.Console/Runtime/Core/IdFactory/Default/RuntimeIdStruct.cs new file mode 100644 index 0000000..a5d5e10 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/IdFactory/Default/RuntimeIdStruct.cs @@ -0,0 +1,137 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Fantasy.Helper; +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + +namespace Fantasy.IdFactory +{ + /// + /// 表示一个运行时的ID。 + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct RuntimeIdStruct + { + // RuntimeId:23 + 8 + 8 + 25 = 64 + // +-------------------+-----------------------------+--------------------------------------+ + // | time(23) 最大60天 | SceneId(16) 最多65535个Scene | sequence(25) 每秒每个进程能生产33554431个 + // +-------------------+-----------------------------+--------------------------------------+ + public uint Time { get; private set; } + public uint SceneId { get; private set; } + public uint Sequence { get; private set; } + + public const uint MaskSequence = 0x1FFFFFF; + public const uint MaskSceneId = 0xFFFF; + public const uint MaskTime = 0x7FFFFF; + + /// + /// RuntimeIdStruct(如果超过下面参数的设定该ID会失效)。 + /// + /// time不能超过8388607 + /// sceneId不能超过65535 + /// sequence不能超过33554431 + public RuntimeIdStruct(uint time, uint sceneId, uint sequence) + { + // 因为都是在配置表里拿到参数、所以这个不做边界判定、能节省一点点性能。 + Time = time; + SceneId = sceneId; + Sequence = sequence; + } + + public static implicit operator long(RuntimeIdStruct runtimeIdStruct) + { + ulong result = runtimeIdStruct.Sequence; + result |= (ulong)runtimeIdStruct.SceneId << 25; + result |= (ulong)runtimeIdStruct.Time << 41; + return (long)result; + } + + public static implicit operator RuntimeIdStruct(long runtimeId) + { + var result = (ulong)runtimeId; + var runtimeIdStruct = new RuntimeIdStruct + { + Sequence = (uint)(result & MaskSequence) + }; + result >>= 25; + runtimeIdStruct.SceneId = (byte)(result & MaskSceneId); + result >>= 16; + runtimeIdStruct.Time = (uint)(result & MaskTime); + return runtimeIdStruct; + } + } + + public sealed class RuntimeIdFactory : IRuntimeIdFactory + { + private readonly uint _sceneId; + + private uint _lastTime; + private uint _lastSequence; + private readonly long _epochNow; + private readonly long _epoch1970 = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).Ticks / 10000; + + private RuntimeIdFactory() { } + + public RuntimeIdFactory(uint sceneId) : this(TimeHelper.Now, sceneId) { } + + public RuntimeIdFactory(long epochNow, uint sceneId) + { + switch (sceneId) + { + case > 65535: + { + throw new NotSupportedException($"sceneId:{sceneId} cannot be greater than 255255"); + } + default: + { + _sceneId = (ushort)sceneId; + _epochNow = epochNow - _epoch1970; + break; + } + } + } + + public long Create + { + get + { + var time = (uint)((TimeHelper.Now - _epochNow) / 1000); + + if (time > _lastTime) + { + _lastTime = time; + _lastSequence = 0; + } + else if (++_lastSequence > RuntimeIdStruct.MaskSequence - 1) + { + _lastTime++; + _lastSequence = 0; + } + + return new RuntimeIdStruct(time, _sceneId, _lastSequence); + } + } + } + + public sealed class RuntimeIdFactoryTool : IIdFactoryTool + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint GetTime(ref long runtimeId) + { + var result = (ulong)runtimeId >> 41; + return (uint)(result & RuntimeIdStruct.MaskTime); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint GetSceneId(ref long runtimeId) + { + var result = (ulong)runtimeId >> 25; + return (uint)(result & RuntimeIdStruct.MaskSceneId); + } + + public byte GetWorldId(ref long entityId) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/IdFactory/IdFactoryHelper.cs b/Fantasy/Fantays.Console/Runtime/Core/IdFactory/IdFactoryHelper.cs new file mode 100644 index 0000000..4251d53 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/IdFactory/IdFactoryHelper.cs @@ -0,0 +1,132 @@ +using System; +using System.Runtime.CompilerServices; +using Fantasy.Helper; +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. + +namespace Fantasy.IdFactory +{ + /// + /// Id生成器帮助类 + /// + public static class IdFactoryHelper + { + private static IdFactoryType _idFactoryType = IdFactoryType.World; + + /// + /// EntityId工具 + /// + public static IIdFactoryTool EntityIdTool { get; private set; } = new WorldEntityIdFactoryTool(); + + /// + /// RuntimeId工具 + /// + public static IIdFactoryTool RuntimeIdTool { get; private set; } = new WorldRuntimeIdFactoryTool(); + + /// + /// 初始化 + /// + /// + public static void Initialize(IdFactoryType idFactoryType) + { + _idFactoryType = idFactoryType; + + switch (_idFactoryType) + { + case IdFactoryType.Default: + { + EntityIdTool = new EntityIdFactoryTool(); + RuntimeIdTool = new RuntimeIdFactoryTool(); + return; + } + case IdFactoryType.World: + { + EntityIdTool = new WorldEntityIdFactoryTool(); + RuntimeIdTool = new WorldRuntimeIdFactoryTool(); + return; + } + } + } + + internal static IEntityIdFactory EntityIdFactory(uint sceneId, byte worldId) + { + switch (_idFactoryType) + { + case IdFactoryType.Default: + { + return new EntityIdFactory(sceneId); + } + case IdFactoryType.World: + { + return new WorldEntityIdFactory(sceneId, worldId); + } + default: + { + throw new NotSupportedException($"IdFactoryType {_idFactoryType} is not supported."); + } + } + } + + internal static IRuntimeIdFactory RuntimeIdFactory(long epochNow, uint sceneId, byte worldId) + { + switch (_idFactoryType) + { + case IdFactoryType.Default: + { + return new RuntimeIdFactory(sceneId); + } + case IdFactoryType.World: + { + if (epochNow == 0) + { + epochNow = TimeHelper.Now; + } + + return new WorldRuntimeIdFactory(epochNow, sceneId, worldId); + } + default: + { + throw new NotSupportedException($"IdFactoryType {_idFactoryType} is not supported."); + } + } + } + + internal static long EntityId(uint time, uint sceneId, byte wordId, uint sequence) + { + switch (_idFactoryType) + { + case IdFactoryType.Default: + { + return new EntityIdStruct(time, sceneId, sequence); + } + case IdFactoryType.World: + { + return new WorldEntityIdStruct(time, sceneId, wordId, sequence); + } + default: + { + throw new NotSupportedException($"IdFactoryType {_idFactoryType} is not supported."); + } + } + } + + internal static long RuntimeId(uint time, uint sceneId, byte wordId, uint sequence) + { + switch (_idFactoryType) + { + case IdFactoryType.Default: + { + return new RuntimeIdStruct(time, sceneId, sequence); + } + case IdFactoryType.World: + { + return new WorldRuntimeIdStruct(time, sceneId, wordId, sequence); + } + default: + { + throw new NotSupportedException($"IdFactoryType {_idFactoryType} is not supported."); + } + } + } + } +} + diff --git a/Fantasy/Fantays.Console/Runtime/Core/IdFactory/IdFactoryType.cs b/Fantasy/Fantays.Console/Runtime/Core/IdFactory/IdFactoryType.cs new file mode 100644 index 0000000..d465b5b --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/IdFactory/IdFactoryType.cs @@ -0,0 +1,23 @@ +namespace Fantasy.IdFactory +{ + /// + /// ID生成器规则 + /// + public enum IdFactoryType + { + /// + /// 空。 + /// + None = 0, + /// + /// 默认生成器 + /// Scene最大为65535个。 + /// + Default = 1, + /// + /// ID中包含World,使用这种方式可以不用管理合区的ID重复的问题。 + /// 但Scene的数量也会限制到255个。 + /// + World = 2 + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/IdFactory/Interface/IIdFactory.cs b/Fantasy/Fantays.Console/Runtime/Core/IdFactory/Interface/IIdFactory.cs new file mode 100644 index 0000000..d71c15b --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/IdFactory/Interface/IIdFactory.cs @@ -0,0 +1,24 @@ +namespace Fantasy.IdFactory +{ + /// + /// EntityId生成器接口类 + /// + public interface IEntityIdFactory + { + /// + /// 创建一个新的Id + /// + public long Create { get; } + } + + /// + /// RuntimeId生成器接口类 + /// + public interface IRuntimeIdFactory + { + /// + /// 创建一个新的Id + /// + public long Create { get; } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/IdFactory/Interface/IIdFactoryTool.cs b/Fantasy/Fantays.Console/Runtime/Core/IdFactory/Interface/IIdFactoryTool.cs new file mode 100644 index 0000000..8dd1e99 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/IdFactory/Interface/IIdFactoryTool.cs @@ -0,0 +1,27 @@ +namespace Fantasy.IdFactory +{ + /// + /// Id扩展工具接口 + /// + public interface IIdFactoryTool + { + /// + /// 获得创建时间 + /// + /// + /// + public uint GetTime(ref long entityId); + /// + /// 获得SceneId + /// + /// + /// + public uint GetSceneId(ref long entityId); + /// + /// 获得WorldId + /// + /// + /// + public byte GetWorldId(ref long entityId); + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/IdFactory/World/WorldEntityIdFactory.cs b/Fantasy/Fantays.Console/Runtime/Core/IdFactory/World/WorldEntityIdFactory.cs new file mode 100644 index 0000000..dbba88f --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/IdFactory/World/WorldEntityIdFactory.cs @@ -0,0 +1,153 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Fantasy.Helper; + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + +namespace Fantasy.IdFactory +{ + /// + /// 表示一个唯一实体的ID。 + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct WorldEntityIdStruct + { + // EntityId:39 + 8 + 8 + 18 = 64 + // +-------------------+--------------------------+-----------------------+------------------------------------+ + // | time(30) 最大34年 | SceneId(8) 最多255个Scene | WordId(8) 最多255个世界 | sequence(18) 每秒每个进程能生产262143个 + // +-------------------+--------------------------+-----------------------+------------------------------------+ + public uint Time { get; private set; } + public uint SceneId { get; private set; } + public byte WordId { get; private set; } + public uint Sequence { get; private set; } + + public const uint MaskSequence = 0x3FFFF; + public const uint MaskSceneId = 0xFF; + public const uint MaskWordId = 0xFF; + public const uint MaskTime = 0x3FFFFFFF; + + /// + /// WorldEntityIdStruct(如果超过下面参数的设定该ID会失效)。 + /// + /// time不能超过1073741823 + /// sceneId不能超过255 + /// wordId不能超过255 + /// sequence不能超过262143 + public WorldEntityIdStruct(uint time, uint sceneId, byte wordId, uint sequence) + { + // 因为都是在配置表里拿到参数、所以这个不做边界判定、能节省一点点性能。 + Time = time; + SceneId = sceneId; + WordId = wordId; + Sequence = sequence; + } + + public static implicit operator long(WorldEntityIdStruct entityIdStruct) + { + ulong result = 0; + result |= entityIdStruct.Sequence; + result |= (ulong)entityIdStruct.WordId << 18; + result |= (ulong)(entityIdStruct.SceneId % (entityIdStruct.WordId * 1000)) << 26; + result |= (ulong)entityIdStruct.Time << 34; + return (long)result; + } + + public static implicit operator WorldEntityIdStruct(long entityId) + { + var result = (ulong) entityId; + var entityIdStruct = new WorldEntityIdStruct + { + Sequence = (uint)(result & MaskSequence) + }; + result >>= 18; + entityIdStruct.WordId = (byte)(result & MaskWordId); + result >>= 8; + entityIdStruct.SceneId = (uint)(result & MaskSceneId) + (uint)entityIdStruct.WordId * 1000; + result >>= 8; + entityIdStruct.Time = (uint)(result & MaskTime); + return entityIdStruct; + } + } + + public sealed class WorldEntityIdFactory : IEntityIdFactory + { + private readonly uint _sceneId; + private readonly byte _worldId; + + private uint _lastTime; + private uint _lastSequence; + private static readonly long Epoch1970 = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).Ticks / 10000; + private static readonly long EpochThisYear = new DateTime(2025, 1, 1, 0, 0, 0, DateTimeKind.Utc).Ticks / 10000 - Epoch1970; + + private WorldEntityIdFactory() { } + + public WorldEntityIdFactory(uint sceneId, byte worldId) + { + switch (sceneId) + { + case > 255255: + { + throw new NotSupportedException($"sceneId:{sceneId} cannot be greater than 255255"); + } + case < 1001: + { + throw new NotSupportedException($"sceneId:{sceneId} cannot be less than 1001"); + } + default: + { + _sceneId = sceneId; + _worldId = worldId; + break; + } + } + } + + public long Create + { + get + { + var time = (uint)((TimeHelper.Now - EpochThisYear) / 1000); + + if (time > _lastTime) + { + _lastTime = time; + _lastSequence = 0; + } + else if (++_lastSequence > WorldEntityIdStruct.MaskSequence - 1) + { + _lastTime++; + _lastSequence = 0; + } + + return new WorldEntityIdStruct(time, _sceneId, _worldId, _lastSequence); + } + } + } + + public sealed class WorldEntityIdFactoryTool : IIdFactoryTool + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint GetTime(ref long entityId) + { + var result = (ulong)entityId >> 34; + return (uint)(result & WorldEntityIdStruct.MaskTime); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint GetSceneId(ref long entityId) + { + var result = (ulong)entityId >> 18; + var worldId = (uint)(result & WorldEntityIdStruct.MaskWordId) * 1000; + result >>= 8; + return (uint)(result & WorldEntityIdStruct.MaskSceneId) + worldId; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public byte GetWorldId(ref long entityId) + { + var result = (ulong)entityId >> 18; + return (byte)(result & WorldEntityIdStruct.MaskWordId); + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/IdFactory/World/WorldRuntimeIdFactory.cs b/Fantasy/Fantays.Console/Runtime/Core/IdFactory/World/WorldRuntimeIdFactory.cs new file mode 100644 index 0000000..a5d582b --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/IdFactory/World/WorldRuntimeIdFactory.cs @@ -0,0 +1,155 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Fantasy.Helper; + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + +namespace Fantasy.IdFactory +{ + /// + /// 表示一个运行时的ID。 + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct WorldRuntimeIdStruct + { + // RuntimeId:23 + 8 + 8 + 25 = 64 + // +-------------------+--------------------------+-----------------------+--------------------------------------+ + // | time(23) 最大60天 | SceneId(8) 最多255个Scene | WordId(8) 最多255个世界 | sequence(25) 每秒每个进程能生产33554431个 + // +-------------------+--------------------------+-----------------------+--------------------------------------+ + public uint Time { get; private set; } + public uint SceneId { get; private set; } + public byte WordId { get; private set; } + public uint Sequence { get; private set; } + + public const uint MaskSequence = 0x1FFFFFF; + public const uint MaskSceneId = 0xFF; + public const uint MaskWordId = 0xFF; + public const uint MaskTime = 0x7FFFFF; + + /// + /// WorldRuntimeIdStruct(如果超过下面参数的设定该ID会失效)。 + /// + /// time不能超过8388607 + /// sceneId不能超过255 + /// wordId不能超过255 + /// sequence不能超过33554431 + public WorldRuntimeIdStruct(uint time, uint sceneId, byte wordId, uint sequence) + { + // 因为都是在配置表里拿到参数、所以这个不做边界判定、能节省一点点性能。 + Time = time; + SceneId = sceneId; + WordId = wordId; + Sequence = sequence; + } + + public static implicit operator long(WorldRuntimeIdStruct runtimeIdStruct) + { + ulong result = runtimeIdStruct.Sequence; + result |= (ulong)runtimeIdStruct.WordId << 25; + result |= (ulong)(runtimeIdStruct.SceneId % (runtimeIdStruct.WordId * 1000)) << 33; + result |= (ulong)runtimeIdStruct.Time << 41; + return (long)result; + } + + public static implicit operator WorldRuntimeIdStruct(long runtimeId) + { + var result = (ulong)runtimeId; + var runtimeIdStruct = new WorldRuntimeIdStruct + { + Sequence = (uint)(result & MaskSequence) + }; + result >>= 25; + runtimeIdStruct.WordId = (byte)(result & MaskWordId); + result >>= 8; + runtimeIdStruct.SceneId = (uint)(result & MaskSceneId) + (uint)runtimeIdStruct.WordId * 1000; + result >>= 8; + runtimeIdStruct.Time = (uint)(result & MaskTime); + return runtimeIdStruct; + } + } + + public sealed class WorldRuntimeIdFactory : IRuntimeIdFactory + { + private readonly uint _sceneId; + private readonly byte _worldId; + + private uint _lastTime; + private uint _lastSequence; + private readonly long _epochNow; + private readonly long _epoch1970 = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).Ticks / 10000; + + private WorldRuntimeIdFactory() { } + + public WorldRuntimeIdFactory(uint sceneId, byte worldId) : this(TimeHelper.Now, sceneId, worldId) { } + + public WorldRuntimeIdFactory(long epochNow, uint sceneId, byte worldId) + { + switch (sceneId) + { + case > 255255: + { + throw new NotSupportedException($"sceneId:{sceneId} cannot be greater than 255255"); + } + case < 1001: + { + throw new NotSupportedException($"sceneId:{sceneId} cannot be less than 1001"); + } + default: + { + _sceneId = sceneId; + _worldId = worldId; + _epochNow = epochNow - _epoch1970; + break; + } + } + } + + public long Create + { + get + { + var time = (uint)((TimeHelper.Now - _epochNow) / 1000); + + if (time > _lastTime) + { + _lastTime = time; + _lastSequence = 0; + } + else if (++_lastSequence > WorldRuntimeIdStruct.MaskSequence - 1) + { + _lastTime++; + _lastSequence = 0; + } + + return new WorldRuntimeIdStruct(time, _sceneId, _worldId, _lastSequence); + } + } + } + + public sealed class WorldRuntimeIdFactoryTool : IIdFactoryTool + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint GetTime(ref long runtimeId) + { + var result = (ulong)runtimeId >> 41; + return (uint)(result & WorldRuntimeIdStruct.MaskTime); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint GetSceneId(ref long runtimeId) + { + var result = (ulong)runtimeId >> 25; + var worldId = (uint)(result & WorldRuntimeIdStruct.MaskWordId) * 1000; + result >>= 8; + return (uint)(result & WorldRuntimeIdStruct.MaskSceneId) + worldId; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public byte GetWorldId(ref long runtimeId) + { + var result = (ulong)runtimeId >> 25; + return (byte)(result & WorldRuntimeIdStruct.MaskWordId); + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/InnerErrorCode.cs b/Fantasy/Fantays.Console/Runtime/Core/InnerErrorCode.cs new file mode 100644 index 0000000..e549a22 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/InnerErrorCode.cs @@ -0,0 +1,66 @@ +namespace Fantasy.Network +{ + /// + /// 定义 Fantasy 框架中的内部错误代码。 + /// + public class InnerErrorCode + { + private InnerErrorCode() { } + /// + /// 表示 Rpc 消息发送失败的错误代码。 + /// + public const uint ErrRpcFail = 100000002; + /// + /// 表示未找到 Route 消息的错误代码。 + /// + public const uint ErrNotFoundRoute = 100000003; + /// + /// 表示发送 Route 消息超时的错误代码。 + /// + public const uint ErrRouteTimeout = 100000004; + /// + /// 表示未找到实体的错误代码。 + /// + public const uint ErrEntityNotFound = 100000008; + /// + /// 表示传送过程中发生错误的错误代码。 + /// + public const uint ErrTransfer = 100000009; + /// + /// 表示连接Roaming时候已经存在同RoamingType的Roaming了。 + /// + public const uint ErrLinkRoamingAlreadyExists = 100000009; + /// + /// 表示连接Roaming时候在漫游终端已经存在同Id的终端。 + /// + public const uint ErrAddRoamingTerminalAlreadyExists = 100000010; + /// + /// 表示未找到 Roaming 消息的错误代码。 + /// + public const uint ErrNotFoundRoaming = 100000011; + /// + /// 表示发送 Roaming 消息超时的错误代码。 + /// + public const uint ErrRoamingTimeout = 100000012; + /// + /// 表示再锁定 Roaming 消息的时候没有找到对应的Session错误代码。 + /// + public const uint ErrLockTerminusIdNotFoundSession = 100000013; + /// + /// 表示再锁定 Roaming 消息的时候没有找到对应的RoamingType错误代码。 + /// + public const uint ErrLockTerminusIdNotFoundRoamingType = 100000014; + /// + /// 表示再解除锁定 Roaming 消息的时候没有找到对应的Session错误代码。 + /// + public const uint ErrUnLockTerminusIdNotFoundSession = 100000015; + /// + /// 表示再解除锁定 Roaming 消息的时候没有找到对应的RoamingType错误代码。 + /// + public const uint ErrUnLockTerminusIdNotFoundRoamingType = 100000016; + /// + /// 表示再传送 Terminus 时对应的错误代码。 + /// + public const uint ErrTerminusStartTransfer = 100000017; + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Log/ConsoleLog.cs b/Fantasy/Fantays.Console/Runtime/Core/Log/ConsoleLog.cs new file mode 100644 index 0000000..0d57e22 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Log/ConsoleLog.cs @@ -0,0 +1,144 @@ +#if FANTASY_NET +using Fantasy.Platform.Net; +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + +namespace Fantasy; + +/// +/// 标准的控制台Log +/// +public sealed class ConsoleLog : ILog +{ + /// + /// 初始化方法 + /// + /// + public void Initialize(ProcessMode processMode) { } + + /// + /// 记录跟踪级别的日志消息。 + /// + /// 日志消息。 + public void Trace(string message) + { + Console.ForegroundColor = ConsoleColor.White; + Console.WriteLine(message); + } + + /// + /// 记录警告级别的日志消息。 + /// + /// 日志消息。 + public void Warning(string message) + { + Console.ForegroundColor = ConsoleColor.Yellow; + Console.WriteLine(message); + } + + /// + /// 记录信息级别的日志消息。 + /// + /// 日志消息。 + public void Info(string message) + { + Console.ForegroundColor = ConsoleColor.Gray; + Console.WriteLine(message); + } + + /// + /// 记录调试级别的日志消息。 + /// + /// 日志消息。 + public void Debug(string message) + { + Console.ForegroundColor = ConsoleColor.DarkGreen; + Console.WriteLine(message); + } + + /// + /// 记录错误级别的日志消息。 + /// + /// 日志消息。 + public void Error(string message) + { + Console.ForegroundColor = ConsoleColor.DarkRed; + Console.WriteLine(message); + } + + /// + /// 记录严重错误级别的日志消息。 + /// + /// 日志消息。 + public void Fatal(string message) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine(message); + } + + /// + /// 记录跟踪级别的格式化日志消息。 + /// + /// 日志消息模板。 + /// 格式化参数。 + public void Trace(string message, params object[] args) + { + Console.ForegroundColor = ConsoleColor.White; + Console.WriteLine(message, args); + } + + /// + /// 记录警告级别的格式化日志消息。 + /// + /// 日志消息模板。 + /// 格式化参数。 + public void Warning(string message, params object[] args) + { + Console.ForegroundColor = ConsoleColor.Yellow; + Console.WriteLine(message, args); + } + + /// + /// 记录信息级别的格式化日志消息。 + /// + /// 日志消息模板。 + /// 格式化参数。 + public void Info(string message, params object[] args) + { + Console.ForegroundColor = ConsoleColor.Gray; + Console.WriteLine(message, args); + } + + /// + /// 记录调试级别的格式化日志消息。 + /// + /// 日志消息模板。 + /// 格式化参数。 + public void Debug(string message, params object[] args) + { + Console.ForegroundColor = ConsoleColor.DarkGreen; + Console.WriteLine(message, args); + } + + /// + /// 记录错误级别的格式化日志消息。 + /// + /// 日志消息模板。 + /// 格式化参数。 + public void Error(string message, params object[] args) + { + Console.ForegroundColor = ConsoleColor.DarkRed; + Console.WriteLine(message, args); + } + + /// + /// 记录严重错误级别的格式化日志消息。 + /// + /// 日志消息模板。 + /// 格式化参数。 + public void Fatal(string message, params object[] args) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine(message, args); + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Log/ILog.cs b/Fantasy/Fantays.Console/Runtime/Core/Log/ILog.cs new file mode 100644 index 0000000..b01e857 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Log/ILog.cs @@ -0,0 +1,74 @@ +#if FANTASY_NET +using Fantasy.Platform.Net; +#endif +namespace Fantasy +{ + /// + /// 定义日志记录功能的接口。 + /// + public interface ILog + { +#if FANTASY_NET + /// + /// 初始化 + /// + /// + void Initialize(ProcessMode processMode); +#endif + /// + /// 记录跟踪级别的日志消息。 + /// + /// 日志消息。 + void Trace(string message); + /// + /// 记录警告级别的日志消息。 + /// + /// 日志消息。 + void Warning(string message); + /// + /// 记录信息级别的日志消息。 + /// + /// 日志消息。 + void Info(string message); + /// + /// 记录调试级别的日志消息。 + /// + /// 日志消息。 + void Debug(string message); + /// + /// 记录错误级别的日志消息。 + /// + /// 日志消息。 + void Error(string message); + /// + /// 记录跟踪级别的格式化日志消息。 + /// + /// 日志消息模板。 + /// 格式化参数。 + void Trace(string message, params object[] args); + /// + /// 记录警告级别的格式化日志消息。 + /// + /// 日志消息模板。 + /// 格式化参数。 + void Warning(string message, params object[] args); + /// + /// 记录信息级别的格式化日志消息。 + /// + /// 日志消息模板。 + /// 格式化参数。 + void Info(string message, params object[] args); + /// + /// 记录调试级别的格式化日志消息。 + /// + /// 日志消息模板。 + /// 格式化参数。 + void Debug(string message, params object[] args); + /// + /// 记录错误级别的格式化日志消息。 + /// + /// 日志消息模板。 + /// 格式化参数。 + void Error(string message, params object[] args); + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Log/Log.cs b/Fantasy/Fantays.Console/Runtime/Core/Log/Log.cs new file mode 100644 index 0000000..8069fff --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Log/Log.cs @@ -0,0 +1,189 @@ +using System; +using System.Diagnostics; +#if FANTASY_NET +using Fantasy.Platform.Net; +#endif + +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. + +namespace Fantasy +{ + /// + /// 提供日志记录功能的静态类。 + /// + public static class Log + { + private static ILog _logCore; + private static bool _isRegister; +#if FANTASY_NET + /// + /// 初始化Log系统 + /// + public static void Initialize() + { + if (!_isRegister) + { + Register(new ConsoleLog()); + return; + } + + var processMode = ProcessMode.None; + + switch (ProcessDefine.Options.Mode) + { + case "Develop": + { + processMode = ProcessMode.Develop; + break; + } + case "Release": + { + processMode = ProcessMode.Release; + break; + } + } + + _logCore.Initialize(processMode); + } +#endif + /// + /// 注册一个日志系统 + /// + /// + public static void Register(ILog log) + { + if (_isRegister) + { + return; + } + + _logCore = log; + _isRegister = true; + } + + /// + /// 记录跟踪级别的日志消息。 + /// + /// 日志消息。 + public static void Trace(string msg) + { + var st = new StackTrace(1, true); + _logCore.Trace($"{msg}\n{st}"); + } + + /// + /// 记录调试级别的日志消息。 + /// + /// 日志消息。 + public static void Debug(string msg) + { + _logCore.Debug(msg); + } + + /// + /// 记录信息级别的日志消息。 + /// + /// 日志消息。 + public static void Info(string msg) + { + _logCore.Info(msg); + } + + /// + /// 记录跟踪级别的日志消息,并附带调用栈信息。 + /// + /// 日志消息。 + public static void TraceInfo(string msg) + { + var st = new StackTrace(1, true); + _logCore.Trace($"{msg}\n{st}"); + } + + /// + /// 记录警告级别的日志消息。 + /// + /// 日志消息。 + public static void Warning(string msg) + { + _logCore.Warning(msg); + } + + /// + /// 记录错误级别的日志消息,并附带调用栈信息。 + /// + /// 日志消息。 + public static void Error(string msg) + { + var st = new StackTrace(1, true); + _logCore.Error($"{msg}\n{st}"); + } + + /// + /// 记录异常的错误级别的日志消息,并附带调用栈信息。 + /// + /// 异常对象。 + public static void Error(Exception e) + { + if (e.Data.Contains("StackTrace")) + { + _logCore.Error($"{e.Data["StackTrace"]}\n{e}"); + return; + } + var str = e.ToString(); + _logCore.Error(str); + } + + /// + /// 记录跟踪级别的格式化日志消息,并附带调用栈信息。 + /// + /// 日志消息模板。 + /// 格式化参数。 + public static void Trace(string message, params object[] args) + { + var st = new StackTrace(1, true); + _logCore.Trace($"{string.Format(message, args)}\n{st}"); + } + + /// + /// 记录警告级别的格式化日志消息。 + /// + /// 日志消息模板。 + /// 格式化参数。 + public static void Warning(string message, params object[] args) + { + _logCore.Warning(string.Format(message, args)); + } + + /// + /// 记录信息级别的格式化日志消息。 + /// + /// 日志消息模板。 + /// 格式化参数。 + public static void Info(string message, params object[] args) + { + _logCore.Info(string.Format(message, args)); + } + + /// + /// 记录调试级别的格式化日志消息。 + /// + /// 日志消息模板。 + /// 格式化参数。 + public static void Debug(string message, params object[] args) + { + _logCore.Debug(string.Format(message, args)); + } + + /// + /// 记录错误级别的格式化日志消息,并附带调用栈信息。 + /// + /// 日志消息模板。 + /// 格式化参数。 + public static void Error(string message, params object[] args) + { + var st = new StackTrace(1, true); + var s = string.Format(message, args) + '\n' + st; + _logCore.Error(s); + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Log/UnityLog.cs b/Fantasy/Fantays.Console/Runtime/Core/Log/UnityLog.cs new file mode 100644 index 0000000..df95444 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Log/UnityLog.cs @@ -0,0 +1,74 @@ +using System.Collections.Generic; +#if UNITY_EDITOR +using System; +using System.Reflection; +using System.Text.RegularExpressions; +using UnityEditor; +using UnityEditor.Callbacks; +using UnityEditorInternal; +#else +using System; +#endif + +#if FANTASY_UNITY +namespace Fantasy +{ + public class UnityLog : ILog + { + public void Trace(string msg) + { + UnityEngine.Debug.Log(msg); + } + + public void Debug(string msg) + { + UnityEngine.Debug.Log(msg); + } + + public void Info(string msg) + { + UnityEngine.Debug.Log(msg); + } + + public void Warning(string msg) + { + UnityEngine.Debug.LogWarning(msg); + } + + public void Error(string msg) + { + UnityEngine.Debug.LogError(msg); + } + + public void Error(Exception e) + { + UnityEngine.Debug.LogException(e); + } + + public void Trace(string message, params object[] args) + { + UnityEngine.Debug.LogFormat(message, args); + } + + public void Warning(string message, params object[] args) + { + UnityEngine.Debug.LogWarningFormat(message, args); + } + + public void Info(string message, params object[] args) + { + UnityEngine.Debug.LogFormat(message, args); + } + + public void Debug(string message, params object[] args) + { + UnityEngine.Debug.LogFormat(message, args); + } + + public void Error(string message, params object[] args) + { + UnityEngine.Debug.LogErrorFormat(message, args); + } + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/LowLevel/FantasyMemory.cs b/Fantasy/Fantays.Console/Runtime/Core/LowLevel/FantasyMemory.cs new file mode 100644 index 0000000..cadfd16 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/LowLevel/FantasyMemory.cs @@ -0,0 +1,267 @@ +#if FANTASY_NET +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using mimalloc; +#if NET7_0_OR_GREATER +using System.Runtime.Intrinsics; +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +#endif + +namespace Fantasy.LowLevel +{ + public static unsafe class FantasyMemory + { + private static delegate* managed _alloc; + private static delegate* managed _allocZeroed; + private static delegate* managed _free; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Initialize() + { + // KCP 使用 FantasyMemory + kcp.KCP.ikcp_allocator(&Alloc, &Free); + + try + { + _ = MiMalloc.mi_version(); + } + catch + { + Log.Info("mimalloc的二进制文件丢失"); + return; + } + + try + { + var ptr = MiMalloc.mi_malloc(MiMalloc.MI_SMALL_SIZE_MAX); + MiMalloc.mi_free(ptr); + } + catch + { + Log.Info("mimalloc权限不足,\r\n可能的问题:\r\n1. 禁止了虚拟内存分配 -> 允许虚拟内存分配;\r\n2. 没有硬盘读写权限 -> 管理员获取所有权限"); + return; + } + + Custom(&MiAlloc, &MiAllocZeroed, &MiFree); + + return; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static void* MiAlloc(nuint size) => MiMalloc.mi_malloc(size); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static void* MiAllocZeroed(nuint size) + { + var ptr = MiAlloc(size); + Unsafe.InitBlockUnaligned(ptr, 0, (uint)size); + return ptr; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static void MiFree(void* ptr) => MiMalloc.mi_free(ptr); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Custom(delegate* alloc, delegate* allocZeroed, delegate* free) + { + _alloc = alloc; + _allocZeroed = allocZeroed; + _free = free; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static nuint Align(nuint size) => AlignUp(size, (nuint)sizeof(nint)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static nuint AlignUp(nuint size, nuint alignment) => (size + (alignment - 1)) & ~(alignment - 1); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static nuint AlignDown(nuint size, nuint alignment) => size - (size & (alignment - 1)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void* Alloc(nuint byteCount) + { + if (_alloc != null) + return _alloc(byteCount); +#if NET6_0_OR_GREATER + return NativeMemory.Alloc(byteCount); +#else + return (void*)Marshal.AllocHGlobal((nint)byteCount); +#endif + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void* AllocZeroed(nuint byteCount) + { + if (_allocZeroed != null) + return _allocZeroed(byteCount); + void* ptr; + if (_alloc != null) + { + ptr = _alloc(byteCount); + Unsafe.InitBlockUnaligned(ptr, 0, (uint)byteCount); + return ptr; + } +#if NET6_0_OR_GREATER + return NativeMemory.AllocZeroed(byteCount, 1); +#else + ptr = (void*)Marshal.AllocHGlobal((nint)byteCount); + Unsafe.InitBlockUnaligned(ptr, 0, (uint)byteCount); + return ptr; +#endif + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Free(void* ptr) + { + if (_free != null) + { + _free(ptr); + return; + } +#if NET6_0_OR_GREATER + NativeMemory.Free(ptr); +#else + Marshal.FreeHGlobal((nint)ptr); +#endif + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Copy(void* destination, void* source, nuint byteCount) => Unsafe.CopyBlockUnaligned(destination, source, (uint)byteCount); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Move(void* destination, void* source, nuint byteCount) => Buffer.MemoryCopy(source, destination, byteCount, byteCount); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Set(void* startAddress, byte value, nuint byteCount) => Unsafe.InitBlockUnaligned(startAddress, value, (uint)byteCount); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool Compare(void* left, void* right, nuint byteCount) + { + ref var first = ref *(byte*)left; + ref var second = ref *(byte*)right; + if (byteCount >= (nuint)sizeof(nuint)) + { + if (!Unsafe.AreSame(ref first, ref second)) + { +#if NET7_0_OR_GREATER + if (Vector128.IsHardwareAccelerated) + { +#if NET8_0_OR_GREATER + if (Vector512.IsHardwareAccelerated && byteCount >= (nuint)Vector512.Count) + { + nuint offset = 0; + var lengthToExamine = byteCount - (nuint)Vector512.Count; + if (lengthToExamine != 0) + { + do + { + if (Vector512.LoadUnsafe(ref first, offset) != Vector512.LoadUnsafe(ref second, offset)) + return false; + offset += (nuint)Vector512.Count; + } while (lengthToExamine > offset); + } + return Vector512.LoadUnsafe(ref first, lengthToExamine) == Vector512.LoadUnsafe(ref second, lengthToExamine); + } +#endif + if (Vector256.IsHardwareAccelerated && byteCount >= (nuint)Vector256.Count) + { + nuint offset = 0; + var lengthToExamine = byteCount - (nuint)Vector256.Count; + if (lengthToExamine != 0) + { + do + { + if (Vector256.LoadUnsafe(ref first, offset) != Vector256.LoadUnsafe(ref second, offset)) + return false; + offset += (nuint)Vector256.Count; + } while (lengthToExamine > offset); + } + return Vector256.LoadUnsafe(ref first, lengthToExamine) == Vector256.LoadUnsafe(ref second, lengthToExamine); + } + if (byteCount >= (nuint)Vector128.Count) + { + nuint offset = 0; + var lengthToExamine = byteCount - (nuint)Vector128.Count; + if (lengthToExamine != 0) + { + do + { + if (Vector128.LoadUnsafe(ref first, offset) != Vector128.LoadUnsafe(ref second, offset)) + return false; + offset += (nuint)Vector128.Count; + } while (lengthToExamine > offset); + } + return Vector128.LoadUnsafe(ref first, lengthToExamine) == Vector128.LoadUnsafe(ref second, lengthToExamine); + } + } + if (sizeof(nint) == 8 && Vector128.IsHardwareAccelerated) + { + var offset = byteCount - (nuint)sizeof(nuint); + var differentBits = Unsafe.ReadUnaligned(ref first) - Unsafe.ReadUnaligned(ref second); + differentBits |= Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref first, offset)) - Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref second, offset)); + return differentBits == 0; + } + else +#endif + { + nuint offset = 0; + var lengthToExamine = byteCount - (nuint)sizeof(nuint); + if (lengthToExamine > 0) + { + do + { +#if NET7_0_OR_GREATER + if (Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref first, offset)) != Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref second, offset))) +#else + if (Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref first, (nint)offset)) != Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref second, (nint)offset))) +#endif + return false; + offset += (nuint)sizeof(nuint); + } while (lengthToExamine > offset); + } +#if NET7_0_OR_GREATER + return Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref first, lengthToExamine)) == Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref second, lengthToExamine)); +#else + return Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref first, (nint)lengthToExamine)) == Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref second, (nint)lengthToExamine)); +#endif + } + } + + return true; + } + + if (byteCount < sizeof(uint) || sizeof(nint) != 8) + { + uint differentBits = 0; + var offset = byteCount & 2; + if (offset != 0) + { + differentBits = Unsafe.ReadUnaligned(ref first); + differentBits -= Unsafe.ReadUnaligned(ref second); + } + + if ((byteCount & 1) != 0) +#if NET7_0_OR_GREATER + differentBits |= Unsafe.AddByteOffset(ref first, offset) - (uint)Unsafe.AddByteOffset(ref second, offset); +#else + differentBits |= Unsafe.AddByteOffset(ref first, (nint)offset) - (uint)Unsafe.AddByteOffset(ref second, (nint)offset); +#endif + return differentBits == 0; + } + else + { + var offset = byteCount - sizeof(uint); + var differentBits = Unsafe.ReadUnaligned(ref first) - Unsafe.ReadUnaligned(ref second); +#if NET7_0_OR_GREATER + differentBits |= Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref first, offset)) - Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref second, offset)); +#else + differentBits |= Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref first, (nint)offset)) - Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref second, (nint)offset)); +#endif + return differentBits == 0; + } + } + } +} +#endif diff --git a/Fantasy/Fantays.Console/Runtime/Core/LowLevel/FixedBytes.cs b/Fantasy/Fantays.Console/Runtime/Core/LowLevel/FixedBytes.cs new file mode 100644 index 0000000..7c5a7a4 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/LowLevel/FixedBytes.cs @@ -0,0 +1,107 @@ +#if FANTASY_NET || !FANTASY_WEBGL +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + +namespace Fantasy.LowLevel +{ + [StructLayout(LayoutKind.Sequential)] + public struct FixedBytes1 + { + private byte _e0; + + public Span AsSpan() => MemoryMarshal.CreateSpan(ref Unsafe.As(ref Unsafe.AsRef(in this)), Unsafe.SizeOf()); + } + + [StructLayout(LayoutKind.Sequential)] + public struct FixedBytes2 + { + private FixedBytes1 _e0; + private FixedBytes1 _e1; + + public Span AsSpan() => MemoryMarshal.CreateSpan(ref Unsafe.As(ref Unsafe.AsRef(in this)), Unsafe.SizeOf()); + } + + [StructLayout(LayoutKind.Sequential)] + public struct FixedBytes4 + { + private FixedBytes2 _e0; + private FixedBytes2 _e1; + + public Span AsSpan() => MemoryMarshal.CreateSpan(ref Unsafe.As(ref Unsafe.AsRef(in this)), Unsafe.SizeOf()); + } + + [StructLayout(LayoutKind.Sequential)] + public struct FixedBytes8 + { + private FixedBytes4 _e0; + private FixedBytes4 _e1; + + public Span AsSpan() => MemoryMarshal.CreateSpan(ref Unsafe.As(ref Unsafe.AsRef(in this)), Unsafe.SizeOf()); + } + + [StructLayout(LayoutKind.Sequential)] + public struct FixedBytes16 + { + private FixedBytes8 _e0; + private FixedBytes8 _e1; + + public Span AsSpan() => MemoryMarshal.CreateSpan(ref Unsafe.As(ref Unsafe.AsRef(in this)), Unsafe.SizeOf()); + } + + [StructLayout(LayoutKind.Sequential)] + public struct FixedBytes32 + { + private FixedBytes16 _e0; + private FixedBytes16 _e1; + + public Span AsSpan() => MemoryMarshal.CreateSpan(ref Unsafe.As(ref Unsafe.AsRef(in this)), Unsafe.SizeOf()); + } + + [StructLayout(LayoutKind.Sequential)] + public struct FixedBytes64 + { + private FixedBytes32 _e0; + private FixedBytes32 _e1; + + public Span AsSpan() => MemoryMarshal.CreateSpan(ref Unsafe.As(ref Unsafe.AsRef(in this)), Unsafe.SizeOf()); + } + + [StructLayout(LayoutKind.Sequential)] + public struct FixedBytes128 + { + private FixedBytes64 _e0; + private FixedBytes64 _e1; + + public Span AsSpan() => MemoryMarshal.CreateSpan(ref Unsafe.As(ref Unsafe.AsRef(in this)), Unsafe.SizeOf()); + } + + [StructLayout(LayoutKind.Sequential)] + public struct FixedBytes256 + { + private FixedBytes128 _e0; + private FixedBytes128 _e1; + + public Span AsSpan() => MemoryMarshal.CreateSpan(ref Unsafe.As(ref Unsafe.AsRef(in this)), Unsafe.SizeOf()); + } + + [StructLayout(LayoutKind.Sequential)] + public struct FixedBytes512 + { + private FixedBytes256 _e0; + private FixedBytes256 _e1; + + public Span AsSpan() => MemoryMarshal.CreateSpan(ref Unsafe.As(ref Unsafe.AsRef(in this)), Unsafe.SizeOf()); + } + + [StructLayout(LayoutKind.Sequential)] + public struct FixedBytes1024 + { + private FixedBytes512 _e0; + private FixedBytes512 _e1; + + public Span AsSpan() => MemoryMarshal.CreateSpan(ref Unsafe.As(ref Unsafe.AsRef(in this)), Unsafe.SizeOf()); + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/LowLevel/XxHash.cs b/Fantasy/Fantays.Console/Runtime/Core/LowLevel/XxHash.cs new file mode 100644 index 0000000..dff36b3 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/LowLevel/XxHash.cs @@ -0,0 +1,171 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +#pragma warning disable CS1591 + +namespace Fantasy.LowLevel +{ + public static class XxHash + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int Hash32(string obj, uint seed = 0) => Hash32(obj.AsSpan(), seed); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int Hash32(in T obj, uint seed = 0) where T : unmanaged => Hash32(MemoryMarshal.CreateReadOnlySpan(ref Unsafe.As(ref Unsafe.AsRef(in obj)), Unsafe.SizeOf()), seed); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int Hash32(ReadOnlySpan buffer, uint seed = 0) where T : unmanaged => Hash32(MemoryMarshal.Cast(buffer), seed); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int Hash32(ReadOnlySpan buffer, uint seed = 0) + { + int length = buffer.Length; + ref byte local1 = ref MemoryMarshal.GetReference(buffer); + uint num1; + if (buffer.Length >= 16) + { + uint num2 = seed + 606290984U; + uint num3 = seed + 2246822519U; + uint num4 = seed; + uint num5 = seed - 2654435761U; + for (; length >= 16; length -= 16) + { + const nint elementOffset1 = 4; + const nint elementOffset2 = 8; + const nint elementOffset3 = 12; + nint byteOffset = buffer.Length - length; + ref byte local2 = ref Unsafe.AddByteOffset(ref local1, byteOffset); + uint num6 = num2 + Unsafe.ReadUnaligned(ref local2) * 2246822519U; + num2 = (uint)((((int)num6 << 13) | (int)(num6 >> 19)) * -1640531535); + uint num7 = num3 + Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref local2, elementOffset1)) * 2246822519U; + num3 = (uint)((((int)num7 << 13) | (int)(num7 >> 19)) * -1640531535); + uint num8 = num4 + Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref local2, elementOffset2)) * 2246822519U; + num4 = (uint)((((int)num8 << 13) | (int)(num8 >> 19)) * -1640531535); + uint num9 = num5 + Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref local2, elementOffset3)) * 2246822519U; + num5 = (uint)((((int)num9 << 13) | (int)(num9 >> 19)) * -1640531535); + } + + num1 = (uint)((((int)num2 << 1) | (int)(num2 >> 31)) + (((int)num3 << 7) | (int)(num3 >> 25)) + (((int)num4 << 12) | (int)(num4 >> 20)) + (((int)num5 << 18) | (int)(num5 >> 14)) + buffer.Length); + } + else + num1 = (uint)((int)seed + 374761393 + buffer.Length); + + for (; length >= 4; length -= 4) + { + nint byteOffset = buffer.Length - length; + uint num10 = Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref local1, byteOffset)); + uint num11 = num1 + num10 * 3266489917U; + num1 = (uint)((((int)num11 << 17) | (int)(num11 >> 15)) * 668265263); + } + + nint byteOffset1 = buffer.Length - length; + ref byte local3 = ref Unsafe.AddByteOffset(ref local1, byteOffset1); + for (int index = 0; index < length; ++index) + { + nint byteOffset2 = index; + uint num12 = Unsafe.AddByteOffset(ref local3, byteOffset2); + uint num13 = num1 + num12 * 374761393U; + num1 = (uint)((((int)num13 << 11) | (int)(num13 >> 21)) * -1640531535); + } + +#if NET7_0_OR_GREATER + int num14 = ((int)num1 ^ (int)(num1 >> 15)) * -2048144777; + int num15 = (num14 ^ (num14 >>> 13)) * -1028477379; + return num15 ^ (num15 >>> 16); +#else + int num14 = ((int)num1 ^ (int)(num1 >> 15)) * -2048144777; + int num15 = (num14 ^ (int)((uint)num14 >> 13)) * -1028477379; + return num15 ^ (int)((uint)num15 >> 16); +#endif + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static long Hash64(string obj, ulong seed = 0) => Hash64(obj.AsSpan(), seed); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static long Hash64(in T obj, ulong seed = 0) where T : unmanaged => Hash64(MemoryMarshal.CreateReadOnlySpan(ref Unsafe.As(ref Unsafe.AsRef(in obj)), Unsafe.SizeOf()), seed); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static long Hash64(ReadOnlySpan buffer, ulong seed = 0) where T : unmanaged => Hash64(MemoryMarshal.Cast(buffer), seed); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static long Hash64(ReadOnlySpan buffer, ulong seed = 0) + { + ref var local1 = ref MemoryMarshal.GetReference(buffer); + var length = buffer.Length; + ulong num1; + if (buffer.Length >= 32) + { + var num2 = seed + 6983438078262162902UL; + var num3 = seed + 14029467366897019727UL; + var num4 = seed; + var num5 = seed - 11400714785074694791UL; + for (; length >= 32; length -= 32) + { + ref var local2 = ref Unsafe.AddByteOffset(ref local1, (IntPtr)(buffer.Length - length)); + var num6 = num2 + Unsafe.ReadUnaligned(ref local2) * 14029467366897019727UL; + num2 = (ulong)((((long)num6 << 31) | (long)(num6 >> 33)) * -7046029288634856825L); + var num7 = num3 + Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref local2, new UIntPtr(8U))) * 14029467366897019727UL; + num3 = (ulong)((((long)num7 << 31) | (long)(num7 >> 33)) * -7046029288634856825L); + var num8 = num4 + Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref local2, new UIntPtr(16U))) * 14029467366897019727UL; + num4 = (ulong)((((long)num8 << 31) | (long)(num8 >> 33)) * -7046029288634856825L); + var num9 = num5 + Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref local2, new UIntPtr(24U))) * 14029467366897019727UL; + num5 = (ulong)((((long)num9 << 31) | (long)(num9 >> 33)) * -7046029288634856825L); + } + + var num10 = (((long)num2 << 1) | (long)(num2 >> 63)) + (((long)num3 << 7) | (long)(num3 >> 57)) + (((long)num4 << 12) | (long)(num4 >> 52)) + (((long)num5 << 18) | (long)(num5 >> 46)); + var num11 = num2 * 14029467366897019727UL; + var num12 = (((long)num11 << 31) | (long)(num11 >> 33)) * -7046029288634856825L; + var num13 = (num10 ^ num12) * -7046029288634856825L + -8796714831421723037L; + var num14 = num3 * 14029467366897019727UL; + var num15 = (((long)num14 << 31) | (long)(num14 >> 33)) * -7046029288634856825L; + var num16 = (num13 ^ num15) * -7046029288634856825L + -8796714831421723037L; + var num17 = num4 * 14029467366897019727UL; + var num18 = (((long)num17 << 31) | (long)(num17 >> 33)) * -7046029288634856825L; + var num19 = (num16 ^ num18) * -7046029288634856825L + -8796714831421723037L; + var num20 = num5 * 14029467366897019727UL; + var num21 = (((long)num20 << 31) | (long)(num20 >> 33)) * -7046029288634856825L; + num1 = (ulong)((num19 ^ num21) * -7046029288634856825L + -8796714831421723037L); + } + else + num1 = seed + 2870177450012600261UL; + + var num22 = num1 + (ulong)buffer.Length; + for (; length >= 8; length -= 8) + { + var num23 = Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref local1, (IntPtr)(buffer.Length - length))) * 14029467366897019727UL; + var num24 = (ulong)((((long)num23 << 31) | (long)(num23 >> 33)) * -7046029288634856825L); + var num25 = num22 ^ num24; + num22 = (ulong)((((long)num25 << 27) | (long)(num25 >> 37)) * -7046029288634856825L + -8796714831421723037L); + } + + if (length >= 4) + { + ulong num26 = Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref local1, (IntPtr)(buffer.Length - length))); + var num27 = num22 ^ (num26 * 11400714785074694791UL); + num22 = (ulong)((((long)num27 << 23) | (long)(num27 >> 41)) * -4417276706812531889L + 1609587929392839161L); + length -= 4; + } + + for (var byteOffset = 0; byteOffset < length; ++byteOffset) + { + ulong num28 = Unsafe.AddByteOffset(ref Unsafe.AddByteOffset(ref local1, (IntPtr)(buffer.Length - length)), (IntPtr)byteOffset); + var num29 = num22 ^ (num28 * 2870177450012600261UL); + num22 = (ulong)((((long)num29 << 11) | (long)(num29 >> 53)) * -7046029288634856825L); + } + +#if NET7_0_OR_GREATER + var num30 = (long)num22; + var num31 = (num30 ^ (num30 >>> 33)) * -4417276706812531889L; + var num32 = (num31 ^ (num31 >>> 29)) * 1609587929392839161L; + return num32 ^ (num32 >>> 32); +#else + var num30 = (long)num22; + var num31 = (num30 ^ (long)((ulong)num30 >> 33)) * -4417276706812531889L; + var num32 = (num31 ^ (long)((ulong)num31 >> 29)) * 1609587929392839161L; + return num32 ^ (long)((ulong)num32 >> 32); +#endif + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Addressable/AddressableHelper.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Addressable/AddressableHelper.cs new file mode 100644 index 0000000..098ba8a --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Addressable/AddressableHelper.cs @@ -0,0 +1,141 @@ +#if FANTASY_NET +using Fantasy.Async; +using Fantasy.InnerMessage; +using Fantasy.Platform.Net; +namespace Fantasy.Network.Route +{ + /// + /// 提供操作地址映射的辅助方法。 + /// + public static class AddressableHelper + { + // 声明一个私有静态只读列表 AddressableScenes,用于存储地址映射的场景配置信息 + private static readonly List AddressableScenes = new List(); + + static AddressableHelper() + { + // 遍历场景配置信息,筛选出地址映射类型的场景,并添加到 AddressableScenes 列表中 + foreach (var sceneConfig in SceneConfigData.Instance.List) + { + if (sceneConfig.SceneTypeString == "Addressable") + { + AddressableScenes.Add(new AddressableScene(sceneConfig)); + } + } + } + + /// + /// 添加地址映射并返回操作结果。 + /// + /// 场景实例。 + /// 地址映射的唯一标识。 + /// 路由 ID。 + /// 是否锁定。 + public static async FTask AddAddressable(Scene scene, long addressableId, long routeId, bool isLock = true) + { + // 获取指定索引的地址映射场景配置信息 + var addressableScene = AddressableScenes[(int)addressableId % AddressableScenes.Count]; + // 调用内部路由方法,发送添加地址映射的请求并等待响应 + var response = await scene.NetworkMessagingComponent.CallInnerRoute(addressableScene.RunTimeId, + new I_AddressableAdd_Request + { + AddressableId = addressableId, RouteId = routeId, IsLock = isLock + }); + if (response.ErrorCode != 0) + { + Log.Error($"AddAddressable error is {response.ErrorCode}"); + } + } + + /// + /// 获取地址映射的路由 ID。 + /// + /// 场景实例。 + /// 地址映射的唯一标识。 + /// 地址映射的路由 ID。 + public static async FTask GetAddressableRouteId(Scene scene, long addressableId) + { + // 获取指定索引的地址映射场景配置信息 + var addressableScene = AddressableScenes[(int)addressableId % AddressableScenes.Count]; + // 调用内部路由方法,发送获取地址映射路由 ID 的请求并等待响应 + var response = (I_AddressableGet_Response) await scene.NetworkMessagingComponent.CallInnerRoute(addressableScene.RunTimeId, + new I_AddressableGet_Request + { + AddressableId = addressableId + }); + // 检查响应错误码,如果为零,返回路由 ID;否则,输出错误信息并返回 0 + if (response.ErrorCode == 0) + { + return response.RouteId; + } + + Log.Error($"GetAddressable error is {response.ErrorCode} addressableId:{addressableId}"); + return 0; + } + + /// + /// 移除指定地址映射。 + /// + /// 场景实例。 + /// 地址映射的唯一标识。 + public static async FTask RemoveAddressable(Scene scene, long addressableId) + { + var addressableScene = AddressableScenes[(int)addressableId % AddressableScenes.Count]; + var response = await scene.NetworkMessagingComponent.CallInnerRoute(addressableScene.RunTimeId, + new I_AddressableRemove_Request + { + AddressableId = addressableId + }); + + if (response.ErrorCode != 0) + { + Log.Error($"RemoveAddressable error is {response.ErrorCode}"); + } + } + + /// + /// 锁定指定地址映射。 + /// + /// 场景实例。 + /// 地址映射的唯一标识。 + public static async FTask LockAddressable(Scene scene, long addressableId) + { + var addressableScene = AddressableScenes[(int)addressableId % AddressableScenes.Count]; + var response = await scene.NetworkMessagingComponent.CallInnerRoute(addressableScene.RunTimeId, + new I_AddressableLock_Request + { + AddressableId = addressableId + }); + + if (response.ErrorCode != 0) + { + Log.Error($"LockAddressable error is {response.ErrorCode}"); + } + } + + /// + /// 解锁指定地址映射。 + /// + /// 场景实例。 + /// 地址映射的唯一标识。 + /// 路由 ID。 + /// 解锁来源。 + public static async FTask UnLockAddressable(Scene scene, long addressableId, long routeId, string source) + { + var addressableScene = AddressableScenes[(int)addressableId % AddressableScenes.Count]; + var response = await scene.NetworkMessagingComponent.CallInnerRoute(addressableScene.RunTimeId, + new I_AddressableUnLock_Request + { + AddressableId = addressableId, + RouteId = routeId, + Source = source + }); + + if (response.ErrorCode != 0) + { + Log.Error($"UnLockAddressable error is {response.ErrorCode}"); + } + } + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Addressable/AddressableManageComponent.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Addressable/AddressableManageComponent.cs new file mode 100644 index 0000000..8d08f08 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Addressable/AddressableManageComponent.cs @@ -0,0 +1,144 @@ +#if FANTASY_NET +using System; +using System.Collections.Generic; +using Fantasy.Async; +using Fantasy.Entitas; +using Fantasy.Entitas.Interface; + +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. +#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. +#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type. + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +namespace Fantasy.Network.Route +{ + public class AddressableManageComponentAwakeSystem : AwakeSystem + { + protected override void Awake(AddressableManageComponent self) + { + self.AddressableLock = self.Scene.CoroutineLockComponent.Create(self.GetType().TypeHandle.Value.ToInt64()); + } + } + + public class AddressableManageComponentDestroySystem : DestroySystem + { + protected override void Destroy(AddressableManageComponent self) + { + foreach (var (_, waitCoroutineLock) in self.Locks) + { + waitCoroutineLock.Dispose(); + } + + self.Locks.Clear(); + self.Addressable.Clear(); + self.AddressableLock.Dispose(); + self.AddressableLock = null; + } + } + + public sealed class AddressableManageComponent : Entity + { + public CoroutineLock AddressableLock; + public readonly Dictionary Addressable = new(); + public readonly Dictionary Locks = new(); + + /// + /// 添加地址映射。 + /// + /// 地址映射的唯一标识。 + /// 路由 ID。 + /// 是否进行锁定。 + public async FTask Add(long addressableId, long routeId, bool isLock) + { + WaitCoroutineLock waitCoroutineLock = null; + + try + { + if (isLock) + { + waitCoroutineLock = await AddressableLock.Wait(addressableId); + } + + Addressable[addressableId] = routeId; +#if FANTASY_DEVELOP + Log.Debug($"AddressableManageComponent Add addressableId:{addressableId} routeId:{routeId}"); +#endif + } + catch (Exception e) + { + Log.Error(e); + } + finally + { + waitCoroutineLock?.Dispose(); + } + } + + /// + /// 获取地址映射的路由 ID。 + /// + /// 地址映射的唯一标识。 + /// 地址映射的路由 ID。 + public async FTask Get(long addressableId) + { + using (await AddressableLock.Wait(addressableId)) + { + Addressable.TryGetValue(addressableId, out var routeId); + return routeId; + } + } + + /// + /// 移除地址映射。 + /// + /// 地址映射的唯一标识。 + public async FTask Remove(long addressableId) + { + using (await AddressableLock.Wait(addressableId)) + { + Addressable.Remove(addressableId); +#if FANTASY_DEVELOP + Log.Debug($"Addressable Remove addressableId: {addressableId} _addressable:{Addressable.Count}"); +#endif + } + } + + /// + /// 锁定地址映射。 + /// + /// 地址映射的唯一标识。 + public async FTask Lock(long addressableId) + { + var waitCoroutineLock = await AddressableLock.Wait(addressableId); + Locks.Add(addressableId, waitCoroutineLock); + } + + /// + /// 解锁地址映射。 + /// + /// 地址映射的唯一标识。 + /// 新的路由 ID。 + /// 解锁来源。 + public void UnLock(long addressableId, long routeId, string source) + { + if (!Locks.Remove(addressableId, out var coroutineLock)) + { + Log.Error($"Addressable unlock not found addressableId: {addressableId} Source:{source}"); + return; + } + + Addressable.TryGetValue(addressableId, out var oldAddressableId); + + if (routeId != 0) + { + Addressable[addressableId] = routeId; + } + + coroutineLock.Dispose(); +#if FANTASY_DEVELOP + Log.Debug($"Addressable UnLock key: {addressableId} oldAddressableId : {oldAddressableId} routeId: {routeId} Source:{source}"); +#endif + } + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Addressable/AddressableMessageComponent.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Addressable/AddressableMessageComponent.cs new file mode 100644 index 0000000..03e0def --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Addressable/AddressableMessageComponent.cs @@ -0,0 +1,91 @@ +using Fantasy.Async; +using Fantasy.Entitas; +using Fantasy.Entitas.Interface; + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +#if FANTASY_NET +namespace Fantasy.Network.Route +{ + public class AddressableMessageComponentDestroySystem : DestroySystem + { + protected override void Destroy(AddressableMessageComponent self) + { + if (self.AddressableId != 0) + { + AddressableHelper.RemoveAddressable(self.Scene, self.AddressableId).Coroutine(); + self.AddressableId = 0; + } + } + } + + /// + /// 可寻址消息组件、挂载了这个组件可以接收Addressable消息 + /// + public sealed class AddressableMessageComponent : Entity + { + /// + /// 可寻址消息组件的唯一标识。 + /// + public long AddressableId; + + /// + /// 注册可寻址消息组件。 + /// + /// 是否进行锁定。 + public FTask Register(bool isLock = true) + { + if (Parent == null) + { + throw new Exception("AddressableRouteComponent must be mounted under a component"); + } + + AddressableId = Parent.Id; + + if (AddressableId == 0) + { + throw new Exception("AddressableRouteComponent.Parent.Id is null"); + } + +#if FANTASY_DEVELOP + Log.Debug($"AddressableMessageComponent Register addressableId:{AddressableId} RouteId:{Parent.RouteId}"); +#endif + return AddressableHelper.AddAddressable(Scene, AddressableId, Parent.RouteId, isLock); + } + + /// + /// 锁定可寻址消息组件。 + /// + public FTask Lock() + { +#if FANTASY_DEVELOP + Log.Debug($"AddressableMessageComponent Lock {Parent.Id}"); +#endif + return AddressableHelper.LockAddressable(Scene, Parent.Id); + } + + /// + /// 解锁可寻址消息组件。 + /// + /// 解锁来源。 + public FTask UnLock(string source) + { +#if FANTASY_DEVELOP + Log.Debug($"AddressableMessageComponent UnLock {Parent.Id} {Parent.RouteId}"); +#endif + return AddressableHelper.UnLockAddressable(Scene, Parent.Id, Parent.RouteId, source); + } + + /// + /// 锁定可寻址消息并且释放掉AddressableMessageComponent组件。 + /// 该方法不会自动取Addressable中心删除自己的信息。 + /// 用于传送或转移到其他服务器时使用 + /// + public async FTask LockAndRelease() + { + await Lock(); + AddressableId = 0; + Dispose(); + } + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Addressable/AddressableRouteComponent.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Addressable/AddressableRouteComponent.cs new file mode 100644 index 0000000..bf439d7 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Addressable/AddressableRouteComponent.cs @@ -0,0 +1,218 @@ +using Fantasy.Async; +using Fantasy.Entitas; +using Fantasy.Entitas.Interface; +using Fantasy.Helper; +using Fantasy.Network.Interface; +using Fantasy.PacketParser.Interface; +using Fantasy.Scheduler; +using Fantasy.Timer; + +#pragma warning disable CS8603 // Possible null reference return. +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. +#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. +#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type. +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +#if FANTASY_NET +namespace Fantasy.Network.Route +{ + public class AddressableRouteComponentAwakeSystem : AwakeSystem + { + protected override void Awake(AddressableRouteComponent self) + { + ((Session)self.Parent).AddressableRouteComponent = self; + + var selfScene = self.Scene; + self.TimerComponent = selfScene.TimerComponent; + self.NetworkMessagingComponent = selfScene.NetworkMessagingComponent; + self.MessageDispatcherComponent = selfScene.MessageDispatcherComponent; + self.AddressableRouteLock = + selfScene.CoroutineLockComponent.Create(self.GetType().TypeHandle.Value.ToInt64()); + } + } + + public class AddressableRouteComponentDestroySystem : DestroySystem + { + protected override void Destroy(AddressableRouteComponent self) + { + self.AddressableRouteLock.Dispose(); + + self.AddressableRouteId = 0; + self.AddressableId = 0; + self.TimerComponent = null; + self.AddressableRouteLock = null; + self.NetworkMessagingComponent = null; + self.MessageDispatcherComponent = null; + } + } + + /// + /// 可寻址路由消息组件,挂载了这个组件可以接收和发送 Addressable 消息。 + /// + public sealed class AddressableRouteComponent : Entity + { + public long AddressableId; + public long AddressableRouteId; + public CoroutineLock AddressableRouteLock; + public TimerComponent TimerComponent; + public NetworkMessagingComponent NetworkMessagingComponent; + public MessageDispatcherComponent MessageDispatcherComponent; + + internal void Send(IAddressableRouteMessage message) + { + Call(message).Coroutine(); + } + + internal async FTask Send(Type requestType, APackInfo packInfo) + { + await Call(requestType, packInfo); + } + + internal async FTask Call(Type requestType, APackInfo packInfo) + { + if (IsDisposed) + { + return MessageDispatcherComponent.CreateResponse(requestType, InnerErrorCode.ErrNotFoundRoute); + } + + packInfo.IsDisposed = true; + var failCount = 0; + var runtimeId = RuntimeId; + IResponse iRouteResponse = null; + + try + { + using (await AddressableRouteLock.Wait(AddressableId, "AddressableRouteComponent Call MemoryStream")) + { + while (!IsDisposed) + { + if (AddressableRouteId == 0) + { + AddressableRouteId = await AddressableHelper.GetAddressableRouteId(Scene, AddressableId); + } + + if (AddressableRouteId == 0) + { + return MessageDispatcherComponent.CreateResponse(requestType, + InnerErrorCode.ErrNotFoundRoute); + } + + iRouteResponse = await NetworkMessagingComponent.CallInnerRoute(AddressableRouteId, requestType, packInfo); + + if (runtimeId != RuntimeId) + { + iRouteResponse.ErrorCode = InnerErrorCode.ErrRouteTimeout; + } + + switch (iRouteResponse.ErrorCode) + { + case InnerErrorCode.ErrRouteTimeout: + { + return iRouteResponse; + } + case InnerErrorCode.ErrNotFoundRoute: + { + if (++failCount > 20) + { + Log.Error($"AddressableComponent.Call failCount > 20 route send message fail, routeId: {RouteId} AddressableRouteComponent:{Id}"); + return iRouteResponse; + } + + await TimerComponent.Net.WaitAsync(100); + + if (runtimeId != RuntimeId) + { + iRouteResponse.ErrorCode = InnerErrorCode.ErrRouteTimeout; + } + + AddressableRouteId = 0; + continue; + } + default: + { + return iRouteResponse; // 对于其他情况,直接返回响应,无需额外处理 + } + } + } + } + } + finally + { + packInfo.Dispose(); + } + + + return iRouteResponse; + } + + /// + /// 调用可寻址路由消息并等待响应。 + /// + /// 可寻址路由请求。 + private async FTask Call(IAddressableRouteMessage request) + { + if (IsDisposed) + { + return MessageDispatcherComponent.CreateResponse(request.GetType(), InnerErrorCode.ErrNotFoundRoute); + } + + var failCount = 0; + var runtimeId = RuntimeId; + + using (await AddressableRouteLock.Wait(AddressableId, "AddressableRouteComponent Call")) + { + while (true) + { + if (AddressableRouteId == 0) + { + AddressableRouteId = await AddressableHelper.GetAddressableRouteId(Scene, AddressableId); + } + + if (AddressableRouteId == 0) + { + return MessageDispatcherComponent.CreateResponse(request.GetType(), + InnerErrorCode.ErrNotFoundRoute); + } + + var iRouteResponse = await NetworkMessagingComponent.CallInnerRoute(AddressableRouteId, request); + + if (runtimeId != RuntimeId) + { + iRouteResponse.ErrorCode = InnerErrorCode.ErrRouteTimeout; + } + + switch (iRouteResponse.ErrorCode) + { + case InnerErrorCode.ErrNotFoundRoute: + { + if (++failCount > 20) + { + Log.Error( + $"AddressableRouteComponent.Call failCount > 20 route send message fail, routeId: {RouteId} AddressableRouteComponent:{Id}"); + return iRouteResponse; + } + + await TimerComponent.Net.WaitAsync(500); + + if (runtimeId != RuntimeId) + { + iRouteResponse.ErrorCode = InnerErrorCode.ErrRouteTimeout; + } + + AddressableRouteId = 0; + continue; + } + case InnerErrorCode.ErrRouteTimeout: + { + return iRouteResponse; + } + default: + { + return iRouteResponse; + } + } + } + } + } + } +} +#endif diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Addressable/AddressableScene.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Addressable/AddressableScene.cs new file mode 100644 index 0000000..3105fca --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Addressable/AddressableScene.cs @@ -0,0 +1,31 @@ +#if FANTASY_NET +using Fantasy.IdFactory; +using Fantasy.Platform.Net; + +namespace Fantasy.Network.Route +{ + /// + /// AddressableScene + /// + public sealed class AddressableScene + { + /// + /// Id + /// + public readonly long Id; + /// + /// RunTimeId + /// + public readonly long RunTimeId; + /// + /// 构造方法 + /// + /// sceneConfig + public AddressableScene(SceneConfig sceneConfig) + { + Id = IdFactoryHelper.EntityId(0, sceneConfig.Id, (byte)sceneConfig.WorldConfigId, 0); + RunTimeId = IdFactoryHelper.RuntimeId(0, sceneConfig.Id, (byte)sceneConfig.WorldConfigId, 0); + } + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Addressable/Handler/I_AddressableAddHandler.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Addressable/Handler/I_AddressableAddHandler.cs new file mode 100644 index 0000000..847625b --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Addressable/Handler/I_AddressableAddHandler.cs @@ -0,0 +1,26 @@ +using Fantasy.Async; +using Fantasy.InnerMessage; +using Fantasy.Network.Interface; + +#if FANTASY_NET +namespace Fantasy.Network.Route +{ + /// + /// 声明一个 sealed 类 I_AddressableAddHandler,继承自 RouteRPC 类,并指定泛型参数 + /// + public sealed class I_AddressableAddHandler : RouteRPC + { + /// + /// 在收到地址映射添加请求时执行的逻辑。 + /// + /// 当前场景实例。 + /// 包含请求信息的 I_AddressableAdd_Request 实例。 + /// 用于构建响应的 I_AddressableAdd_Response 实例。 + /// 执行响应的回调操作。 + protected override async FTask Run(Scene scene, I_AddressableAdd_Request request, I_AddressableAdd_Response response, Action reply) + { + await scene.GetComponent().Add(request.AddressableId, request.RouteId, request.IsLock); + } + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Addressable/Handler/I_AddressableGetHandler.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Addressable/Handler/I_AddressableGetHandler.cs new file mode 100644 index 0000000..f53c1f1 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Addressable/Handler/I_AddressableGetHandler.cs @@ -0,0 +1,26 @@ +using Fantasy.Async; +using Fantasy.InnerMessage; +using Fantasy.Network.Interface; + +#if FANTASY_NET +namespace Fantasy.Network.Route +{ + /// + /// 声明一个 sealed 类 I_AddressableGetHandler,继承自 RouteRPC 类,并指定泛型参数 + /// + public sealed class I_AddressableGetHandler : RouteRPC + { + /// + /// 在收到地址映射获取请求时执行的逻辑。 + /// + /// 当前场景实例。 + /// 包含请求信息的 I_AddressableGet_Request 实例。 + /// 用于构建响应的 I_AddressableGet_Response 实例。 + /// 执行响应的回调操作。 + protected override async FTask Run(Scene scene, I_AddressableGet_Request request, I_AddressableGet_Response response, Action reply) + { + response.RouteId = await scene.GetComponent().Get(request.AddressableId); + } + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Addressable/Handler/I_AddressableLockHandler.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Addressable/Handler/I_AddressableLockHandler.cs new file mode 100644 index 0000000..b964274 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Addressable/Handler/I_AddressableLockHandler.cs @@ -0,0 +1,26 @@ +using Fantasy.Async; +using Fantasy.InnerMessage; +using Fantasy.Network.Interface; + +#if FANTASY_NET +namespace Fantasy.Network.Route +{ + /// + /// 声明一个 sealed 类 I_AddressableLockHandler,继承自 RouteRPC 类,并指定泛型参数 + /// + public sealed class I_AddressableLockHandler : RouteRPC + { + /// + /// 在收到地址映射锁定请求时执行的逻辑。 + /// + /// 当前场景实例。 + /// 包含请求信息的 I_AddressableLock_Request 实例。 + /// 用于构建响应的 I_AddressableLock_Response 实例。 + /// 执行响应的回调操作。 + protected override async FTask Run(Scene scene, I_AddressableLock_Request request, I_AddressableLock_Response response, Action reply) + { + await scene.GetComponent().Lock(request.AddressableId); + } + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Addressable/Handler/I_AddressableRemoveHandler.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Addressable/Handler/I_AddressableRemoveHandler.cs new file mode 100644 index 0000000..c4e9654 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Addressable/Handler/I_AddressableRemoveHandler.cs @@ -0,0 +1,26 @@ +using Fantasy.Async; +using Fantasy.InnerMessage; +using Fantasy.Network.Interface; + +#if FANTASY_NET +namespace Fantasy.Network.Route +{ + /// + /// 声明一个 sealed 类 I_AddressableRemoveHandler,继承自 RouteRPC 类,并指定泛型参数 + /// + public sealed class I_AddressableRemoveHandler : RouteRPC + { + /// + /// 在收到地址映射移除请求时执行的逻辑。 + /// + /// 当前场景实例。 + /// 包含请求信息的 I_AddressableRemove_Request 实例。 + /// 用于构建响应的 I_AddressableRemove_Response 实例。 + /// 执行响应的回调操作。 + protected override async FTask Run(Scene scene, I_AddressableRemove_Request request, I_AddressableRemove_Response response, Action reply) + { + await scene.GetComponent().Remove(request.AddressableId); + } + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Addressable/Handler/I_AddressableUnLockHandler.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Addressable/Handler/I_AddressableUnLockHandler.cs new file mode 100644 index 0000000..1ab3937 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Addressable/Handler/I_AddressableUnLockHandler.cs @@ -0,0 +1,27 @@ +using Fantasy.Async; +using Fantasy.InnerMessage; +using Fantasy.Network.Interface; + +#if FANTASY_NET +namespace Fantasy.Network.Route +{ + /// + /// 声明一个 sealed 类 I_AddressableUnLockHandler,继承自 RouteRPC 类,并指定泛型参数 + /// + public sealed class I_AddressableUnLockHandler : RouteRPC + { + /// + /// 在收到地址映射解锁请求时执行的逻辑。 + /// + /// 当前场景实例。 + /// 包含请求信息的 I_AddressableUnLock_Request 实例。 + /// 用于构建响应的 I_AddressableUnLock_Response 实例。 + /// 执行响应的回调操作。 + protected override async FTask Run(Scene scene, I_AddressableUnLock_Request request, I_AddressableUnLock_Response response, Action reply) + { + scene.GetComponent().UnLock(request.AddressableId, request.RouteId, request.Source); + await FTask.CompletedTask; + } + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/MemoryStreamBufferPool.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/MemoryStreamBufferPool.cs new file mode 100644 index 0000000..6e6d4f7 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/MemoryStreamBufferPool.cs @@ -0,0 +1,96 @@ +using System; +using System.Collections.Generic; +using Fantasy.Serialize; +#pragma warning disable CS8603 // Possible null reference return. + +namespace Fantasy.Network +{ + /// + /// MemoryStreamBuffer对象池类 + /// + public sealed class MemoryStreamBufferPool : IDisposable + { + private readonly int _poolSize; + private readonly int _maxMemoryStreamSize; + private readonly Queue _memoryStreamPool = new Queue(); + + /// + /// 构造方法 + /// + /// + /// + public MemoryStreamBufferPool(int maxMemoryStreamSize = 2048, int poolSize = 512) + { + _poolSize = poolSize; + _maxMemoryStreamSize = maxMemoryStreamSize; + } + + /// + /// 租借MemoryStream + /// + /// + /// + /// + public MemoryStreamBuffer RentMemoryStream(MemoryStreamBufferSource memoryStreamBufferSource, int size = 0) + { + if (size > _maxMemoryStreamSize) + { + return new MemoryStreamBuffer(memoryStreamBufferSource, size); + } + + if (size < _maxMemoryStreamSize) + { + size = _maxMemoryStreamSize; + } + + if (_memoryStreamPool.Count == 0) + { + return new MemoryStreamBuffer(memoryStreamBufferSource, size); + } + + if (_memoryStreamPool.TryDequeue(out var memoryStream)) + { + memoryStream.MemoryStreamBufferSource = memoryStreamBufferSource; + return memoryStream; + } + + return new MemoryStreamBuffer(memoryStreamBufferSource, size); + } + + /// + /// 归还ReturnMemoryStream + /// + /// + public void ReturnMemoryStream(MemoryStreamBuffer memoryStreamBuffer) + { + if (memoryStreamBuffer.Capacity > _maxMemoryStreamSize) + { + return; + } + + if (_memoryStreamPool.Count > _poolSize) + { + // 设置该值只能是内网或服务器转发的时候可能在连接之前发送的数据过多的情况下可以修改。 + // 设置过大会导致内存占用过大,所以要谨慎设置。 + return; + } + + memoryStreamBuffer.SetLength(0); + memoryStreamBuffer.MemoryStreamBufferSource = MemoryStreamBufferSource.None; + _memoryStreamPool.Enqueue(memoryStreamBuffer); + } + + /// + /// 销毁方法 + /// + public void Dispose() + { + foreach (var memoryStreamBuffer in _memoryStreamPool) + { + memoryStreamBuffer.MemoryStreamBufferSource = MemoryStreamBufferSource.None; + memoryStreamBuffer.Dispose(); + } + _memoryStreamPool.Clear(); + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Message/Dispatcher/Interface/IMessageHandler.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Message/Dispatcher/Interface/IMessageHandler.cs new file mode 100644 index 0000000..d8f928e --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Message/Dispatcher/Interface/IMessageHandler.cs @@ -0,0 +1,220 @@ +// ReSharper disable InconsistentNaming + +using System; +using System.Collections.Generic; +using Fantasy.Async; +using Fantasy.Network; +using Fantasy.Serialize; + +namespace Fantasy.Network.Interface +{ + /// + /// 表示消息处理器的接口,处理特定类型的消息。 + /// + public interface IMessageHandler + { + /// + /// 获取处理的消息类型。 + /// + /// 消息类型。 + public Type Type(); + /// + /// 处理消息的方法。 + /// + /// 会话对象。 + /// RPC标识。 + /// 消息类型代码。 + /// 要处理的消息。 + /// 异步任务。 + FTask Handle(Session session, uint rpcId, uint messageTypeCode, object message); + } + + /// + /// 泛型消息基类,实现了 接口。 + /// + public abstract class Message : IMessageHandler + { + /// + /// 获取处理的消息类型。 + /// + /// 消息类型。 + public Type Type() + { + return typeof(T); + } + + /// + /// 处理消息的方法。 + /// + /// 会话对象。 + /// RPC标识。 + /// 消息类型代码。 + /// 要处理的消息。 + /// 异步任务。 + public async FTask Handle(Session session, uint rpcId, uint messageTypeCode, object message) + { + try + { + await Run(session, (T) message); + } + catch (Exception e) + { + Log.Error(e); + } + } + + /// + /// 运行消息处理逻辑。 + /// + /// 会话对象。 + /// 要处理的消息。 + /// 异步任务。 + protected abstract FTask Run(Session session, T message); + } + + /// + /// 泛型消息RPC基类,实现了 接口,用于处理请求和响应类型的消息。 + /// + public abstract class MessageRPC : IMessageHandler where TRequest : IRequest where TResponse : AMessage, IResponse, new() + { + /// + /// 获取处理的消息类型。 + /// + /// 消息类型。 + public Type Type() + { + return typeof(TRequest); + } + + /// + /// 处理消息的方法。 + /// + /// 会话对象。 + /// RPC标识。 + /// 消息类型代码。 + /// 要处理的消息。 + /// 异步任务。 + public async FTask Handle(Session session, uint rpcId, uint messageTypeCode, object message) + { + if (message is not TRequest request) + { + Log.Error($"消息类型转换错误: {message.GetType().Name} to {typeof(TRequest).Name}"); + return; + } + + var response = new TResponse(); + var isReply = false; + + void Reply() + { + if (isReply) + { + return; + } + + isReply = true; + + if (session.IsDisposed) + { + return; + } + + session.Send(response, rpcId); + } + + try + { + await Run(session, request, response, Reply); + } + catch (Exception e) + { + Log.Error(e); + response.ErrorCode = InnerErrorCode.ErrRpcFail; + } + finally + { + Reply(); + } + } + + /// + /// 运行消息处理逻辑。 + /// + /// 会话对象。 + /// 请求消息。 + /// 响应消息。 + /// 发送响应的方法。 + /// 异步任务。 + protected abstract FTask Run(Session session, TRequest request, TResponse response, Action reply); + } +#if FANTASY_UNITY + public interface IMessageDelegateHandler + { + /// + /// 注册消息处理器。 + /// + /// + public void Register(object @delegate); + /// + /// 取消注册消息处理器。 + /// + /// + public int UnRegister(object @delegate); + /// + /// 处理消息的方法。 + /// + /// + /// + public void Handle(Session session, object message); + } + public delegate FTask MessageDelegate(Session session, T msg) where T : IMessage; + public sealed class MessageDelegateHandler : IMessageDelegateHandler, IDisposable where T : IMessage + { + private readonly List> _delegates = new List>(); + + public Type Type() + { + return typeof(T); + } + + public void Register(object @delegate) + { + var a = (MessageDelegate)@delegate; + + if (_delegates.Contains(a)) + { + Log.Error($"{typeof(T).Name} already register action delegateName:{a.Method.Name}"); + return; + } + + _delegates.Add(a); + } + + public int UnRegister(object @delegate) + { + _delegates.Remove((MessageDelegate)@delegate); + return _delegates.Count; + } + + public void Handle(Session session, object message) + { + foreach (var registerDelegate in _delegates) + { + try + { + registerDelegate(session, (T)message).Coroutine(); + } + catch (Exception e) + { + Log.Error(e); + } + } + } + + public void Dispose() + { + _delegates.Clear(); + } + } +#endif +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Message/Dispatcher/Interface/IRouteMessageHandler.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Message/Dispatcher/Interface/IRouteMessageHandler.cs new file mode 100644 index 0000000..4a367b2 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Message/Dispatcher/Interface/IRouteMessageHandler.cs @@ -0,0 +1,490 @@ +using Fantasy.Async; +using Fantasy.Entitas; +using Fantasy.InnerMessage; +using Fantasy.Network; +using Fantasy.Serialize; + +#if FANTASY_NET +// ReSharper disable InconsistentNaming + +namespace Fantasy.Network.Interface +{ + /// + /// 表示路由消息处理器的接口,处理特定类型的路由消息。 + /// + public interface IRouteMessageHandler + { + /// + /// 获取处理的消息类型。 + /// + /// 消息类型。 + public Type Type(); + + /// + /// 处理路由消息的方法。 + /// + /// 会话对象。 + /// 实体对象。 + /// RPC标识。 + /// 要处理的路由消息。 + /// 异步任务。 + FTask Handle(Session session, Entity entity, uint rpcId, object routeMessage); + } + + /// + /// 泛型路由基类,实现了 接口,用于处理特定实体和路由消息类型的路由。 + /// + /// 实体类型。 + /// 路由消息类型。 + public abstract class Route : IRouteMessageHandler where TEntity : Entity where TMessage : IRouteMessage + { + /// + /// 获取处理的消息类型。 + /// + /// 消息类型。 + public Type Type() + { + return typeof(TMessage); + } + + /// + /// 处理路由消息的方法。 + /// + /// 会话对象。 + /// 实体对象。 + /// RPC标识。 + /// 要处理的路由消息。 + /// 异步任务。 + public async FTask Handle(Session session, Entity entity, uint rpcId, object routeMessage) + { + if (routeMessage is not TMessage ruteMessage) + { + Log.Error($"Message type conversion error: {routeMessage.GetType().FullName} to {typeof(TMessage).Name}"); + return; + } + + if (entity is not TEntity tEntity) + { + Log.Error($"Route type conversion error: {entity.GetType().Name} to {nameof(TEntity)}"); + return; + } + + try + { + await Run(tEntity, ruteMessage); + } + catch (Exception e) + { + if (entity is not Scene scene) + { + scene = entity.Scene; + } + + Log.Error($"SceneConfigId:{session.Scene.SceneConfigId} ProcessConfigId:{scene.Process.Id} SceneType:{scene.SceneType} EntityId {tEntity.Id} : Error {e}"); + } + } + + /// + /// 运行路由消息处理逻辑。 + /// + /// 实体对象。 + /// 要处理的路由消息。 + /// 异步任务。 + protected abstract FTask Run(TEntity entity, TMessage message); + } + + /// + /// 泛型路由RPC基类,实现了 接口,用于处理请求和响应类型的路由。 + /// + /// 实体类型。 + /// 路由请求类型。 + /// 路由响应类型。 + public abstract class RouteRPC : IRouteMessageHandler where TEntity : Entity where TRouteRequest : IRouteRequest where TRouteResponse : AMessage, IRouteResponse, new() + { + /// + /// 获取处理的消息类型。 + /// + /// 消息类型。 + public Type Type() + { + return typeof(TRouteRequest); + } + + /// + /// 处理路由消息的方法。 + /// + /// 会话对象。 + /// 实体对象。 + /// RPC标识。 + /// 要处理的路由消息。 + /// 异步任务。 + public async FTask Handle(Session session, Entity entity, uint rpcId, object routeMessage) + { + if (routeMessage is not TRouteRequest tRouteRequest) + { + Log.Error($"Message type conversion error: {routeMessage.GetType().FullName} to {typeof(TRouteRequest).Name}"); + return; + } + + if (entity is not TEntity tEntity) + { + Log.Error($"Route type conversion error: {entity.GetType().Name} to {nameof(TEntity)}"); + return; + } + + var isReply = false; + var response = new TRouteResponse(); + + void Reply() + { + if (isReply) + { + return; + } + + isReply = true; + + if (session.IsDisposed) + { + return; + } + + session.Send(response, rpcId); + } + + try + { + await Run(tEntity, tRouteRequest, response, Reply); + } + catch (Exception e) + { + if (entity is not Scene scene) + { + scene = entity.Scene; + } + + Log.Error($"SceneConfigId:{session.Scene.SceneConfigId} ProcessConfigId:{scene.Process.Id} SceneType:{scene.SceneType} EntityId {tEntity.Id} : Error {e}"); + response.ErrorCode = InnerErrorCode.ErrRpcFail; + } + finally + { + Reply(); + } + } + + /// + /// 运行路由消息处理逻辑。 + /// + /// 实体对象。 + /// 请求路由消息。 + /// 响应路由消息。 + /// 发送响应的方法。 + /// 异步任务。 + protected abstract FTask Run(TEntity entity, TRouteRequest request, TRouteResponse response, Action reply); + } + + /// + /// 泛型可寻址路由基类,实现了 接口,用于处理特定实体和可寻址路由消息类型的路由。 + /// + /// 实体类型。 + /// 可寻址路由消息类型。 + public abstract class Addressable : IRouteMessageHandler where TEntity : Entity where TMessage : IAddressableRouteMessage + { + /// + /// 获取消息类型。 + /// + /// 消息类型。 + public Type Type() + { + return typeof(TMessage); + } + + /// + /// 处理可寻址路由消息。 + /// + /// 会话。 + /// 实体。 + /// RPC标识。 + /// 可寻址路由消息。 + public async FTask Handle(Session session, Entity entity, uint rpcId, object routeMessage) + { + if (routeMessage is not TMessage ruteMessage) + { + Log.Error($"Message type conversion error: {routeMessage.GetType().FullName} to {typeof(TMessage).Name}"); + return; + } + + if (entity is not TEntity tEntity) + { + Log.Error($"Route type conversion error: {entity.GetType().Name} to {nameof(TEntity)}"); + return; + } + + try + { + await Run(tEntity, ruteMessage); + } + catch (Exception e) + { + if (entity is not Scene scene) + { + scene = entity.Scene; + } + + Log.Error($"SceneConfigId:{session.Scene.SceneConfigId} ProcessConfigId:{scene.Process.Id} SceneType:{scene.SceneType} EntityId {tEntity.Id} : Error {e}"); + } + finally + { + session.Send(new RouteResponse(), rpcId); + } + } + + /// + /// 运行处理可寻址路由消息。 + /// + /// 实体。 + /// 可寻址路由消息。 + protected abstract FTask Run(TEntity entity, TMessage message); + } + + /// + /// 泛型可寻址RPC路由基类,实现了 接口,用于处理特定实体和可寻址RPC路由请求类型的路由。 + /// + /// 实体类型。 + /// 可寻址RPC路由请求类型。 + /// 可寻址RPC路由响应类型。 + public abstract class AddressableRPC : IRouteMessageHandler where TEntity : Entity where TRouteRequest : IAddressableRouteRequest where TRouteResponse : IAddressableRouteResponse, new() + { + /// + /// 获取消息类型。 + /// + /// 消息类型。 + public Type Type() + { + return typeof(TRouteRequest); + } + + /// + /// 处理可寻址RPC路由请求。 + /// + /// 会话。 + /// 实体。 + /// RPC标识。 + /// 可寻址RPC路由请求。 + public async FTask Handle(Session session, Entity entity, uint rpcId, object routeMessage) + { + if (routeMessage is not TRouteRequest tRouteRequest) + { + Log.Error($"Message type conversion error: {routeMessage.GetType().FullName} to {typeof(TRouteRequest).Name}"); + return; + } + + if (entity is not TEntity tEntity) + { + Log.Error($"Route type conversion error: {entity.GetType().Name} to {nameof(TEntity)}"); + return; + } + + var isReply = false; + var response = new TRouteResponse(); + + void Reply() + { + if (isReply) + { + return; + } + + isReply = true; + + if (session.IsDisposed) + { + return; + } + + session.Send(response, rpcId); + } + + try + { + await Run(tEntity, tRouteRequest, response, Reply); + } + catch (Exception e) + { + if (entity is not Scene scene) + { + scene = entity.Scene; + } + + Log.Error($"SceneConfigId:{session.Scene.SceneConfigId} ProcessConfigId:{scene.Process.Id} SceneType:{scene.SceneType} EntityId {tEntity.Id} : Error {e}"); + response.ErrorCode = InnerErrorCode.ErrRpcFail; + } + finally + { + Reply(); + } + } + + /// + /// 运行处理可寻址RPC路由请求。 + /// + /// 实体。 + /// 可寻址RPC路由请求。 + /// 可寻址RPC路由响应。 + /// 回复操作。 + protected abstract FTask Run(TEntity entity, TRouteRequest request, TRouteResponse response, Action reply); + } + + /// + /// 泛型漫游路由基类,实现了 接口,用于处理特定实体和漫游路由消息类型的路由。 + /// + /// 实体类型。 + /// 漫游消息类型。 + public abstract class Roaming : IRouteMessageHandler where TEntity : Entity where TMessage : IRoamingMessage + { + /// + /// 获取消息类型。 + /// + /// 消息类型。 + public Type Type() + { + return typeof(TMessage); + } + + /// + /// 处理漫游消息。 + /// + /// 会话。 + /// 实体。 + /// RPC标识。 + /// 漫游消息。 + public async FTask Handle(Session session, Entity entity, uint rpcId, object routeMessage) + { + if (routeMessage is not TMessage ruteMessage) + { + Log.Error($"Message type conversion error: {routeMessage.GetType().FullName} to {typeof(TMessage).Name}"); + return; + } + + if (entity is not TEntity tEntity) + { + Log.Error($"Route type conversion error: {entity.GetType().Name} to {nameof(TEntity)}"); + return; + } + + try + { + await Run(tEntity, ruteMessage); + } + catch (Exception e) + { + if (entity is not Scene scene) + { + scene = entity.Scene; + } + + Log.Error($"SceneConfigId:{session.Scene.SceneConfigId} ProcessConfigId:{scene.Process.Id} SceneType:{scene.SceneType} EntityId {tEntity.Id} : Error {e}"); + } + finally + { + session.Send(new RouteResponse(), rpcId); + } + } + + /// + /// 运行处理漫游消息。 + /// + /// 终点实体。 + /// 漫游消息。 + protected abstract FTask Run(TEntity terminus, TMessage message); + } + + /// + /// 漫游RPC路由基类,实现了 接口,用于处理特定实体和漫游RPC路由请求类型的路由。 + /// + /// 实体类型。 + /// 漫游RPC路由请求类型。 + /// 漫游RPC路由响应类型。 + public abstract class RoamingRPC : IRouteMessageHandler where TEntity : Entity where TRouteRequest : IRoamingRequest where TRouteResponse : IRoamingResponse, new() + { + /// + /// 获取消息类型。 + /// + /// 消息类型。 + public Type Type() + { + return typeof(TRouteRequest); + } + + /// + /// 处理漫游RPC路由请求。 + /// + /// 会话。 + /// 实体。 + /// RPC标识。 + /// 漫游RPC路由请求。 + public async FTask Handle(Session session, Entity entity, uint rpcId, object routeMessage) + { + if (routeMessage is not TRouteRequest tRouteRequest) + { + Log.Error($"Message type conversion error: {routeMessage.GetType().FullName} to {typeof(TRouteRequest).Name}"); + return; + } + + if (entity is not TEntity tEntity) + { + Log.Error($"Route type conversion error: {entity.GetType().Name} to {nameof(TEntity)}"); + return; + } + + var isReply = false; + var response = new TRouteResponse(); + + void Reply() + { + if (isReply) + { + return; + } + + isReply = true; + + if (session.IsDisposed) + { + return; + } + + session.Send(response, rpcId); + } + + try + { + await Run(tEntity, tRouteRequest, response, Reply); + } + catch (Exception e) + { + if (entity is not Scene scene) + { + scene = entity.Scene; + } + + Log.Error($"SceneConfigId:{session.Scene.SceneConfigId} ProcessConfigId:{scene.Process.Id} SceneType:{scene.SceneType} EntityId {tEntity.Id} : Error {e}"); + response.ErrorCode = InnerErrorCode.ErrRpcFail; + } + finally + { + Reply(); + } + } + + /// + /// 运行处理漫游RPC路由请求。 + /// + /// 终点实体。 + /// 漫游RPC路由请求。 + /// 漫游RPC路由响应。 + /// 回复操作。 + protected abstract FTask Run(TEntity terminus, TRouteRequest request, TRouteResponse response, Action reply); + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Message/Dispatcher/MessageDispatcherComponent.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Message/Dispatcher/MessageDispatcherComponent.cs new file mode 100644 index 0000000..0a24770 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Message/Dispatcher/MessageDispatcherComponent.cs @@ -0,0 +1,426 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Fantasy.Assembly; +using Fantasy.Async; +using Fantasy.DataStructure.Collection; +using Fantasy.DataStructure.Dictionary; +using Fantasy.Entitas; +using Fantasy.InnerMessage; +using Fantasy.Network; +#pragma warning disable CS8604 // Possible null reference argument. + +#pragma warning disable CS8602 // Dereference of a possibly null reference. +#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type. +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +namespace Fantasy.Network.Interface +{ + /// + /// 用于存储消息处理器的信息,包括类型和对象实例。 + /// + /// 消息处理器的类型 + internal sealed class HandlerInfo + { + /// + /// 获取或设置消息处理器对象。 + /// + public T Obj; + /// + /// 获取或设置消息处理器的类型。 + /// + public Type Type; + } + + /// + /// 网络消息分发组件。 + /// + public sealed class MessageDispatcherComponent : Entity, IAssembly + { + public long AssemblyIdentity { get; set; } + private readonly Dictionary _responseTypes = new Dictionary(); + private readonly DoubleMapDictionary _networkProtocols = new DoubleMapDictionary(); + private readonly Dictionary _messageHandlers = new Dictionary(); + private readonly OneToManyList _assemblyResponseTypes = new OneToManyList(); + private readonly OneToManyList _assemblyNetworkProtocols = new OneToManyList(); + private readonly OneToManyList> _assemblyMessageHandlers = new OneToManyList>(); +#if FANTASY_UNITY + private readonly Dictionary _messageDelegateHandlers = new Dictionary(); +#endif +#if FANTASY_NET + private readonly Dictionary _customRouteMap = new Dictionary(); + private readonly OneToManyList _assemblyCustomRouteMap = new OneToManyList(); + private readonly Dictionary _routeMessageHandlers = new Dictionary(); + private readonly OneToManyList> _assemblyRouteMessageHandlers = new OneToManyList>(); +#endif + private CoroutineLock _receiveRouteMessageLock; + + #region Initialize + + internal async FTask Initialize() + { + _receiveRouteMessageLock = Scene.CoroutineLockComponent.Create(GetType().TypeHandle.Value.ToInt64()); + await AssemblySystem.Register(this); + return this; + } + + public async FTask Load(long assemblyIdentity) + { + var tcs = FTask.Create(false); + Scene?.ThreadSynchronizationContext.Post(() => + { + LoadInner(assemblyIdentity); + tcs.SetResult(); + }); + await tcs; + } + + private void LoadInner(long assemblyIdentity) + { + // 遍历所有实现了IMessage接口的类型,获取OpCode并添加到_networkProtocols字典中 + foreach (var type in AssemblySystem.ForEach(assemblyIdentity, typeof(IMessage))) + { + var obj = (IMessage) Activator.CreateInstance(type); + var opCode = obj.OpCode(); + + _networkProtocols.Add(opCode, type); + + var responseType = type.GetProperty("ResponseType"); + + // 如果类型具有ResponseType属性,将其添加到_responseTypes字典中 + if (responseType != null) + { + _responseTypes.Add(type, responseType.PropertyType); + _assemblyResponseTypes.Add(assemblyIdentity, type); + } + + _assemblyNetworkProtocols.Add(assemblyIdentity, opCode); + } + + // 遍历所有实现了IMessageHandler接口的类型,创建实例并添加到_messageHandlers字典中 + foreach (var type in AssemblySystem.ForEach(assemblyIdentity, typeof(IMessageHandler))) + { + var obj = (IMessageHandler) Activator.CreateInstance(type); + + if (obj == null) + { + throw new Exception($"message handle {type.Name} is null"); + } + + var key = obj.Type(); + _messageHandlers.Add(key, obj); + _assemblyMessageHandlers.Add(assemblyIdentity, new HandlerInfo() + { + Obj = obj, Type = key + }); + } + + // 如果编译符号FANTASY_NET存在,遍历所有实现了IRouteMessageHandler接口的类型,创建实例并添加到_routeMessageHandlers字典中 +#if FANTASY_NET + foreach (var type in AssemblySystem.ForEach(assemblyIdentity, typeof(IRouteMessageHandler))) + { + var obj = (IRouteMessageHandler) Activator.CreateInstance(type); + + if (obj == null) + { + throw new Exception($"message handle {type.Name} is null"); + } + + var key = obj.Type(); + _routeMessageHandlers.Add(key, obj); + _assemblyRouteMessageHandlers.Add(assemblyIdentity, new HandlerInfo() + { + Obj = obj, Type = key + }); + } + + foreach (var type in AssemblySystem.ForEach(assemblyIdentity, typeof(ICustomRoute))) + { + var obj = (ICustomRoute) Activator.CreateInstance(type); + + if (obj == null) + { + throw new Exception($"message handle {type.Name} is null"); + } + + var opCode = obj.OpCode(); + _customRouteMap[opCode] = obj.RouteType; + _assemblyCustomRouteMap.Add(assemblyIdentity, opCode); + } +#endif + } + + public async FTask ReLoad(long assemblyIdentity) + { + var tcs = FTask.Create(false); + Scene?.ThreadSynchronizationContext.Post(() => + { + OnUnLoadInner(assemblyIdentity); + LoadInner(assemblyIdentity); + tcs.SetResult(); + }); + await tcs; + } + + public async FTask OnUnLoad(long assemblyIdentity) + { + var tcs = FTask.Create(false); + Scene?.ThreadSynchronizationContext.Post(() => + { + OnUnLoadInner(assemblyIdentity); + tcs.SetResult(); + }); + await tcs; + } + + private void OnUnLoadInner(long assemblyIdentity) + { + // 移除程序集对应的ResponseType类型和OpCode信息 + if (_assemblyResponseTypes.TryGetValue(assemblyIdentity, out var removeResponseTypes)) + { + foreach (var removeResponseType in removeResponseTypes) + { + _responseTypes.Remove(removeResponseType); + } + + _assemblyResponseTypes.RemoveByKey(assemblyIdentity); + } + + if (_assemblyNetworkProtocols.TryGetValue(assemblyIdentity, out var removeNetworkProtocols)) + { + foreach (var removeNetworkProtocol in removeNetworkProtocols) + { + _networkProtocols.RemoveByKey(removeNetworkProtocol); + } + + _assemblyNetworkProtocols.RemoveByKey(assemblyIdentity); + } + + // 移除程序集对应的消息处理器信息 + if (_assemblyMessageHandlers.TryGetValue(assemblyIdentity, out var removeMessageHandlers)) + { + foreach (var removeMessageHandler in removeMessageHandlers) + { + _messageHandlers.Remove(removeMessageHandler.Type); + } + + _assemblyMessageHandlers.RemoveByKey(assemblyIdentity); + } + + // 如果编译符号FANTASY_NET存在,移除程序集对应的路由消息处理器信息 +#if FANTASY_NET + if (_assemblyRouteMessageHandlers.TryGetValue(assemblyIdentity, out var removeRouteMessageHandlers)) + { + foreach (var removeRouteMessageHandler in removeRouteMessageHandlers) + { + _routeMessageHandlers.Remove(removeRouteMessageHandler.Type); + } + + _assemblyRouteMessageHandlers.RemoveByKey(assemblyIdentity); + } + + if (_assemblyCustomRouteMap.TryGetValue(assemblyIdentity, out var removeCustomRouteMap)) + { + foreach (var removeCustom in removeCustomRouteMap) + { + _customRouteMap.Remove(removeCustom); + } + + _assemblyCustomRouteMap.RemoveByKey(assemblyIdentity); + } +#endif + } + +#if FANTASY_UNITY + /// + /// 手动注册一个消息处理器。 + /// + /// + /// + public void RegisterHandler(MessageDelegate @delegate) where T : IMessage + { + var type = typeof(T); + + if (!_messageDelegateHandlers.TryGetValue(type, out var messageDelegate)) + { + messageDelegate = new MessageDelegateHandler(); + _messageDelegateHandlers.Add(type,messageDelegate); + } + + messageDelegate.Register(@delegate); + } + + /// + /// 手动卸载一个消息处理器,必须是通过RegisterHandler方法注册的消息处理器。 + /// + /// + /// + public void UnRegisterHandler(MessageDelegate @delegate) where T : IMessage + { + var type = typeof(T); + + if (!_messageDelegateHandlers.TryGetValue(type, out var messageDelegate)) + { + return; + } + + if (messageDelegate.UnRegister(@delegate) != 0) + { + return; + } + + _messageDelegateHandlers.Remove(type); + } +#endif + #endregion + + /// + /// 处理普通消息,将消息分发给相应的消息处理器。 + /// + /// 会话对象 + /// 消息类型 + /// 消息对象 + /// RPC标识 + /// 协议码 + public void MessageHandler(Session session, Type type, object message, uint rpcId, uint protocolCode) + { +#if FANTASY_UNITY + if(_messageDelegateHandlers.TryGetValue(type,out var messageDelegateHandler)) + { + messageDelegateHandler.Handle(session, message); + return; + } +#endif + if (!_messageHandlers.TryGetValue(type, out var messageHandler)) + { + Log.Warning($"Scene:{session.Scene.Id} Found Unhandled Message: {message.GetType()}"); + return; + } + + // 调用消息处理器的Handle方法并启动协程执行处理逻辑 + messageHandler.Handle(session, rpcId, protocolCode, message).Coroutine(); + } + + // 如果编译符号FANTASY_NET存在,定义处理路由消息的方法 +#if FANTASY_NET + /// + /// 处理路由消息,将消息分发给相应的路由消息处理器。 + /// + /// 会话对象 + /// 消息类型 + /// 实体对象 + /// 消息对象 + /// RPC标识 + public async FTask RouteMessageHandler(Session session, Type type, Entity entity, object message, uint rpcId) + { + if (!_routeMessageHandlers.TryGetValue(type, out var routeMessageHandler)) + { + Log.Warning($"Scene:{session.Scene.Id} Found Unhandled RouteMessage: {message.GetType()}"); + + if (message is IRouteRequest request) + { + FailRouteResponse(session, request.GetType(), InnerErrorCode.ErrEntityNotFound, rpcId); + } + + return; + } + + var runtimeId = entity.RuntimeId; + var sessionRuntimeId = session.RuntimeId; + + if (entity is Scene) + { + // 如果是Scene的话、就不要加锁了、如果加锁很一不小心就可能会造成死锁 + await routeMessageHandler.Handle(session, entity, rpcId, message); + return; + } + + // 使用协程锁来确保多线程安全 + using (await _receiveRouteMessageLock.Wait(runtimeId)) + { + if (sessionRuntimeId != session.RuntimeId) + { + return; + } + + if (runtimeId != entity.RuntimeId) + { + if (message is IRouteRequest request) + { + FailRouteResponse(session, request.GetType(), InnerErrorCode.ErrEntityNotFound, rpcId); + } + + return; + } + + await routeMessageHandler.Handle(session, entity, rpcId, message); + } + } + + internal bool GetCustomRouteType(long protocolCode, out int routeType) + { + return _customRouteMap.TryGetValue(protocolCode, out routeType); + } +#endif + internal void FailRouteResponse(Session session, Type requestType, uint error, uint rpcId) + { + var response = CreateRouteResponse(requestType, error); + session.Send(response, rpcId); + } + + internal IResponse CreateResponse(Type requestType, uint error) + { + IResponse response; + + if (_responseTypes.TryGetValue(requestType, out var responseType)) + { + response = (IResponse) Activator.CreateInstance(responseType); + } + else + { + response = new Response(); + } + + response.ErrorCode = error; + return response; + } + + internal IRouteResponse CreateRouteResponse(Type requestType, uint error) + { + IRouteResponse response; + + if (_responseTypes.TryGetValue(requestType, out var responseType)) + { + response = (IRouteResponse) Activator.CreateInstance(responseType); + } + else + { + response = new RouteResponse(); + } + + response.ErrorCode = error; + return response; + } + + /// + /// 根据消息类型获取对应的OpCode。 + /// + /// 消息类型 + /// 消息对应的OpCode + public uint GetOpCode(Type type) + { + return _networkProtocols.GetKeyByValue(type); + } + + /// + /// 根据OpCode获取对应的消息类型。 + /// + /// OpCode + /// OpCode对应的消息类型 + public Type GetOpCodeType(uint code) + { + return _networkProtocols.GetValueByKey(code); + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Message/IMessage.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Message/IMessage.cs new file mode 100644 index 0000000..aea7d08 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Message/IMessage.cs @@ -0,0 +1,95 @@ +using System; +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + +namespace Fantasy.Network.Interface +{ + /// + /// 表示通用消息接口。 + /// + public interface IMessage + { + /// + /// 获取消息的操作代码。 + /// + /// 操作代码。 + uint OpCode(); + } + + /// + /// 表示请求消息接口。 + /// + public interface IRequest : IMessage + { + + } + + /// + /// 表示响应消息接口。 + /// + public interface IResponse : IMessage + { + /// + /// 获取或设置错误代码。 + /// + uint ErrorCode { get; set; } + } + // 普通路由消息 + /// + /// 表示普通路由消息的接口,继承自请求接口。 + /// + public interface IRouteMessage : IRequest + { + + } + + /// + /// 普通路由请求接口,继承自普通路由消息接口。 + /// + public interface IRouteRequest : IRouteMessage { } + /// + /// 普通路由响应接口,继承自响应接口。 + /// + public interface IRouteResponse : IResponse { } + // 可寻址协议 + /// + /// 表示可寻址协议的普通路由消息接口,继承自普通路由消息接口。 + /// + public interface IAddressableRouteMessage : IRouteMessage { } + /// + /// 可寻址协议的普通路由请求接口,继承自可寻址协议的普通路由消息接口。 + /// + public interface IAddressableRouteRequest : IRouteRequest { } + /// + /// 可寻址协议的普通路由响应接口,继承自普通路由响应接口。 + /// + public interface IAddressableRouteResponse : IRouteResponse { } + // 自定义Route协议 + public interface ICustomRoute : IMessage + { + int RouteType { get; } + } + /// + /// 表示自定义Route协议的普通路由消息接口,继承自普通路由消息接口。 + /// + public interface ICustomRouteMessage : IRouteMessage, ICustomRoute { } + /// + /// 自定义Route协议的普通路由请求接口,继承自自定义Route协议的普通路由消息接口。 + /// + public interface ICustomRouteRequest : IRouteRequest, ICustomRoute { } + /// + /// 自定义Route协议的普通路由响应接口,继承自普通路由响应接口。 + /// + public interface ICustomRouteResponse : IRouteResponse { } + /// + /// 表示漫游协议的普通路由消息接口,继承自普通路由消息接口。 + /// + public interface IRoamingMessage : IRouteMessage, ICustomRoute { } + /// + /// 漫游协议的普通路由请求接口,继承自自定义Route协议的普通路由消息接口。 + /// + public interface IRoamingRequest : IRoamingMessage { } + /// + /// 漫游协议的普通路由响应接口,继承自普通路由响应接口。 + /// + public interface IRoamingResponse : IRouteResponse { } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Message/InnerMessage.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Message/InnerMessage.cs new file mode 100644 index 0000000..643c92b --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Message/InnerMessage.cs @@ -0,0 +1,319 @@ +using Fantasy.Network.Interface; +using Fantasy.Serialize; +using MongoDB.Bson.Serialization.Attributes; +using ProtoBuf; +#if FANTASY_NET +using Fantasy.Network.Roaming; +#endif +#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. +// ReSharper disable InconsistentNaming +// ReSharper disable PropertyCanBeMadeInitOnly.Global +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + +namespace Fantasy.InnerMessage +{ + [ProtoContract] + public sealed partial class BenchmarkMessage : AMessage, IMessage + { + public uint OpCode() + { + return Fantasy.Network.OpCode.BenchmarkMessage; + } + } + [ProtoContract] + public partial class BenchmarkRequest : AMessage, IRequest + { + public uint OpCode() + { + return Fantasy.Network.OpCode.BenchmarkRequest; + } + [ProtoIgnore] + public BenchmarkResponse ResponseType { get; set; } + [ProtoMember(1)] + public long RpcId { get; set; } + } + + [ProtoContract] + public partial class BenchmarkResponse : AMessage, IResponse + { + public uint OpCode() + { + return Fantasy.Network.OpCode.BenchmarkResponse; + } + [ProtoMember(1)] + public long RpcId { get; set; } + [ProtoMember(2)] + public uint ErrorCode { get; set; } + } + public sealed partial class Response : AMessage, IResponse + { + public uint OpCode() + { + return Fantasy.Network.OpCode.DefaultResponse; + } + [ProtoMember(1)] + public long RpcId { get; set; } + [ProtoMember(2)] + public uint ErrorCode { get; set; } + } + [ProtoContract] + public sealed partial class RouteResponse : AMessage, IRouteResponse + { + public uint OpCode() + { + return Fantasy.Network.OpCode.DefaultRouteResponse; + } + [ProtoMember(1)] + public long RpcId { get; set; } + [ProtoMember(2)] + public uint ErrorCode { get; set; } + } + [ProtoContract] + public partial class PingRequest : AMessage, IRequest + { + public uint OpCode() + { + return Fantasy.Network.OpCode.PingRequest; + } + [ProtoIgnore] + public PingResponse ResponseType { get; set; } + [ProtoMember(1)] + public long RpcId { get; set; } + } + + [ProtoContract] + public partial class PingResponse : AMessage, IResponse + { + public uint OpCode() + { + return Fantasy.Network.OpCode.PingResponse; + } + [ProtoMember(1)] + public long RpcId { get; set; } + [ProtoMember(2)] + public uint ErrorCode { get; set; } + [ProtoMember(3)] + public long Now; + } + [ProtoContract] + public partial class I_AddressableAdd_Request : AMessage, IRouteRequest + { + [ProtoIgnore] + public I_AddressableAdd_Response ResponseType { get; set; } + public uint OpCode() { return Fantasy.Network.OpCode.AddressableAddRequest; } + public long RouteTypeOpCode() { return 1; } + [ProtoMember(1)] + public long AddressableId { get; set; } + [ProtoMember(2)] + public long RouteId { get; set; } + [ProtoMember(3)] + public bool IsLock { get; set; } + } + [ProtoContract] + public partial class I_AddressableAdd_Response : AMessage, IRouteResponse + { + public uint OpCode() { return Fantasy.Network.OpCode.AddressableAddResponse; } + [ProtoMember(1)] + public uint ErrorCode { get; set; } + } + [ProtoContract] + public partial class I_AddressableGet_Request : AMessage, IRouteRequest + { + [ProtoIgnore] + public I_AddressableGet_Response ResponseType { get; set; } + public uint OpCode() { return Fantasy.Network.OpCode.AddressableGetRequest; } + public long RouteTypeOpCode() { return 1; } + [ProtoMember(1)] + public long AddressableId { get; set; } + } + [ProtoContract] + public partial class I_AddressableGet_Response : AMessage, IRouteResponse + { + public uint OpCode() { return Fantasy.Network.OpCode.AddressableGetResponse; } + [ProtoMember(2)] + public uint ErrorCode { get; set; } + [ProtoMember(1)] + public long RouteId { get; set; } + } + [ProtoContract] + public partial class I_AddressableRemove_Request : AMessage, IRouteRequest + { + [ProtoIgnore] + public I_AddressableRemove_Response ResponseType { get; set; } + public uint OpCode() { return Fantasy.Network.OpCode.AddressableRemoveRequest; } + public long RouteTypeOpCode() { return 1; } + [ProtoMember(1)] + public long AddressableId { get; set; } + } + [ProtoContract] + public partial class I_AddressableRemove_Response : AMessage, IRouteResponse + { + public uint OpCode() { return Fantasy.Network.OpCode.AddressableRemoveResponse; } + [ProtoMember(1)] + public uint ErrorCode { get; set; } + } + [ProtoContract] + public partial class I_AddressableLock_Request : AMessage, IRouteRequest + { + [ProtoIgnore] + public I_AddressableLock_Response ResponseType { get; set; } + public uint OpCode() { return Fantasy.Network.OpCode.AddressableLockRequest; } + public long RouteTypeOpCode() { return 1; } + [ProtoMember(1)] + public long AddressableId { get; set; } + } + [ProtoContract] + public partial class I_AddressableLock_Response : AMessage, IRouteResponse + { + public uint OpCode() { return Fantasy.Network.OpCode.AddressableLockResponse; } + [ProtoMember(1)] + public uint ErrorCode { get; set; } + } + [ProtoContract] + public partial class I_AddressableUnLock_Request : AMessage, IRouteRequest + { + [ProtoIgnore] + public I_AddressableUnLock_Response ResponseType { get; set; } + public uint OpCode() { return Fantasy.Network.OpCode.AddressableUnLockRequest; } + public long RouteTypeOpCode() { return 1; } + [ProtoMember(1)] + public long AddressableId { get; set; } + [ProtoMember(2)] + public long RouteId { get; set; } + [ProtoMember(3)] + public string Source { get; set; } + } + [ProtoContract] + public partial class I_AddressableUnLock_Response : AMessage, IRouteResponse + { + public uint OpCode() { return Fantasy.Network.OpCode.AddressableUnLockResponse; } + [ProtoMember(1)] + public uint ErrorCode { get; set; } + } +#if FANTASY_NET + [ProtoContract] + public sealed class I_LinkRoamingRequest : AMessage, IRouteRequest + { + [ProtoIgnore] + public I_LinkRoamingResponse ResponseType { get; set; } + public uint OpCode() { return Fantasy.Network.OpCode.LinkRoamingRequest; } + public long RouteTypeOpCode() { return 1; } + [ProtoMember(1)] + public long RoamingId { get; set; } + [ProtoMember(2)] + public int RoamingType { get; set; } + [ProtoMember(3)] + public long ForwardSessionRouteId { get; set; } + [ProtoMember(4)] + public long SceneRouteId { get; set; } + } + [ProtoContract] + public sealed class I_LinkRoamingResponse : AMessage, IRouteResponse + { + public uint OpCode() { return Fantasy.Network.OpCode.LinkRoamingResponse; } + [ProtoMember(1)] + public long TerminusId { get; set; } + [ProtoMember(2)] + public uint ErrorCode { get; set; } + } + [ProtoContract] + public sealed class I_UnLinkRoamingRequest : AMessage, IRouteRequest + { + [ProtoIgnore] + public I_UnLinkRoamingResponse ResponseType { get; set; } + public uint OpCode() { return Fantasy.Network.OpCode.UnLinkRoamingRequest; } + public long RouteTypeOpCode() { return 1; } + [ProtoMember(1)] + public long RoamingId { get; set; } + [ProtoMember(2)] + public bool DisposeRoaming { get; set; } + } + [ProtoContract] + public sealed class I_UnLinkRoamingResponse : AMessage, IRouteResponse + { + public uint OpCode() { return Fantasy.Network.OpCode.UnLinkRoamingResponse; } + [ProtoMember(1)] + public uint ErrorCode { get; set; } + } + [ProtoContract] + public partial class I_LockTerminusIdRequest : AMessage, IRouteRequest + { + [ProtoIgnore] + public I_LockTerminusIdResponse ResponseType { get; set; } + public uint OpCode() { return Fantasy.Network.OpCode.LockTerminusIdRequest; } + [ProtoMember(1)] + public long SessionRuntimeId { get; set; } + [ProtoMember(2)] + public int RoamingType { get; set; } + } + [ProtoContract] + public partial class I_LockTerminusIdResponse : AMessage, IRouteResponse + { + public uint OpCode() { return Fantasy.Network.OpCode.LockTerminusIdResponse; } + [ProtoMember(1)] + public uint ErrorCode { get; set; } + } + [ProtoContract] + public sealed class I_UnLockTerminusIdRequest : AMessage, IRouteRequest + { + [ProtoIgnore] + public I_UnLockTerminusIdResponse ResponseType { get; set; } + public uint OpCode() { return Fantasy.Network.OpCode.UnLockTerminusIdRequest; } + public long RouteTypeOpCode() { return 1; } + [ProtoMember(1)] + public long SessionRuntimeId { get; set; } + [ProtoMember(2)] + public int RoamingType { get; set; } + [ProtoMember(3)] + public long TerminusId { get; set; } + [ProtoMember(4)] + public long TargetSceneRouteId { get; set; } + } + [ProtoContract] + public sealed class I_UnLockTerminusIdResponse : AMessage, IRouteResponse + { + public uint OpCode() { return Fantasy.Network.OpCode.UnLockTerminusIdResponse; } + [ProtoMember(1)] + public uint ErrorCode { get; set; } + } + /// + /// 漫游传送终端的请求 + /// + public partial class I_TransferTerminusRequest : AMessage, IRouteRequest + { + [BsonIgnore] + public I_TransferTerminusResponse ResponseType { get; set; } + public uint OpCode() { return Fantasy.Network.OpCode.TransferTerminusRequest; } + public Terminus Terminus { get; set; } + } + public partial class I_TransferTerminusResponse : AMessage, IRouteResponse + { + public uint OpCode() { return Fantasy.Network.OpCode.TransferTerminusResponse; } + public uint ErrorCode { get; set; } + } + /// + /// 用于服务器之间获取漫游的TerminusId。 + /// + [ProtoContract] + public partial class I_GetTerminusIdRequest : AMessage, IRouteRequest + { + [ProtoIgnore] + public I_GetTerminusIdResponse ResponseType { get; set; } + public uint OpCode() { return Fantasy.Network.OpCode.GetTerminusIdRequest; } + [ProtoMember(1)] + public int RoamingType { get; set; } + [ProtoMember(2)] + public long SessionRuntimeId { get; set; } + } + [ProtoContract] + public partial class I_GetTerminusIdResponse : AMessage, IRouteResponse + { + public uint OpCode() { return Fantasy.Network.OpCode.GetTerminusIdResponse; } + [ProtoMember(1)] + public long TerminusId { get; set; } + [ProtoMember(2)] + public uint ErrorCode { get; set; } + } +#endif +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Message/PacketParser/Handler/BufferPacketParser.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Message/PacketParser/Handler/BufferPacketParser.cs new file mode 100644 index 0000000..e4d1540 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Message/PacketParser/Handler/BufferPacketParser.cs @@ -0,0 +1,386 @@ +using System; +using System.Buffers; +using System.IO; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Fantasy.Helper; +using Fantasy.Network; +using Fantasy.Network.Interface; +using Fantasy.PacketParser.Interface; +using Fantasy.Serialize; + +// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + +#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. +namespace Fantasy.PacketParser +{ + /// + /// BufferPacketParser消息格式化器抽象类 + /// 这个不会用在TCP协议中、因此不用考虑分包和粘包的问题。 + /// 目前这个只会用在KCP协议中、因为KCP出来的就是一个完整的包、所以可以一次性全部解析出来。 + /// 如果是用在其他协议上可能会出现问题。 + /// + public abstract class BufferPacketParser : APacketParser + { + protected uint RpcId; + protected long RouteId; + protected uint ProtocolCode; + protected int MessagePacketLength; + public override void Dispose() + { + RpcId = 0; + RouteId = 0; + ProtocolCode = 0; + MessagePacketLength = 0; + base.Dispose(); + } + /// + /// 解包方法 + /// + /// buffer + /// count + /// packInfo + /// + public abstract bool UnPack(byte[] buffer, ref int count, out APackInfo packInfo); + } +#if FANTASY_NET + /// + /// 服务器之间专用的BufferPacketParser消息格式化器 + /// + public sealed class InnerBufferPacketParser : BufferPacketParser + { + /// + /// + /// + /// + /// + /// + /// + /// + public override unsafe bool UnPack(byte[] buffer, ref int count, out APackInfo packInfo) + { + packInfo = null; + + if (buffer.Length < count) + { + throw new ScanException($"The buffer length is less than the specified count. buffer.Length={buffer.Length} count={count}"); + } + + if (count < Packet.InnerPacketHeadLength) + { + // 如果内存资源中的数据长度小于内部消息头的长度,无法解析 + return false; + } + + fixed (byte* bufferPtr = buffer) + { + MessagePacketLength = *(int*)bufferPtr; + + if (MessagePacketLength > Packet.PacketBodyMaxLength || count < MessagePacketLength) + { + // 检查消息体长度是否超出限制 + throw new ScanException($"The received information exceeds the maximum limit = {MessagePacketLength}"); + } + + ProtocolCode = *(uint*)(bufferPtr + Packet.PacketLength); + RpcId = *(uint*)(bufferPtr + Packet.InnerPacketRpcIdLocation); + RouteId = *(long*)(bufferPtr + Packet.InnerPacketRouteRouteIdLocation); + } + + packInfo = InnerPackInfo.Create(Network); + packInfo.RpcId = RpcId; + packInfo.RouteId = RouteId; + packInfo.ProtocolCode = ProtocolCode; + packInfo.RentMemoryStream(MemoryStreamBufferSource.UnPack, count).Write(buffer, 0, count); + return true; + } + + public override MemoryStreamBuffer Pack(ref uint rpcId, ref long routeId, MemoryStreamBuffer memoryStream, IMessage message) + { + return memoryStream == null ? Pack(ref rpcId, ref routeId, message) : Pack(ref rpcId, ref routeId, memoryStream); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private unsafe MemoryStreamBuffer Pack(ref uint rpcId, ref long routeId, MemoryStreamBuffer memoryStream) + { + fixed (byte* bufferPtr = memoryStream.GetBuffer()) + { + *(uint*)(bufferPtr + Packet.InnerPacketRpcIdLocation) = rpcId; + *(long*)(bufferPtr + Packet.InnerPacketRouteRouteIdLocation) = routeId; + } + + return memoryStream; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private unsafe MemoryStreamBuffer Pack(ref uint rpcId, ref long routeId, IMessage message) + { + var memoryStreamLength = 0; + var messageType = message.GetType(); + var memoryStream = Network.MemoryStreamBufferPool.RentMemoryStream(MemoryStreamBufferSource.Pack); + OpCodeIdStruct opCodeIdStruct = message.OpCode(); + memoryStream.Seek(Packet.InnerPacketHeadLength, SeekOrigin.Begin); + + if (SerializerManager.TryGetSerializer(opCodeIdStruct.OpCodeProtocolType, out var serializer)) + { + serializer.Serialize(messageType, message, memoryStream); + memoryStreamLength = (int)memoryStream.Position; + } + else + { + Log.Error($"type:{messageType} Does not support processing protocol"); + } + + var opCode = Scene.MessageDispatcherComponent.GetOpCode(messageType); + var packetBodyCount = memoryStreamLength - Packet.InnerPacketHeadLength; + + if (packetBodyCount == 0) + { + // protoBuf做了一个优化、就是当序列化的对象里的属性和字段都为默认值的时候就不会序列化任何东西。 + // 为了TCP的分包和粘包、需要判定下是当前包数据不完整还是本应该如此、所以用-1代表。 + packetBodyCount = -1; + } + + if (packetBodyCount > Packet.PacketBodyMaxLength) + { + // 检查消息体长度是否超出限制 + throw new Exception($"Message content exceeds {Packet.PacketBodyMaxLength} bytes"); + } + + fixed (byte* bufferPtr = memoryStream.GetBuffer()) + { + *(int*)bufferPtr = packetBodyCount; + *(uint*)(bufferPtr + Packet.PacketLength) = opCode; + *(uint*)(bufferPtr + Packet.InnerPacketRpcIdLocation) = rpcId; + *(long*)(bufferPtr + Packet.InnerPacketRouteRouteIdLocation) = routeId; + } + + return memoryStream; + } + } +#endif + /// + /// 客户端和服务器之间专用的BufferPacketParser消息格式化器 + /// + public sealed class OuterBufferPacketParser : BufferPacketParser + { + /// + /// + /// + /// + /// + /// + /// + /// + public override unsafe bool UnPack(byte[] buffer, ref int count, out APackInfo packInfo) + { + packInfo = null; + + if (buffer.Length < count) + { + throw new ScanException($"The buffer length is less than the specified count. buffer.Length={buffer.Length} count={count}"); + } + + if (count < Packet.OuterPacketHeadLength) + { + // 如果内存资源中的数据长度小于内部消息头的长度,无法解析 + return false; + } + + fixed (byte* bufferPtr = buffer) + { + MessagePacketLength = *(int*)bufferPtr; + + if (MessagePacketLength > Packet.PacketBodyMaxLength || count < MessagePacketLength) + { + // 检查消息体长度是否超出限制 + throw new ScanException($"The received information exceeds the maximum limit = {MessagePacketLength}"); + } + + ProtocolCode = *(uint*)(bufferPtr + Packet.PacketLength); + RpcId = *(uint*)(bufferPtr + Packet.OuterPacketRpcIdLocation); + } + + packInfo = OuterPackInfo.Create(Network); + packInfo.RpcId = RpcId; + packInfo.ProtocolCode = ProtocolCode; + packInfo.RentMemoryStream(MemoryStreamBufferSource.UnPack, count).Write(buffer, 0, count); + return true; + } + + public override MemoryStreamBuffer Pack(ref uint rpcId, ref long routeId, MemoryStreamBuffer memoryStream, IMessage message) + { + return memoryStream == null ? Pack(ref rpcId, message) : Pack(ref rpcId, memoryStream); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private unsafe MemoryStreamBuffer Pack(ref uint rpcId, MemoryStreamBuffer memoryStream) + { + fixed (byte* bufferPtr = memoryStream.GetBuffer()) + { + *(uint*)(bufferPtr + Packet.InnerPacketRpcIdLocation) = rpcId; + } + + return memoryStream; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private unsafe MemoryStreamBuffer Pack(ref uint rpcId, IMessage message) + { + var memoryStreamLength = 0; + var messageType = message.GetType(); + var memoryStream = Network.MemoryStreamBufferPool.RentMemoryStream(MemoryStreamBufferSource.Pack); + OpCodeIdStruct opCodeIdStruct = message.OpCode(); + memoryStream.Seek(Packet.OuterPacketHeadLength, SeekOrigin.Begin); + + if (SerializerManager.TryGetSerializer(opCodeIdStruct.OpCodeProtocolType, out var serializer)) + { + serializer.Serialize(messageType, message, memoryStream); + memoryStreamLength = (int)memoryStream.Position; + } + else + { + Log.Error($"type:{messageType} Does not support processing protocol"); + } + + var opCode = Scene.MessageDispatcherComponent.GetOpCode(messageType); + var packetBodyCount = memoryStreamLength - Packet.OuterPacketHeadLength; + + if (packetBodyCount == 0) + { + // protoBuf做了一个优化、就是当序列化的对象里的属性和字段都为默认值的时候就不会序列化任何东西。 + // 为了TCP的分包和粘包、需要判定下是当前包数据不完整还是本应该如此、所以用-1代表。 + packetBodyCount = -1; + } + + if (packetBodyCount > Packet.PacketBodyMaxLength) + { + // 检查消息体长度是否超出限制 + throw new Exception($"Message content exceeds {Packet.PacketBodyMaxLength} bytes"); + } + + fixed (byte* bufferPtr = memoryStream.GetBuffer()) + { + *(int*)bufferPtr = packetBodyCount; + *(uint*)(bufferPtr + Packet.PacketLength) = opCode; + *(uint*)(bufferPtr + Packet.OuterPacketRpcIdLocation) = rpcId; + } + + return memoryStream; + } + } + /// + /// Webgl专用的客户端和服务器之间专用的BufferPacketParser消息格式化器 + /// + public sealed class OuterWebglBufferPacketParser : BufferPacketParser + { + /// + /// + /// + /// + /// + /// + /// + /// + public override bool UnPack(byte[] buffer, ref int count, out APackInfo packInfo) + { + packInfo = null; + + if (buffer.Length < count) + { + throw new ScanException($"The buffer length is less than the specified count. buffer.Length={buffer.Length} count={count}"); + } + + if (count < Packet.OuterPacketHeadLength) + { + // 如果内存资源中的数据长度小于内部消息头的长度,无法解析 + return false; + } + + MessagePacketLength = BitConverter.ToInt32(buffer, 0); + + if (MessagePacketLength > Packet.PacketBodyMaxLength || count < MessagePacketLength) + { + // 检查消息体长度是否超出限制 + throw new ScanException($"The received information exceeds the maximum limit = {MessagePacketLength}"); + } + + ProtocolCode = BitConverter.ToUInt32(buffer, Packet.PacketLength); + RpcId = BitConverter.ToUInt32(buffer, Packet.OuterPacketRpcIdLocation); + + packInfo = OuterPackInfo.Create(Network); + packInfo.RpcId = RpcId; + packInfo.ProtocolCode = ProtocolCode; + packInfo.RentMemoryStream(MemoryStreamBufferSource.UnPack, count).Write(buffer, 0, count); + return true; + } + + public override MemoryStreamBuffer Pack(ref uint rpcId, ref long routeId, MemoryStreamBuffer memoryStream, IMessage message) + { + return memoryStream == null ? Pack(ref rpcId, message) : Pack(ref rpcId, memoryStream); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private MemoryStreamBuffer Pack(ref uint rpcId, MemoryStreamBuffer memoryStream) + { + var buffer = memoryStream.GetBuffer().AsSpan(); +#if FANTASY_NET + MemoryMarshal.Write(buffer.Slice(Packet.OuterPacketRpcIdLocation, sizeof(uint)), in rpcId); +#endif +#if FANTASY_UNITY + MemoryMarshal.Write(buffer.Slice(Packet.OuterPacketRpcIdLocation, sizeof(uint)), ref rpcId); +#endif + return memoryStream; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private MemoryStreamBuffer Pack(ref uint rpcId, IMessage message) + { + var memoryStreamLength = 0; + var messageType = message.GetType(); + var memoryStream = Network.MemoryStreamBufferPool.RentMemoryStream(MemoryStreamBufferSource.UnPack); + OpCodeIdStruct opCodeIdStruct = message.OpCode(); + memoryStream.Seek(Packet.OuterPacketHeadLength, SeekOrigin.Begin); + + if (SerializerManager.TryGetSerializer(opCodeIdStruct.OpCodeProtocolType, out var serializer)) + { + serializer.Serialize(messageType, message, memoryStream); + memoryStreamLength = (int)memoryStream.Position; + } + else + { + Log.Error($"type:{messageType} Does not support processing protocol"); + } + + var opCode = Scene.MessageDispatcherComponent.GetOpCode(messageType); + var packetBodyCount = memoryStreamLength - Packet.OuterPacketHeadLength; + + if (packetBodyCount == 0) + { + // protoBuf做了一个优化、就是当序列化的对象里的属性和字段都为默认值的时候就不会序列化任何东西。 + // 为了TCP的分包和粘包、需要判定下是当前包数据不完整还是本应该如此、所以用-1代表。 + packetBodyCount = -1; + } + + if (packetBodyCount > Packet.PacketBodyMaxLength) + { + // 检查消息体长度是否超出限制 + throw new Exception($"Message content exceeds {Packet.PacketBodyMaxLength} bytes"); + } + + var buffer = memoryStream.GetBuffer().AsSpan(); +#if FANTASY_NET + MemoryMarshal.Write(buffer, in packetBodyCount); + MemoryMarshal.Write(buffer.Slice(Packet.PacketLength, sizeof(uint)), in opCode); + MemoryMarshal.Write(buffer.Slice(Packet.OuterPacketRpcIdLocation, sizeof(uint)), in rpcId); +#endif +#if FANTASY_UNITY + MemoryMarshal.Write(buffer, ref packetBodyCount); + MemoryMarshal.Write(buffer.Slice(Packet.PacketLength, sizeof(uint)), ref opCode); + MemoryMarshal.Write(buffer.Slice(Packet.OuterPacketRpcIdLocation, sizeof(uint)), ref rpcId); +#endif + return memoryStream; + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Message/PacketParser/Handler/CircularBufferPacketParser.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Message/PacketParser/Handler/CircularBufferPacketParser.cs new file mode 100644 index 0000000..24c368b --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Message/PacketParser/Handler/CircularBufferPacketParser.cs @@ -0,0 +1,170 @@ +// using System.Runtime.CompilerServices; +// // ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract +// #pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. +// +// namespace Fantasy +// { +// // 这个对处理分包和粘包逻辑不完整、考虑现在没有任何地方使用了、就先不修改了。 +// // 后面用到了再修改、现在这个只是留做备份、万一以后用到了呢。 +// public abstract class CircularBufferPacketParser : APacketParser +// { +// protected uint RpcId; +// protected long RouteId; +// protected uint ProtocolCode; +// protected int MessagePacketLength; +// protected bool IsUnPackHead = true; +// protected readonly byte[] MessageHead = new byte[Packet.InnerPacketHeadLength]; +// public abstract bool UnPack(CircularBuffer buffer, out APackInfo packInfo); +// } +// +// #if FANTASY_NET +// public sealed class InnerCircularBufferPacketParser : CircularBufferPacketParser, IInnerPacketParser +// { +// public override bool UnPack(CircularBuffer buffer, out APackInfo packInfo) +// { +// packInfo = null; +// +// // 在对象没有被释放的情况下循环解析数据 +// while (!IsDisposed) +// { +// if (IsUnPackHead) +// { +// // 如果缓冲区中的数据长度小于内部消息头的长度,无法解析 +// if (buffer.Length < Packet.InnerPacketHeadLength) +// { +// return false; +// } +// +// // 从缓冲区中读取内部消息头的数据 +// _ = buffer.Read(MessageHead, 0, Packet.InnerPacketHeadLength); +// MessagePacketLength = BitConverter.ToInt32(MessageHead, 0); +// +// // 检查消息体长度是否超出限制 +// if (MessagePacketLength > Packet.PacketBodyMaxLength) +// { +// throw new ScanException( +// $"The received information exceeds the maximum limit = {MessagePacketLength}"); +// } +// +// // 解析协议编号、RPC ID 和 Route ID +// ProtocolCode = BitConverter.ToUInt32(MessageHead, Packet.PacketLength); +// RpcId = BitConverter.ToUInt32(MessageHead, Packet.InnerPacketRpcIdLocation); +// RouteId = BitConverter.ToInt64(MessageHead, Packet.InnerPacketRouteRouteIdLocation); +// IsUnPackHead = false; +// } +// +// try +// { +// // 如果缓冲区中的数据长度小于消息体的长度,无法解析 +// if (MessagePacketLength < 0 || buffer.Length < MessagePacketLength) +// { +// return false; +// } +// +// IsUnPackHead = true; +// packInfo = InnerPackInfo.Create(Network); +// var memoryStream = packInfo.RentMemoryStream(MessagePacketLength); +// memoryStream.SetLength(MessagePacketLength); +// buffer.Read(memoryStream, MessagePacketLength); +// packInfo.RpcId = RpcId; +// packInfo.RouteId = RouteId; +// packInfo.ProtocolCode = ProtocolCode; +// packInfo.MessagePacketLength = MessagePacketLength; +// return true; +// } +// catch (Exception e) +// { +// // 在发生异常时,释放 packInfo 并记录日志 +// packInfo?.Dispose(); +// Log.Error(e); +// return false; +// } +// } +// +// return false; +// } +// +// public override MemoryStream Pack(ref uint rpcId, ref long routeTypeOpCode, ref long routeId, +// MemoryStream memoryStream, object message) +// { +// return memoryStream == null +// ? Pack(ref rpcId, ref routeId, message) +// : Pack(ref rpcId, ref routeId, memoryStream); +// } +// +// [MethodImpl(MethodImplOptions.AggressiveInlining)] +// private unsafe MemoryStream Pack(ref uint rpcId, ref long routeId, MemoryStream memoryStream) +// { +// var buffer = memoryStream.GetBuffer(); +// +// fixed (byte* bufferPtr = buffer) +// { +// var rpcIdPtr = bufferPtr + Packet.InnerPacketRpcIdLocation; +// var routeIdPtr = bufferPtr + Packet.InnerPacketRouteRouteIdLocation; +// *(uint*)rpcIdPtr = rpcId; +// *(long*)routeIdPtr = routeId; +// } +// +// memoryStream.Seek(0, SeekOrigin.Begin); +// return memoryStream; +// } +// +// [MethodImpl(MethodImplOptions.AggressiveInlining)] +// private unsafe MemoryStream Pack(ref uint rpcId, ref long routeId, object message) +// { +// var memoryStream = Network.RentMemoryStream(); +// memoryStream.Seek(Packet.InnerPacketHeadLength, SeekOrigin.Begin); +// +// switch (message) +// { +// case IBsonMessage: +// { +// MongoHelper.SerializeTo(message, memoryStream); +// break; +// } +// default: +// { +// ProtoBuffHelper.ToStream(message, memoryStream); +// break; +// } +// } +// +// var opCode = Scene.MessageDispatcherComponent.GetOpCode(message.GetType()); +// var packetBodyCount = (int)(memoryStream.Position - Packet.InnerPacketHeadLength); +// +// if (packetBodyCount == 0) +// { +// // protoBuf做了一个优化、就是当序列化的对象里的属性和字段都为默认值的时候就不会序列化任何东西。 +// // 为了TCP的分包和粘包、需要判定下是当前包数据不完整还是本应该如此、所以用-1代表。 +// packetBodyCount = -1; +// } +// +// // 检查消息体长度是否超出限制 +// if (packetBodyCount > Packet.PacketBodyMaxLength) +// { +// throw new Exception($"Message content exceeds {Packet.PacketBodyMaxLength} bytes"); +// } +// +// var buffer = memoryStream.GetBuffer(); +// +// fixed (byte* bufferPtr = buffer) +// { +// var opCodePtr = bufferPtr + Packet.PacketLength; +// var rpcIdPtr = bufferPtr + Packet.InnerPacketRpcIdLocation; +// var routeIdPtr = bufferPtr + Packet.InnerPacketRouteRouteIdLocation; +// *(int*)bufferPtr = packetBodyCount; +// *(uint*)opCodePtr = opCode; +// *(uint*)rpcIdPtr = rpcId; +// *(long*)routeIdPtr = routeId; +// } +// +// memoryStream.Seek(0, SeekOrigin.Begin); +// return memoryStream; +// } +// } +// #endif +// } +// +// +// +// diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Message/PacketParser/Handler/OuterBufferPacketParserHelper.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Message/PacketParser/Handler/OuterBufferPacketParserHelper.cs new file mode 100644 index 0000000..a6e66ca --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Message/PacketParser/Handler/OuterBufferPacketParserHelper.cs @@ -0,0 +1,72 @@ +#if FANTASY_NET +using System.Runtime.CompilerServices; +using Fantasy.Helper; +using Fantasy.Network; +using Fantasy.Network.Interface; +using Fantasy.Serialize; + +namespace Fantasy.PacketParser +{ + /// + /// 打包Outer消息的帮助类 + /// + public static class OuterBufferPacketParserHelper + { + /// + /// 打包一个网络消息 + /// + /// scene + /// 如果是RPC消息需要传递一个rpcId + /// 打包的网络消息 + /// 序列化后流的长度 + /// 打包完成会返回一个MemoryStreamBuffer + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe MemoryStreamBuffer Pack(Scene scene, uint rpcId, IMessage message, out int memoryStreamLength) + { + memoryStreamLength = 0; + var messageType = message.GetType(); + var memoryStream = new MemoryStreamBuffer(); + memoryStream.MemoryStreamBufferSource = MemoryStreamBufferSource.Pack; + OpCodeIdStruct opCodeIdStruct = message.OpCode(); + memoryStream.Seek(Packet.OuterPacketHeadLength, SeekOrigin.Begin); + + if (SerializerManager.TryGetSerializer(opCodeIdStruct.OpCodeProtocolType, out var serializer)) + { + serializer.Serialize(messageType, message, memoryStream); + memoryStreamLength = (int)memoryStream.Position; + } + else + { + Log.Error($"type:{messageType} Does not support processing protocol"); + } + + var opCode = scene.MessageDispatcherComponent.GetOpCode(messageType); + var packetBodyCount = memoryStreamLength - Packet.OuterPacketHeadLength; + + if (packetBodyCount == 0) + { + // protoBuf做了一个优化、就是当序列化的对象里的属性和字段都为默认值的时候就不会序列化任何东西。 + // 为了TCP的分包和粘包、需要判定下是当前包数据不完整还是本应该如此、所以用-1代表。 + packetBodyCount = -1; + } + + if (packetBodyCount > Packet.PacketBodyMaxLength) + { + // 检查消息体长度是否超出限制 + throw new Exception($"Message content exceeds {Packet.PacketBodyMaxLength} bytes"); + } + + fixed (byte* bufferPtr = memoryStream.GetBuffer()) + { + *(int*)bufferPtr = packetBodyCount; + *(uint*)(bufferPtr + Packet.PacketLength) = opCode; + *(uint*)(bufferPtr + Packet.OuterPacketRpcIdLocation) = rpcId; + } + + return memoryStream; + } + } +} + +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Message/PacketParser/Handler/ReadOnlyMemoryPacketParser.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Message/PacketParser/Handler/ReadOnlyMemoryPacketParser.cs new file mode 100644 index 0000000..758ec17 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Message/PacketParser/Handler/ReadOnlyMemoryPacketParser.cs @@ -0,0 +1,357 @@ +using System; +using System.IO; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Fantasy.Helper; +using Fantasy.Network; +using Fantasy.Network.Interface; +using Fantasy.PacketParser.Interface; +using Fantasy.Serialize; + +// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. +#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. + +namespace Fantasy.PacketParser +{ + internal abstract class ReadOnlyMemoryPacketParser : APacketParser + { + /// + /// 一个网络消息包 + /// + protected APackInfo PackInfo; + + protected int Offset; + protected int MessageHeadOffset; + protected int MessageBodyOffset; + protected int MessagePacketLength; + protected bool IsUnPackHead = true; + protected readonly byte[] MessageHead = new byte[20]; + public ReadOnlyMemoryPacketParser() { } + + public abstract bool UnPack(ref ReadOnlyMemory buffer, out APackInfo packInfo); + + public override void Dispose() + { + Offset = 0; + MessageHeadOffset = 0; + MessageBodyOffset = 0; + MessagePacketLength = 0; + IsUnPackHead = true; + PackInfo = null; + Array.Clear(MessageHead, 0, 20); + base.Dispose(); + } + } + +#if FANTASY_NET + internal sealed class InnerReadOnlyMemoryPacketParser : ReadOnlyMemoryPacketParser + { + public override unsafe bool UnPack(ref ReadOnlyMemory buffer, out APackInfo packInfo) + { + packInfo = null; + var readOnlySpan = buffer.Span; + var bufferLength = buffer.Length - Offset; + + if (bufferLength == 0) + { + // 没有剩余的数据需要处理、等待下一个包再处理。 + Offset = 0; + return false; + } + + if (IsUnPackHead) + { + fixed (byte* bufferPtr = readOnlySpan) + fixed (byte* messagePtr = MessageHead) + { + // 在当前buffer中拿到包头的数据 + var innerPacketHeadLength = Packet.InnerPacketHeadLength - MessageHeadOffset; + var copyLength = Math.Min(bufferLength, innerPacketHeadLength); + Buffer.MemoryCopy(bufferPtr + Offset, messagePtr + MessageHeadOffset, innerPacketHeadLength, copyLength); + Offset += copyLength; + MessageHeadOffset += copyLength; + // 检查是否有完整包头 + if (MessageHeadOffset == Packet.InnerPacketHeadLength) + { + // 通过指针直接读取协议编号、messagePacketLength protocolCode rpcId routeId + MessagePacketLength = *(int*)messagePtr; + // 检查消息体长度是否超出限制 + if (MessagePacketLength > Packet.PacketBodyMaxLength) + { + throw new ScanException($"The received information exceeds the maximum limit = {MessagePacketLength}"); + } + + PackInfo = InnerPackInfo.Create(Network); + var memoryStream = PackInfo.RentMemoryStream(MemoryStreamBufferSource.UnPack, Packet.InnerPacketHeadLength + MessagePacketLength); + PackInfo.RpcId = *(uint*)(messagePtr + Packet.InnerPacketRpcIdLocation); + PackInfo.ProtocolCode = *(uint*)(messagePtr + Packet.PacketLength); + PackInfo.RouteId = *(long*)(messagePtr + Packet.InnerPacketRouteRouteIdLocation); + memoryStream.Write(MessageHead); + IsUnPackHead = false; + bufferLength -= copyLength; + MessageHeadOffset = 0; + } + else + { + Offset = 0; + return false; + } + } + } + + if (MessagePacketLength == -1) + { + // protoBuf做了一个优化、就是当序列化的对象里的属性和字段都为默认值的时候就不会序列化任何东西。 + // 为了TCP的分包和粘包、需要判定下是当前包数据不完整还是本应该如此、所以用-1代表。 + packInfo = PackInfo; + PackInfo = null; + IsUnPackHead = true; + return true; + } + + if (bufferLength == 0) + { + // 没有剩余的数据需要处理、等待下一个包再处理。 + Offset = 0; + return false; + } + + // 处理包消息体 + var innerPacketBodyLength = MessagePacketLength - MessageBodyOffset; + var copyBodyLength = Math.Min(bufferLength, innerPacketBodyLength); + // 写入数据到消息体中 + PackInfo.MemoryStream.Write(readOnlySpan.Slice(Offset, copyBodyLength)); + Offset += copyBodyLength; + MessageBodyOffset += copyBodyLength; + // 检查是否是完整的消息体 + if (MessageBodyOffset == MessagePacketLength) + { + packInfo = PackInfo; + PackInfo = null; + IsUnPackHead = true; + MessageBodyOffset = 0; + return true; + } + Offset = 0; + return false; + } + + public override MemoryStreamBuffer Pack(ref uint rpcId, ref long routeId, MemoryStreamBuffer memoryStream, IMessage message) + { + return memoryStream == null ? Pack(ref rpcId, ref routeId, message) : Pack(ref rpcId, ref routeId, memoryStream); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private unsafe MemoryStreamBuffer Pack(ref uint rpcId, ref long routeId, MemoryStreamBuffer memoryStream) + { + fixed (byte* bufferPtr = memoryStream.GetBuffer()) + { + *(uint*)(bufferPtr + Packet.InnerPacketRpcIdLocation) = rpcId; + *(long*)(bufferPtr + Packet.InnerPacketRouteRouteIdLocation) = routeId; + } + + return memoryStream; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private unsafe MemoryStreamBuffer Pack(ref uint rpcId, ref long routeId, IMessage message) + { + var memoryStreamLength = 0; + var messageType = message.GetType(); + var memoryStream = Network.MemoryStreamBufferPool.RentMemoryStream(MemoryStreamBufferSource.Pack); + OpCodeIdStruct opCodeIdStruct = message.OpCode(); + memoryStream.Seek(Packet.InnerPacketHeadLength, SeekOrigin.Begin); + + if (SerializerManager.TryGetSerializer(opCodeIdStruct.OpCodeProtocolType, out var serializer)) + { + serializer.Serialize(messageType, message, memoryStream); + memoryStreamLength = (int)memoryStream.Position; + } + else + { + Log.Error($"type:{messageType} Does not support processing protocol"); + } + + var opCode = Scene.MessageDispatcherComponent.GetOpCode(messageType); + var packetBodyCount = memoryStreamLength - Packet.InnerPacketHeadLength; + + if (packetBodyCount == 0) + { + // protoBuf做了一个优化、就是当序列化的对象里的属性和字段都为默认值的时候就不会序列化任何东西。 + // 为了TCP的分包和粘包、需要判定下是当前包数据不完整还是本应该如此、所以用-1代表。 + // 其实可以不用设置-1、解包的时候判断如果是0也可以、但我仔细想了下,还是用-1代表更加清晰。 + packetBodyCount = -1; + } + + if (packetBodyCount > Packet.PacketBodyMaxLength) + { + // 检查消息体长度是否超出限制 + throw new Exception($"Message content exceeds {Packet.PacketBodyMaxLength} bytes"); + } + + fixed (byte* bufferPtr = memoryStream.GetBuffer()) + { + *(int*)bufferPtr = packetBodyCount; + *(uint*)(bufferPtr + Packet.PacketLength) = opCode; + *(uint*)(bufferPtr + Packet.InnerPacketRpcIdLocation) = rpcId; + *(long*)(bufferPtr + Packet.InnerPacketRouteRouteIdLocation) = routeId; + } + + return memoryStream; + } + } +#endif + internal sealed class OuterReadOnlyMemoryPacketParser : ReadOnlyMemoryPacketParser + { + public override unsafe bool UnPack(ref ReadOnlyMemory buffer, out APackInfo packInfo) + { + packInfo = null; + var readOnlySpan = buffer.Span; + var bufferLength = buffer.Length - Offset; + + if (bufferLength == 0) + { + // 没有剩余的数据需要处理、等待下一个包再处理。 + Offset = 0; + return false; + } + + if (IsUnPackHead) + { + fixed (byte* bufferPtr = readOnlySpan) + fixed (byte* messagePtr = MessageHead) + { + // 在当前buffer中拿到包头的数据 + var outerPacketHeadLength = Packet.OuterPacketHeadLength - MessageHeadOffset; + var copyLength = Math.Min(bufferLength, outerPacketHeadLength); + Buffer.MemoryCopy(bufferPtr + Offset, messagePtr + MessageHeadOffset, outerPacketHeadLength, copyLength); + Offset += copyLength; + MessageHeadOffset += copyLength; + // 检查是否有完整包头 + if (MessageHeadOffset == Packet.OuterPacketHeadLength) + { + // 通过指针直接读取协议编号、messagePacketLength protocolCode rpcId routeId + MessagePacketLength = *(int*)messagePtr; + // 检查消息体长度是否超出限制 + if (MessagePacketLength > Packet.PacketBodyMaxLength) + { + throw new ScanException($"The received information exceeds the maximum limit = {MessagePacketLength}"); + } + + PackInfo = OuterPackInfo.Create(Network); + PackInfo.ProtocolCode = *(uint*)(messagePtr + Packet.PacketLength); + PackInfo.RpcId = *(uint*)(messagePtr + Packet.OuterPacketRpcIdLocation); + var memoryStream = PackInfo.RentMemoryStream(MemoryStreamBufferSource.UnPack, Packet.OuterPacketHeadLength + MessagePacketLength); + memoryStream.Write(MessageHead); + IsUnPackHead = false; + bufferLength -= copyLength; + MessageHeadOffset = 0; + } + else + { + Offset = 0; + return false; + } + } + } + + if (MessagePacketLength == -1) + { + // protoBuf做了一个优化、就是当序列化的对象里的属性和字段都为默认值的时候就不会序列化任何东西。 + // 为了TCP的分包和粘包、需要判定下是当前包数据不完整还是本应该如此、所以用-1代表。 + packInfo = PackInfo; + PackInfo = null; + IsUnPackHead = true; + return true; + } + + if (bufferLength == 0) + { + // 没有剩余的数据需要处理、等待下一个包再处理。 + Offset = 0; + return false; + } + // 处理包消息体 + var outerPacketBodyLength = MessagePacketLength - MessageBodyOffset; + var copyBodyLength = Math.Min(bufferLength, outerPacketBodyLength); + // 写入数据到消息体中 + PackInfo.MemoryStream.Write(readOnlySpan.Slice(Offset, copyBodyLength)); + Offset += copyBodyLength; + MessageBodyOffset += copyBodyLength; + // 检查是否是完整的消息体 + if (MessageBodyOffset == MessagePacketLength) + { + packInfo = PackInfo; + PackInfo = null; + IsUnPackHead = true; + MessageBodyOffset = 0; + return true; + } + + Offset = 0; + return false; + } + + public override MemoryStreamBuffer Pack(ref uint rpcId, ref long routeId, MemoryStreamBuffer memoryStream, IMessage message) + { + return memoryStream == null ? Pack(ref rpcId, message) : Pack(ref rpcId, memoryStream); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private unsafe MemoryStreamBuffer Pack(ref uint rpcId, MemoryStreamBuffer memoryStream) + { + fixed (byte* bufferPtr = memoryStream.GetBuffer()) + { + *(uint*)(bufferPtr + Packet.OuterPacketRpcIdLocation) = rpcId; + } + + return memoryStream; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private unsafe MemoryStreamBuffer Pack(ref uint rpcId, IMessage message) + { + var memoryStreamLength = 0; + var messageType = message.GetType(); + var memoryStream = Network.MemoryStreamBufferPool.RentMemoryStream(MemoryStreamBufferSource.Pack); + OpCodeIdStruct opCodeIdStruct = message.OpCode(); + memoryStream.Seek(Packet.OuterPacketHeadLength, SeekOrigin.Begin); + + if (SerializerManager.TryGetSerializer(opCodeIdStruct.OpCodeProtocolType, out var serializer)) + { + serializer.Serialize(messageType, message, memoryStream); + memoryStreamLength = (int)memoryStream.Position; + } + else + { + Log.Error($"type:{messageType} Does not support processing protocol"); + } + + var opCode = Scene.MessageDispatcherComponent.GetOpCode(messageType); + var packetBodyCount = memoryStreamLength - Packet.OuterPacketHeadLength; + + if (packetBodyCount == 0) + { + // protoBuf做了一个优化、就是当序列化的对象里的属性和字段都为默认值的时候就不会序列化任何东西。 + // 为了TCP的分包和粘包、需要判定下是当前包数据不完整还是本应该如此、所以用-1代表。 + packetBodyCount = -1; + } + + if (packetBodyCount > Packet.PacketBodyMaxLength) + { + // 检查消息体长度是否超出限制 + throw new Exception($"Message content exceeds {Packet.PacketBodyMaxLength} bytes"); + } + + fixed (byte* bufferPtr = memoryStream.GetBuffer()) + { + *(int*)bufferPtr = packetBodyCount; + *(uint*)(bufferPtr + Packet.PacketLength) = opCode; + *(uint*)(bufferPtr + Packet.OuterPacketRpcIdLocation) = rpcId; + } + + return memoryStream; + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Message/PacketParser/Interface/APackInfo.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Message/PacketParser/Interface/APackInfo.cs new file mode 100644 index 0000000..1ea6d6b --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Message/PacketParser/Interface/APackInfo.cs @@ -0,0 +1,61 @@ +using System; +using System.IO; +using Fantasy.Network; +using Fantasy.Network.Interface; +using Fantasy.Serialize; + +// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. +#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + +namespace Fantasy.PacketParser.Interface +{ + public abstract class APackInfo : IDisposable + { + internal ANetwork Network; + + public uint RpcId; + public long RouteId; + public long PackInfoId; + public bool IsDisposed; + private uint _protocolCode; + + public uint ProtocolCode + { + get => _protocolCode; + set + { + _protocolCode = value; + OpCodeIdStruct = value; + } + } + public OpCodeIdStruct OpCodeIdStruct { get; private set; } + public MemoryStreamBuffer MemoryStream { get; protected set; } + public abstract object Deserialize(Type messageType); + public abstract MemoryStreamBuffer RentMemoryStream(MemoryStreamBufferSource memoryStreamBufferSource, int size = 0); + public virtual void Dispose() + { + if (IsDisposed) + { + return; + } + + RpcId = 0; + RouteId = 0; + PackInfoId = 0; + ProtocolCode = 0; + _protocolCode = 0; + OpCodeIdStruct = default; + + if (MemoryStream != null) + { + Network.MemoryStreamBufferPool.ReturnMemoryStream(MemoryStream); + MemoryStream = null; + } + + IsDisposed = true; + Network = null; + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Message/PacketParser/Interface/APacketParser.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Message/PacketParser/Interface/APacketParser.cs new file mode 100644 index 0000000..579cf6f --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Message/PacketParser/Interface/APacketParser.cs @@ -0,0 +1,30 @@ +using System; +using System.Buffers; +using System.IO; +using Fantasy.Network.Interface; +using Fantasy.Serialize; + +#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + +namespace Fantasy.PacketParser.Interface +{ + /// + /// 抽象的包解析器基类,用于解析网络通信数据包。 + /// + public abstract class APacketParser : IDisposable + { + internal Scene Scene; + internal ANetwork Network; + internal MessageDispatcherComponent MessageDispatcherComponent; + protected bool IsDisposed { get; private set; } + public abstract MemoryStreamBuffer Pack(ref uint rpcId, ref long routeId, MemoryStreamBuffer memoryStream, IMessage message); + public virtual void Dispose() + { + IsDisposed = true; + Scene = null; + MessageDispatcherComponent = null; + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Message/PacketParser/OpCode.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Message/PacketParser/OpCode.cs new file mode 100644 index 0000000..27c5dbb --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Message/PacketParser/OpCode.cs @@ -0,0 +1,125 @@ +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +namespace Fantasy.Network +{ + public struct OpCodeIdStruct + { + // OpCodeIdStruct:5 + 4 + 23 = 32 + // +-------------------------+-------------------------------------------+-----------------------------+ + // | protocol(5) 最多31种类型 | OpCodeProtocolType(4) 最多15种不同的网络协议 | Index(23) 最多8388607个协议 | + // +-------------------------+-------------------------------------------+-----------------------------+ + public uint OpCodeProtocolType { get; private set; } + public uint Protocol { get; private set; } + public uint Index { get; private set; } + + public OpCodeIdStruct(uint opCodeProtocolType, uint protocol, uint index) + { + OpCodeProtocolType = opCodeProtocolType; + Protocol = protocol; + Index = index; + } + + public static implicit operator uint(OpCodeIdStruct opCodeIdStruct) + { + var result = opCodeIdStruct.Index; + result |= opCodeIdStruct.OpCodeProtocolType << 23; + result |= opCodeIdStruct.Protocol << 27; + return result; + } + + public static implicit operator OpCodeIdStruct(uint opCodeId) + { + var opCodeIdStruct = new OpCodeIdStruct() + { + Index = opCodeId & 0x7FFFFF + }; + opCodeId >>= 23; + opCodeIdStruct.OpCodeProtocolType = opCodeId & 0xF; + opCodeId >>= 4; + opCodeIdStruct.Protocol = opCodeId & 0x1F; + return opCodeIdStruct; + } + } + + public static class OpCodeProtocolType + { + public const uint Bson = 1; + public const uint ProtoBuf = 0; + } + + public static class OpCodeType + { + public const uint OuterMessage = 1; + public const uint OuterRequest = 2; + public const uint OuterResponse = 3; + + public const uint InnerMessage = 4; + public const uint InnerRequest = 5; + public const uint InnerResponse = 6; + + public const uint InnerRouteMessage = 7; + public const uint InnerRouteRequest = 8; + public const uint InnerRouteResponse = 9; + + public const uint OuterAddressableMessage = 10; + public const uint OuterAddressableRequest = 11; + public const uint OuterAddressableResponse = 12; + + public const uint InnerAddressableMessage = 13; + public const uint InnerAddressableRequest = 14; + public const uint InnerAddressableResponse = 15; + + public const uint OuterCustomRouteMessage = 16; + public const uint OuterCustomRouteRequest = 17; + public const uint OuterCustomRouteResponse = 18; + + public const uint OuterRoamingMessage = 19; + public const uint OuterRoamingRequest = 20; + public const uint OuterRoamingResponse = 21; + + public const uint InnerRoamingMessage = 22; + public const uint InnerRoamingRequest = 23; + public const uint InnerRoamingResponse = 24; + + public const uint OuterPingRequest = 30; + public const uint OuterPingResponse = 31; + } + + public static class OpCode + { + public static readonly uint BenchmarkMessage = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.OuterMessage, 8388607); + public static readonly uint BenchmarkRequest = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.OuterRequest, 8388607); + public static readonly uint BenchmarkResponse = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.OuterResponse, 8388607); + public static readonly uint PingRequest = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.OuterPingRequest, 1); + public static readonly uint PingResponse = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.OuterPingResponse, 1); + public static readonly uint DefaultResponse = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerResponse, 1); + public static readonly uint DefaultRouteResponse = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteResponse, 7); + public static readonly uint AddressableAddRequest = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteRequest, 1); + public static readonly uint AddressableAddResponse = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteResponse, 1); + public static readonly uint AddressableGetRequest = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteRequest, 2); + public static readonly uint AddressableGetResponse = Create(OpCodeProtocolType.ProtoBuf,OpCodeType.InnerRouteResponse,2); + public static readonly uint AddressableRemoveRequest = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteRequest, 3); + public static readonly uint AddressableRemoveResponse = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteResponse, 3); + public static readonly uint AddressableLockRequest = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteRequest, 4); + public static readonly uint AddressableLockResponse = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteResponse, 4); + public static readonly uint AddressableUnLockRequest = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteRequest, 5); + public static readonly uint AddressableUnLockResponse = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteResponse, 5); + public static readonly uint LinkRoamingRequest = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteRequest, 6); + public static readonly uint LinkRoamingResponse = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteResponse, 6); + public static readonly uint UnLinkRoamingRequest = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteRequest, 8); + public static readonly uint UnLinkRoamingResponse = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteResponse, 8); + public static readonly uint LockTerminusIdRequest = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteRequest, 9); + public static readonly uint LockTerminusIdResponse = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteResponse, 9); + public static readonly uint UnLockTerminusIdRequest = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteRequest, 10); + public static readonly uint UnLockTerminusIdResponse = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteResponse, 10); + public static readonly uint GetTerminusIdRequest = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteRequest, 11); + public static readonly uint GetTerminusIdResponse = Create(OpCodeProtocolType.ProtoBuf, OpCodeType.InnerRouteResponse, 11); + + public static readonly uint TransferTerminusRequest = Create(OpCodeProtocolType.Bson, OpCodeType.InnerRouteRequest, 1); + public static readonly uint TransferTerminusResponse = Create(OpCodeProtocolType.Bson, OpCodeType.InnerRouteResponse, 1); + + public static uint Create(uint opCodeProtocolType, uint protocol, uint index) + { + return new OpCodeIdStruct(opCodeProtocolType, protocol, index); + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Message/PacketParser/Pack/InnerPackInfo.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Message/PacketParser/Pack/InnerPackInfo.cs new file mode 100644 index 0000000..e9870e7 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Message/PacketParser/Pack/InnerPackInfo.cs @@ -0,0 +1,79 @@ +// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract + +using Fantasy.Network; +using Fantasy.Network.Interface; +using Fantasy.PacketParser.Interface; +using Fantasy.Pool; +using Fantasy.Serialize; + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type. +#pragma warning disable CS8603 // Possible null reference return. +#if FANTASY_NET +namespace Fantasy.PacketParser +{ + public sealed class InnerPackInfo : APackInfo + { + private readonly Dictionary> _createInstances = new Dictionary>(); + + public override void Dispose() + { + if (IsDisposed) + { + return; + } + + var network = Network; + base.Dispose(); + network.ReturnInnerPackInfo(this); + } + + public static InnerPackInfo Create(ANetwork network) + { + var innerPackInfo = network.RentInnerPackInfo(); + innerPackInfo.Network = network; + innerPackInfo.IsDisposed = false; + return innerPackInfo; + } + + public override MemoryStreamBuffer RentMemoryStream(MemoryStreamBufferSource memoryStreamBufferSource, int size = 0) + { + return MemoryStream ??= Network.MemoryStreamBufferPool.RentMemoryStream(memoryStreamBufferSource, size); + } + + public override object Deserialize(Type messageType) + { + if (MemoryStream == null) + { + Log.Debug("Deserialize MemoryStream is null"); + return null; + } + + MemoryStream.Seek(Packet.InnerPacketHeadLength, SeekOrigin.Begin); + + if (MemoryStream.Length == 0) + { + if (_createInstances.TryGetValue(messageType, out var createInstance)) + { + return createInstance(); + } + + createInstance = CreateInstance.CreateObject(messageType); + _createInstances.Add(messageType, createInstance); + return createInstance(); + } + + if (SerializerManager.TryGetSerializer(OpCodeIdStruct.OpCodeProtocolType, out var serializer)) + { + var obj = serializer.Deserialize(messageType, MemoryStream); + MemoryStream.Seek(0, SeekOrigin.Begin); + return obj; + } + + MemoryStream.Seek(0, SeekOrigin.Begin); + Log.Error($"protocolCode:{ProtocolCode} Does not support processing protocol"); + return null; + } + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Message/PacketParser/Pack/OuterPackInfo.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Message/PacketParser/Pack/OuterPackInfo.cs new file mode 100644 index 0000000..8ce08b7 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Message/PacketParser/Pack/OuterPackInfo.cs @@ -0,0 +1,73 @@ +// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract +using System; +using System.IO; +using Fantasy.Network; +using Fantasy.Network.Interface; +using Fantasy.PacketParser.Interface; +using Fantasy.Serialize; + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +#pragma warning disable CS8603 // Possible null reference return. +#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type. +#pragma warning disable CS8602 // Dereference of a possibly null reference. +namespace Fantasy.PacketParser +{ + public sealed class OuterPackInfo : APackInfo + { + public override void Dispose() + { + if (IsDisposed) + { + return; + } + var network = Network; + base.Dispose(); + network.ReturnOuterPackInfo(this); + } + + public static OuterPackInfo Create(ANetwork network) + { + var outerPackInfo = network.RentOuterPackInfo(); + outerPackInfo.Network = network; + outerPackInfo.IsDisposed = false; + return outerPackInfo; + } + + public override MemoryStreamBuffer RentMemoryStream(MemoryStreamBufferSource memoryStreamBufferSource, int size = 0) + { + if (MemoryStream == null) + { + MemoryStream = Network.MemoryStreamBufferPool.RentMemoryStream(memoryStreamBufferSource, size); + } + + return MemoryStream; + } + + /// + /// 将消息数据从内存反序列化为指定的消息类型实例。 + /// + /// 目标消息类型。 + /// 反序列化后的消息类型实例。 + public override object Deserialize(Type messageType) + { + if (MemoryStream == null) + { + Log.Debug("Deserialize MemoryStream is null"); + return null; + } + + MemoryStream.Seek(Packet.OuterPacketHeadLength, SeekOrigin.Begin); + + if (SerializerManager.TryGetSerializer(OpCodeIdStruct.OpCodeProtocolType, out var serializer)) + { + var obj = serializer.Deserialize(messageType, MemoryStream); + MemoryStream.Seek(0, SeekOrigin.Begin); + return obj; + } + + MemoryStream.Seek(0, SeekOrigin.Begin); + Log.Error($"protocolCode:{ProtocolCode} Does not support processing protocol"); + return null; + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Message/PacketParser/Pack/ProcessPackInfo.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Message/PacketParser/Pack/ProcessPackInfo.cs new file mode 100644 index 0000000..f010858 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Message/PacketParser/Pack/ProcessPackInfo.cs @@ -0,0 +1,160 @@ +#if FANTASY_NET +using System.Collections.Concurrent; +using Fantasy.Network; +using Fantasy.Network.Interface; +using Fantasy.PacketParser.Interface; +using Fantasy.Pool; +using Fantasy.Serialize; + +// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. +#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type. +#pragma warning disable CS8603 // Possible null reference return. +namespace Fantasy.PacketParser +{ + public sealed class ProcessPackInfo : APackInfo + { + private int _disposeCount; + public Type MessageType { get; private set; } + private static readonly ConcurrentQueue Caches = new ConcurrentQueue(); + private readonly ConcurrentDictionary> _createInstances = new ConcurrentDictionary>(); + + public override void Dispose() + { + if (--_disposeCount > 0 || IsDisposed) + { + return; + } + + _disposeCount = 0; + MessageType = null; + base.Dispose(); + + if (Caches.Count > 2000) + { + return; + } + + Caches.Enqueue(this); + } + + public static unsafe ProcessPackInfo Create(Scene scene, T message, int disposeCount, uint rpcId = 0, long routeId = 0) where T : IRouteMessage + { + if (!Caches.TryDequeue(out var packInfo)) + { + packInfo = new ProcessPackInfo(); + } + + var type = typeof(T); + var memoryStreamLength = 0; + packInfo._disposeCount = disposeCount; + packInfo.MessageType = type; + packInfo.IsDisposed = false; + var memoryStream = new MemoryStreamBuffer(); + memoryStream.MemoryStreamBufferSource = MemoryStreamBufferSource.Pack; + OpCodeIdStruct opCodeIdStruct = message.OpCode(); + memoryStream.Seek(Packet.InnerPacketHeadLength, SeekOrigin.Begin); + + if (SerializerManager.TryGetSerializer(opCodeIdStruct.OpCodeProtocolType, out var serializer)) + { + serializer.Serialize(type, message, memoryStream); + memoryStreamLength = (int)memoryStream.Position; + } + else + { + Log.Error($"type:{type} Does not support processing protocol"); + } + + var opCode = scene.MessageDispatcherComponent.GetOpCode(packInfo.MessageType); + var packetBodyCount = memoryStreamLength - Packet.InnerPacketHeadLength; + + if (packetBodyCount == 0) + { + // protoBuf做了一个优化、就是当序列化的对象里的属性和字段都为默认值的时候就不会序列化任何东西。 + // 为了TCP的分包和粘包、需要判定下是当前包数据不完整还是本应该如此、所以用-1代表。 + packetBodyCount = -1; + } + + if (packetBodyCount > Packet.PacketBodyMaxLength) + { + // 检查消息体长度是否超出限制 + throw new Exception($"Message content exceeds {Packet.PacketBodyMaxLength} bytes"); + } + + var buffer = memoryStream.GetBuffer(); + + fixed (byte* bufferPtr = buffer) + { + var opCodePtr = bufferPtr + Packet.PacketLength; + var rpcIdPtr = bufferPtr + Packet.InnerPacketRpcIdLocation; + var routeIdPtr = bufferPtr + Packet.InnerPacketRouteRouteIdLocation; + *(int*)bufferPtr = packetBodyCount; + *(uint*)opCodePtr = opCode; + *(uint*)rpcIdPtr = rpcId; + *(long*)routeIdPtr = routeId; + } + + memoryStream.Seek(0, SeekOrigin.Begin); + packInfo.MemoryStream = memoryStream; + return packInfo; + } + + public unsafe void Set(uint rpcId, long routeId) + { + var buffer = MemoryStream.GetBuffer(); + + fixed (byte* bufferPtr = buffer) + { + var rpcIdPtr = bufferPtr + Packet.InnerPacketRpcIdLocation; + var routeIdPtr = bufferPtr + Packet.InnerPacketRouteRouteIdLocation; + *(uint*)rpcIdPtr = rpcId; + *(long*)routeIdPtr = routeId; + } + + MemoryStream.Seek(0, SeekOrigin.Begin); + } + + public override MemoryStreamBuffer RentMemoryStream(MemoryStreamBufferSource memoryStreamBufferSource, int size = 0) + { + throw new NotImplementedException(); + } + + public override object Deserialize(Type messageType) + { + if (MemoryStream == null) + { + Log.Debug("Deserialize MemoryStream is null"); + return null; + } + + object obj = null; + MemoryStream.Seek(Packet.InnerPacketHeadLength, SeekOrigin.Begin); + + if (MemoryStream.Length == 0) + { + if (_createInstances.TryGetValue(messageType, out var createInstance)) + { + return createInstance(); + } + + createInstance = CreateInstance.CreateObject(messageType); + _createInstances.TryAdd(messageType, createInstance); + return createInstance(); + } + + if (SerializerManager.TryGetSerializer(OpCodeIdStruct.OpCodeProtocolType, out var serializer)) + { + obj = serializer.Deserialize(messageType, MemoryStream); + MemoryStream.Seek(0, SeekOrigin.Begin); + return obj; + } + + MemoryStream.Seek(0, SeekOrigin.Begin); + Log.Error($"protocolCode:{ProtocolCode} Does not support processing protocol"); + return null; + } + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Message/PacketParser/Packet.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Message/PacketParser/Packet.cs new file mode 100644 index 0000000..7198af7 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Message/PacketParser/Packet.cs @@ -0,0 +1,49 @@ +namespace Fantasy.PacketParser +{ + /// + /// 提供关于消息包的常量定义。 + /// + public struct Packet + { + /// + /// 消息体最大长度 + /// + public const int PacketBodyMaxLength = ushort.MaxValue * 16; + /// + /// 消息体长度在消息头占用的长度 + /// + public const int PacketLength = sizeof(int); + /// + /// 协议编号在消息头占用的长度 + /// + public const int ProtocolCodeLength = sizeof(uint); + /// + /// RouteId长度 + /// + public const int PacketRouteIdLength = sizeof(long); + /// + /// RpcId在消息头占用的长度 + /// + public const int RpcIdLength = sizeof(uint); + /// + /// OuterRPCId所在的位置 + /// + public const int OuterPacketRpcIdLocation = PacketLength + ProtocolCodeLength; + /// + /// InnerRPCId所在的位置 + /// + public const int InnerPacketRpcIdLocation = PacketLength + ProtocolCodeLength; + /// + /// RouteId所在的位置 + /// + public const int InnerPacketRouteRouteIdLocation = PacketLength + ProtocolCodeLength + RpcIdLength; + /// + /// 外网消息头长度(消息体长度在消息头占用的长度 + 协议编号在消息头占用的长度 + RPCId长度 + RouteId长度) + /// + public const int OuterPacketHeadLength = PacketLength + ProtocolCodeLength + RpcIdLength + PacketRouteIdLength; + /// + /// 内网消息头长度(消息体长度在消息头占用的长度 + 协议编号在消息头占用的长度 + RPCId长度 + RouteId长度) + /// + public const int InnerPacketHeadLength = PacketLength + ProtocolCodeLength + RpcIdLength + PacketRouteIdLength; + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Message/PacketParser/PacketParserFactory.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Message/PacketParser/PacketParserFactory.cs new file mode 100644 index 0000000..679f95d --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Message/PacketParser/PacketParserFactory.cs @@ -0,0 +1,173 @@ +using System; +using Fantasy.Network; +using Fantasy.Network.Interface; +using Fantasy.PacketParser.Interface; + +// ReSharper disable PossibleNullReferenceException +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +#pragma warning disable CS8602 // Dereference of a possibly null reference. +#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type. +#pragma warning disable CS8603 // Possible null reference return. +namespace Fantasy.PacketParser +{ + internal static class PacketParserFactory + { +#if FANTASY_NET + internal static ReadOnlyMemoryPacketParser CreateServerReadOnlyMemoryPacket(ANetwork network) + { + ReadOnlyMemoryPacketParser readOnlyMemoryPacketParser = null; + + switch (network.NetworkTarget) + { + case NetworkTarget.Inner: + { + readOnlyMemoryPacketParser = new InnerReadOnlyMemoryPacketParser(); + break; + } + case NetworkTarget.Outer: + { + readOnlyMemoryPacketParser = new OuterReadOnlyMemoryPacketParser(); + break; + } + } + + readOnlyMemoryPacketParser.Scene = network.Scene; + readOnlyMemoryPacketParser.Network = network; + readOnlyMemoryPacketParser.MessageDispatcherComponent = network.Scene.MessageDispatcherComponent; + return readOnlyMemoryPacketParser; + } + + public static BufferPacketParser CreateServerBufferPacket(ANetwork network) + { + BufferPacketParser bufferPacketParser = null; + + switch (network.NetworkTarget) + { + case NetworkTarget.Inner: + { + bufferPacketParser = new InnerBufferPacketParser(); + break; + } + case NetworkTarget.Outer: + { + bufferPacketParser = new OuterBufferPacketParser(); + break; + } + } + + bufferPacketParser.Scene = network.Scene; + bufferPacketParser.Network = network; + bufferPacketParser.MessageDispatcherComponent = network.Scene.MessageDispatcherComponent; + return bufferPacketParser; + } +#endif + internal static ReadOnlyMemoryPacketParser CreateClientReadOnlyMemoryPacket(ANetwork network) + { + ReadOnlyMemoryPacketParser readOnlyMemoryPacketParser = null; + + switch (network.NetworkTarget) + { +#if FANTASY_NET + case NetworkTarget.Inner: + { + readOnlyMemoryPacketParser = new InnerReadOnlyMemoryPacketParser(); + break; + } +#endif + case NetworkTarget.Outer: + { + readOnlyMemoryPacketParser = new OuterReadOnlyMemoryPacketParser(); + break; + } + } + + readOnlyMemoryPacketParser.Scene = network.Scene; + readOnlyMemoryPacketParser.Network = network; + readOnlyMemoryPacketParser.MessageDispatcherComponent = network.Scene.MessageDispatcherComponent; + return readOnlyMemoryPacketParser; + } + +#if !FANTASY_WEBGL + public static BufferPacketParser CreateClientBufferPacket(ANetwork network) + { + BufferPacketParser bufferPacketParser = null; + + switch (network.NetworkTarget) + { +#if FANTASY_NET + case NetworkTarget.Inner: + { + bufferPacketParser = new InnerBufferPacketParser(); + break; + } +#endif + case NetworkTarget.Outer: + { + bufferPacketParser = new OuterBufferPacketParser(); + break; + } + } + + bufferPacketParser.Scene = network.Scene; + bufferPacketParser.Network = network; + bufferPacketParser.MessageDispatcherComponent = network.Scene.MessageDispatcherComponent; + return bufferPacketParser; + } +#endif + public static T CreateClient(ANetwork network) where T : APacketParser + { + var packetParserType = typeof(T); + + switch (network.NetworkTarget) + { +#if FANTASY_NET + case NetworkTarget.Inner: + { + APacketParser innerPacketParser = null; + + if (packetParserType == typeof(ReadOnlyMemoryPacketParser)) + { + innerPacketParser = new InnerReadOnlyMemoryPacketParser(); + } + else if (packetParserType == typeof(BufferPacketParser)) + { + innerPacketParser = new InnerBufferPacketParser(); + } + // else if(packetParserType == typeof(CircularBufferPacketParser)) + // { + // innerPacketParser = new InnerCircularBufferPacketParser(); + // } + + innerPacketParser.Scene = network.Scene; + innerPacketParser.Network = network; + innerPacketParser.MessageDispatcherComponent = network.Scene.MessageDispatcherComponent; + return (T)innerPacketParser; + } +#endif + case NetworkTarget.Outer: + { + APacketParser outerPacketParser = null; + + if (packetParserType == typeof(ReadOnlyMemoryPacketParser)) + { + outerPacketParser = new OuterReadOnlyMemoryPacketParser(); + } + else if (packetParserType == typeof(BufferPacketParser)) + { +#if FANTASY_WEBGL + outerPacketParser = new OuterWebglBufferPacketParser(); +#else + outerPacketParser = new OuterBufferPacketParser(); +#endif + } + outerPacketParser.Scene = network.Scene; + outerPacketParser.Network = network; + outerPacketParser.MessageDispatcherComponent = network.Scene.MessageDispatcherComponent; + return (T)outerPacketParser; + } + default: + throw new ArgumentOutOfRangeException(); + } + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Message/Scheduler/ClientMessageScheduler.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Message/Scheduler/ClientMessageScheduler.cs new file mode 100644 index 0000000..baf981a --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Message/Scheduler/ClientMessageScheduler.cs @@ -0,0 +1,96 @@ +using System; +using Fantasy.Async; +using Fantasy.Network; +using Fantasy.Network.Interface; +using Fantasy.PacketParser.Interface; + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + +namespace Fantasy.Scheduler +{ +#if FANTASY_UNITY || FANTASY_CONSOLE + /// + /// 提供了一个用于客户端网络消息调度和处理的抽象基类。 + /// + public sealed class ClientMessageScheduler : ANetworkMessageScheduler + { + public ClientMessageScheduler(Scene scene) : base(scene) { } + + public override async FTask Scheduler(Session session, APackInfo packInfo) + { + await FTask.CompletedTask; + switch (packInfo.OpCodeIdStruct.Protocol) + { + case OpCodeType.OuterMessage: + case OpCodeType.OuterRequest: + case OpCodeType.OuterAddressableMessage: + case OpCodeType.OuterAddressableRequest: + case OpCodeType.OuterCustomRouteMessage: + case OpCodeType.OuterCustomRouteRequest: + case OpCodeType.OuterRoamingMessage: + case OpCodeType.OuterRoamingRequest: + { + using (packInfo) + { + var messageType = MessageDispatcherComponent.GetOpCodeType(packInfo.ProtocolCode); + + if (messageType == null) + { + throw new Exception($"可能遭受到恶意发包或没有协议定义ProtocolCode ProtocolCode:{packInfo.ProtocolCode}"); + } + + var message = packInfo.Deserialize(messageType); + MessageDispatcherComponent.MessageHandler(session, messageType, message, packInfo.RpcId, packInfo.ProtocolCode); + } + + return; + } + case OpCodeType.OuterResponse: + case OpCodeType.OuterPingResponse: + case OpCodeType.OuterAddressableResponse: + case OpCodeType.OuterCustomRouteResponse: + case OpCodeType.OuterRoamingResponse: + { + using (packInfo) + { + var messageType = MessageDispatcherComponent.GetOpCodeType(packInfo.ProtocolCode); + + if (messageType == null) + { + throw new Exception($"可能遭受到恶意发包或没有协议定义ProtocolCode ProtocolCode:{packInfo.ProtocolCode}"); + } + + // 这个一般是客户端Session.Call发送时使用的、目前这个逻辑只有Unity客户端时使用 + + var aResponse = (IResponse)packInfo.Deserialize(messageType); + + if (!session.RequestCallback.Remove(packInfo.RpcId, out var action)) + { + Log.Error($"not found rpc {packInfo.RpcId}, response message: {aResponse.GetType().Name}"); + return; + } + + action.SetResult(aResponse); + } + + return; + } + default: + { + packInfo.Dispose(); + throw new NotSupportedException($"Received unsupported message protocolCode:{packInfo.ProtocolCode}"); + } + } + } + } +#endif +#if FANTASY_NET + internal sealed class ClientMessageScheduler(Scene scene) : ANetworkMessageScheduler(scene) + { + public override FTask Scheduler(Session session, APackInfo packInfo) + { + throw new NotSupportedException($"ClientMessageScheduler Received unsupported message protocolCode:{packInfo.ProtocolCode}"); + } + } +#endif +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Message/Scheduler/InnerMessageScheduler.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Message/Scheduler/InnerMessageScheduler.cs new file mode 100644 index 0000000..b1b55f6 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Message/Scheduler/InnerMessageScheduler.cs @@ -0,0 +1,211 @@ +#if FANTASY_NET +// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract +using System.Runtime.CompilerServices; +using Fantasy.Async; +using Fantasy.Network; +using Fantasy.Network.Interface; +using Fantasy.PacketParser; +using Fantasy.PacketParser.Interface; +#pragma warning disable CS8604 // Possible null reference argument. + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +namespace Fantasy.Scheduler +{ + /// + /// 提供了一个机制来调度和处理内部网络消息。 + /// + internal sealed class InnerMessageScheduler(Scene scene) : ANetworkMessageScheduler(scene) + { + public override async FTask Scheduler(Session session, APackInfo packInfo) + { + var protocol = packInfo.OpCodeIdStruct.Protocol; + + switch (protocol) + { + case OpCodeType.InnerMessage: + case OpCodeType.InnerRequest: + { + var messageType = MessageDispatcherComponent.GetOpCodeType(packInfo.ProtocolCode); + + try + { + if (messageType == null) + { + throw new Exception($"可能遭受到恶意发包或没有协议定义ProtocolCode ProtocolCode:{packInfo.ProtocolCode}"); + } + + var message = packInfo.Deserialize(messageType); + MessageDispatcherComponent.MessageHandler(session, messageType, message, packInfo.RpcId, packInfo.ProtocolCode); + } + catch (Exception e) + { + Log.Error($"ANetworkMessageScheduler OuterResponse error messageProtocolCode:{packInfo.ProtocolCode} messageType:{messageType} SessionId {session.Id} IsDispose {session.IsDisposed} {e}"); + } + finally + { + packInfo.Dispose(); + } + + return; + } + case OpCodeType.InnerResponse: + case OpCodeType.InnerRouteResponse: + case OpCodeType.InnerAddressableResponse: + case OpCodeType.InnerRoamingResponse: + case OpCodeType.OuterAddressableResponse: + case OpCodeType.OuterCustomRouteResponse: + case OpCodeType.OuterRoamingResponse: + { + using (packInfo) + { + var messageType = MessageDispatcherComponent.GetOpCodeType(packInfo.ProtocolCode); + + if (messageType == null) + { + throw new Exception($"可能遭受到恶意发包或没有协议定义ProtocolCode ProtocolCode:{packInfo.ProtocolCode}"); + } + + NetworkMessagingComponent.ResponseHandler(packInfo.RpcId, (IResponse)packInfo.Deserialize(messageType)); + } + + return; + } + case OpCodeType.InnerRouteMessage: + case OpCodeType.InnerAddressableMessage: + case OpCodeType.InnerRoamingMessage: + { + using (packInfo) + { + var messageType = MessageDispatcherComponent.GetOpCodeType(packInfo.ProtocolCode); + + if (messageType == null) + { + throw new Exception($"InnerMessageScheduler error 可能遭受到恶意发包或没有协议定义ProtocolCode ProtocolCode:{packInfo.ProtocolCode}"); + } + + if (!Scene.TryGetEntity(packInfo.RouteId, out var entity)) + { + Scene.MessageDispatcherComponent.FailRouteResponse(session, messageType, InnerErrorCode.ErrNotFoundRoute, packInfo.RpcId); + return; + } + + var obj = packInfo.Deserialize(messageType); + await Scene.MessageDispatcherComponent.RouteMessageHandler(session, messageType, entity, (IMessage)obj, packInfo.RpcId); + } + + return; + } + case OpCodeType.InnerRouteRequest: + case OpCodeType.InnerAddressableRequest: + case OpCodeType.InnerRoamingRequest: + { + using (packInfo) + { + var messageType = MessageDispatcherComponent.GetOpCodeType(packInfo.ProtocolCode); + + if (messageType == null) + { + throw new Exception($"InnerMessageScheduler error 可能遭受到恶意发包或没有协议定义ProtocolCode ProtocolCode:{packInfo.ProtocolCode}"); + } + + if (!Scene.TryGetEntity(packInfo.RouteId, out var entity)) + { + Scene.MessageDispatcherComponent.FailRouteResponse(session, messageType, InnerErrorCode.ErrNotFoundRoute, packInfo.RpcId); + return; + } + + var obj = packInfo.Deserialize(messageType); + await Scene.MessageDispatcherComponent.RouteMessageHandler(session, messageType, entity, (IMessage)obj, packInfo.RpcId); + } + + return; + } + case OpCodeType.OuterCustomRouteRequest: + case OpCodeType.OuterAddressableRequest: + case OpCodeType.OuterAddressableMessage: + case OpCodeType.OuterCustomRouteMessage: + case OpCodeType.OuterRoamingMessage: + case OpCodeType.OuterRoamingRequest: + { + var entity = Scene.GetEntity(packInfo.RouteId); + + switch (entity) + { + case null: + { + // 执行到这里有两种情况: + using (packInfo) + { + switch (Scene.SceneConfig.SceneTypeString) + { + case "Gate": + { + // 1、当前是Gate进行,需要转发消息给客户端,但当前这个Session已经断开了。 + // 这种情况不需要做任何处理。 + return; + } + default: + { + // 2、当前是其他Scene、消息通过Gate发送到这个Scene上面,但这个Scene上面没有这个Entity。 + // 因为这个是Gate转发消息到这个Scene的,如果没有找到Entity要返回错误给Gate。 + // 出现这个情况一定要打印日志,因为出现这个问题肯定是上层逻辑导致的,不应该出现这样的问题。 + var packInfoRouteId = packInfo.RouteId; + var messageType = MessageDispatcherComponent.GetOpCodeType(packInfo.ProtocolCode); + + switch (protocol) + { + case OpCodeType.OuterCustomRouteRequest: + case OpCodeType.OuterAddressableRequest: + case OpCodeType.OuterAddressableMessage: + { + Scene.MessageDispatcherComponent.FailRouteResponse(session, messageType, InnerErrorCode.ErrNotFoundRoute, packInfo.RpcId); + return; + } + } + + throw new Exception($"The Entity associated with RouteId = {packInfoRouteId} was not found! messageType = {messageType.FullName} protocol = {protocol}"); + } + } + } + } + case Session gateSession: + { + using (packInfo) + { + // 这里如果是Session只可能是Gate的Session、如果是的话、肯定是转发消息 + gateSession.Send(packInfo.MemoryStream, packInfo.RpcId); + } + + return; + } + default: + { + using (packInfo) + { + var messageType = MessageDispatcherComponent.GetOpCodeType(packInfo.ProtocolCode); + + if (messageType == null) + { + throw new Exception($"InnerMessageScheduler error 可能遭受到恶意发包或没有协议定义ProtocolCode ProtocolCode:{packInfo.ProtocolCode}"); + } + + var obj = packInfo.Deserialize(messageType); + await Scene.MessageDispatcherComponent.RouteMessageHandler(session, messageType, entity, (IMessage)obj, packInfo.RpcId); + } + + return; + } + } + } + default: + { + var infoProtocolCode = packInfo.ProtocolCode; + packInfo.Dispose(); + throw new NotSupportedException($"InnerMessageScheduler Received unsupported message protocolCode:{infoProtocolCode}"); + } + } + } + } +} +#endif + diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Message/Scheduler/Interface/ANetworkMessageScheduler.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Message/Scheduler/Interface/ANetworkMessageScheduler.cs new file mode 100644 index 0000000..b296cff --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Message/Scheduler/Interface/ANetworkMessageScheduler.cs @@ -0,0 +1,30 @@ +using System; +using System.IO; +using Fantasy.Async; +using Fantasy.Network; +using Fantasy.Network.Interface; +using Fantasy.PacketParser.Interface; +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +// ReSharper disable UnassignedField.Global +#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type. + +namespace Fantasy.Scheduler +{ + public abstract class ANetworkMessageScheduler + { + protected readonly Scene Scene; + protected readonly MessageDispatcherComponent MessageDispatcherComponent; +#if FANTASY_NET + protected readonly NetworkMessagingComponent NetworkMessagingComponent; +#endif + protected ANetworkMessageScheduler(Scene scene) + { + Scene = scene; + MessageDispatcherComponent = scene.MessageDispatcherComponent; +#if FANTASY_NET + NetworkMessagingComponent = scene.NetworkMessagingComponent; +#endif + } + public abstract FTask Scheduler(Session session, APackInfo packInfo); + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Message/Scheduler/MessageHelper/MessageSender.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Message/Scheduler/MessageHelper/MessageSender.cs new file mode 100644 index 0000000..c645183 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Message/Scheduler/MessageHelper/MessageSender.cs @@ -0,0 +1,107 @@ +using System; +using Fantasy.Async; +using Fantasy.Helper; +using Fantasy.Network.Interface; + +#pragma warning disable CS8625 +#pragma warning disable CS8618 + +namespace Fantasy.Scheduler +{ + /// + /// 网络消息发送者的类。 + /// + public struct MessageSender : IDisposable + { + /// + /// 获取或设置 RPC ID。 + /// + public uint RpcId { get; private set; } + /// + /// 获取或设置路由 ID。 + /// + public long RouteId { get; private set; } + /// + /// 获取或设置创建时间。 + /// + public long CreateTime { get; private set; } + /// + /// 获取或设置消息类型。 + /// + public Type MessageType { get; private set; } + /// + /// 获取或设置请求消息。 + /// + public IMessage Request { get; private set; } + /// + /// 获取或设置任务。 + /// + public FTask Tcs { get; private set; } + + /// + /// 释放资源。 + /// + public void Dispose() + { + RpcId = 0; + RouteId = 0; + CreateTime = 0; + Tcs = null; + Request = null; + MessageType = null; + } + + /// + /// 创建一个 实例。 + /// + /// RPC ID。 + /// 请求消息类型。 + /// 任务。 + /// 创建的 实例。 + public static MessageSender Create(uint rpcId, Type requestType, FTask tcs) + { + var routeMessageSender = new MessageSender(); + routeMessageSender.Tcs = tcs; + routeMessageSender.RpcId = rpcId; + routeMessageSender.MessageType = requestType; + routeMessageSender.CreateTime = TimeHelper.Now; + return routeMessageSender; + } + + /// + /// 创建一个 实例。 + /// + /// RPC ID。 + /// 请求消息。 + /// 任务。 + /// 创建的 实例。 + public static MessageSender Create(uint rpcId, IRequest request, FTask tcs) + { + var routeMessageSender = new MessageSender(); + routeMessageSender.Tcs = tcs; + routeMessageSender.RpcId = rpcId; + routeMessageSender.Request = request; + routeMessageSender.CreateTime = TimeHelper.Now; + return routeMessageSender; + } + + /// + /// 创建一个 实例。 + /// + /// RPC ID。 + /// 路由 ID。 + /// 路由消息请求。 + /// 任务。 + /// 创建的 实例。 + public static MessageSender Create(uint rpcId, long routeId, IRouteMessage request, FTask tcs) + { + var routeMessageSender = new MessageSender(); + routeMessageSender.Tcs = tcs; + routeMessageSender.RpcId = rpcId; + routeMessageSender.RouteId = routeId; + routeMessageSender.Request = request; + routeMessageSender.CreateTime = TimeHelper.Now; + return routeMessageSender; + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Message/Scheduler/MessageHelper/NetworkMessagingComponent.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Message/Scheduler/MessageHelper/NetworkMessagingComponent.cs new file mode 100644 index 0000000..7580dd9 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Message/Scheduler/MessageHelper/NetworkMessagingComponent.cs @@ -0,0 +1,273 @@ +#if FANTASY_NET +using Fantasy.Entitas; +using System.Runtime.CompilerServices; +using Fantasy.Async; +using Fantasy.Entitas.Interface; +using Fantasy.Helper; +using Fantasy.Network; +using Fantasy.Network.Interface; +using Fantasy.Network.Route; +using Fantasy.PacketParser; +using Fantasy.PacketParser.Interface; +using Fantasy.Timer; +// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract +#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. +#pragma warning disable CS8603 // Possible null reference return. +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +namespace Fantasy.Scheduler +{ + public struct NetworkMessageUpdate + { + public NetworkMessagingComponent NetworkMessagingComponent; + } + + public class NetworkMessagingComponentAwakeSystem : AwakeSystem + { + protected override void Awake(NetworkMessagingComponent self) + { + var selfScene = self.Scene; + self.TimerComponent = selfScene.TimerComponent; + self.MessageDispatcherComponent = selfScene.MessageDispatcherComponent; + self.AddressableRouteMessageLock = selfScene.CoroutineLockComponent.Create(self.GetType().TypeHandle.Value.ToInt64()); + + self.TimerId = self.TimerComponent.Net.RepeatedTimer(10000, new NetworkMessageUpdate() + { + NetworkMessagingComponent = self + }); + } + } + + public class NetworkMessagingComponentDestroySystem : DestroySystem + { + protected override void Destroy(NetworkMessagingComponent self) + { + if (self.TimerId != 0) + { + self.TimerComponent.Net.Remove(ref self.TimerId); + } + + foreach (var (rpcId, messageSender) in self.RequestCallback.ToDictionary()) + { + self.ReturnMessageSender(rpcId, messageSender); + } + + self.AddressableRouteMessageLock.Dispose(); + + self.RequestCallback.Clear(); + self.TimeoutRouteMessageSenders.Clear(); + self.TimerComponent = null; + self.MessageDispatcherComponent = null; + self.AddressableRouteMessageLock = null; + } + } + public sealed class NetworkMessagingComponent : Entity + { + public long TimerId; + private uint _rpcId; + public CoroutineLock AddressableRouteMessageLock; + public TimerComponent TimerComponent; + public MessageDispatcherComponent MessageDispatcherComponent; + public readonly SortedDictionary RequestCallback = new(); + public readonly Dictionary TimeoutRouteMessageSenders = new(); + + public void SendInnerRoute(long routeId, IRouteMessage message) + { + if (routeId == 0) + { + Log.Error($"SendInnerRoute appId == 0"); + return; + } + + Scene.GetSession(routeId).Send(message, 0, routeId); + } + + internal void SendInnerRoute(long routeId, Type messageType, APackInfo packInfo) + { + if (routeId == 0) + { + Log.Error($"SendInnerRoute routeId == 0"); + return; + } + + Scene.GetSession(routeId).Send(0, routeId, messageType, packInfo); + } + + public void SendInnerRoute(ICollection routeIdCollection, IRouteMessage message) + { + if (routeIdCollection.Count <= 0) + { + Log.Error("SendInnerRoute routeIdCollection.Count <= 0"); + return; + } + + using var processPackInfo = ProcessPackInfo.Create(Scene, message, routeIdCollection.Count); + foreach (var routeId in routeIdCollection) + { + processPackInfo.Set(0, routeId); + Scene.GetSession(routeId).Send(processPackInfo, 0, routeId); + } + } + + public async FTask SendAddressable(long addressableId, IRouteMessage message) + { + await CallAddressable(addressableId, message); + } + + internal async FTask CallInnerRoute(long routeId, Type requestType, APackInfo packInfo) + { + if (routeId == 0) + { + Log.Error($"CallInnerRoute routeId == 0"); + return null; + } + + var rpcId = ++_rpcId; + var session = Scene.GetSession(routeId); + var requestCallback = FTask.Create(false); + RequestCallback.Add(rpcId, MessageSender.Create(rpcId, requestType, requestCallback)); + session.Send(rpcId, routeId, requestType, packInfo); + return await requestCallback; + } + + public async FTask CallInnerRouteBySession(Session session, long routeId, IRouteMessage request) + { + var rpcId = ++_rpcId; + var requestCallback = FTask.Create(false); + RequestCallback.Add(rpcId, MessageSender.Create(rpcId, request, requestCallback)); + session.Send(request, rpcId, routeId); + return await requestCallback; + } + + public async FTask CallInnerRoute(long routeId, IRouteMessage request) + { + if (routeId == 0) + { + Log.Error($"CallInnerRoute routeId == 0"); + return null; + } + + var rpcId = ++_rpcId; + var session = Scene.GetSession(routeId); + var requestCallback = FTask.Create(false); + RequestCallback.Add(rpcId, MessageSender.Create(rpcId, request, requestCallback)); + session.Send(request, rpcId, routeId); + return await requestCallback; + } + + public async FTask CallAddressable(long addressableId, IRouteMessage request) + { + var failCount = 0; + + using (await AddressableRouteMessageLock.Wait(addressableId, "CallAddressable")) + { + var addressableRouteId = await AddressableHelper.GetAddressableRouteId(Scene, addressableId); + + while (true) + { + if (addressableRouteId == 0) + { + addressableRouteId = await AddressableHelper.GetAddressableRouteId(Scene, addressableId); + } + + if (addressableRouteId == 0) + { + return MessageDispatcherComponent.CreateResponse(request.GetType(), InnerErrorCode.ErrNotFoundRoute); + } + + var iRouteResponse = await CallInnerRoute(addressableRouteId, request); + + switch (iRouteResponse.ErrorCode) + { + case InnerErrorCode.ErrNotFoundRoute: + { + if (++failCount > 20) + { + Log.Error($"AddressableComponent.Call failCount > 20 route send message fail, routeId: {addressableRouteId} AddressableMessageComponent:{addressableId}"); + return iRouteResponse; + } + + await TimerComponent.Net.WaitAsync(500); + addressableRouteId = 0; + continue; + } + case InnerErrorCode.ErrRouteTimeout: + { + Log.Error($"CallAddressableRoute ErrorCode.ErrRouteTimeout Error:{iRouteResponse.ErrorCode} Message:{request}"); + return iRouteResponse; + } + default: + { + return iRouteResponse; + } + } + } + } + } + + public void ResponseHandler(uint rpcId, IResponse response) + { + if (!RequestCallback.Remove(rpcId, out var routeMessageSender)) + { + throw new Exception($"not found rpc, response.RpcId:{rpcId} response message: {response.GetType().Name} Process:{Scene.Process.Id} Scene:{Scene.SceneConfigId}"); + } + + ResponseHandler(routeMessageSender, response); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void ResponseHandler(MessageSender messageSender, IResponse response) + { + if (response.ErrorCode == InnerErrorCode.ErrRouteTimeout) + { +#if FANTASY_DEVELOP + messageSender.Tcs.SetException(new Exception($"Rpc error: request, 注意RouteId消息超时,请注意查看是否死锁或者没有reply: RouteId: {messageSender.RouteId} {messageSender.Request.ToJson()}, response: {response}")); +#else + messageSender.Tcs.SetException(new Exception($"Rpc error: request, 注意RouteId消息超时,请注意查看是否死锁或者没有reply: RouteId: {messageSender.RouteId} {messageSender.Request}, response: {response}")); +#endif + messageSender.Dispose(); + return; + } + + messageSender.Tcs.SetResult(response); + messageSender.Dispose(); + } + + public void ReturnMessageSender(uint rpcId, MessageSender messageSender) + { + try + { + switch (messageSender.Request) + { + case IRouteMessage iRouteMessage: + { + // IRouteMessage是个特殊的RPC协议、这里不处理就可以了。 + break; + } + case IRequest iRequest: + { + var response = MessageDispatcherComponent.CreateResponse(iRequest.GetType(), InnerErrorCode.ErrRpcFail); + var responseRpcId = messageSender.RpcId; + ResponseHandler(responseRpcId, response); + Log.Warning($"timeout rpcId:{rpcId} responseRpcId:{responseRpcId} {iRequest.ToJson()}"); + break; + } + default: + { + Log.Error(messageSender.Request != null + ? $"Unsupported protocol type {messageSender.Request.GetType()} rpcId:{rpcId} messageSender.Request != null" + : $"Unsupported protocol type:{messageSender.MessageType.FullName} rpcId:{rpcId}"); + RequestCallback.Remove(rpcId); + break; + } + } + } + catch (Exception e) + { + Console.WriteLine(e); + throw; + } + } + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Message/Scheduler/MessageHelper/OnNetworkMessageUpdateCheckTimeout.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Message/Scheduler/MessageHelper/OnNetworkMessageUpdateCheckTimeout.cs new file mode 100644 index 0000000..7dd2ea3 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Message/Scheduler/MessageHelper/OnNetworkMessageUpdateCheckTimeout.cs @@ -0,0 +1,60 @@ +using Fantasy.Helper; +using Fantasy.Timer; + +#if FANTASY_NET +// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract +namespace Fantasy.Scheduler +{ + /// + /// 网络消息更新检查超时。 + /// + public sealed class OnNetworkMessageUpdateCheckTimeout : TimerHandler + { + /// + /// 超时时间(毫秒)。 + /// + private const long Timeout = 40000; + + /// + /// 处理网络消息更新检查超时。 + /// + /// + /// + protected override void Handler(NetworkMessageUpdate self) + { + var timeNow = TimeHelper.Now; + var selfNetworkMessagingComponent = self.NetworkMessagingComponent; + + // 遍历请求回调字典,检查是否有超时的请求,将超时请求添加到超时消息发送列表中。 + + foreach (var (rpcId, value) in selfNetworkMessagingComponent.RequestCallback) + { + if (timeNow < value.CreateTime + Timeout) + { + break; + } + + selfNetworkMessagingComponent.TimeoutRouteMessageSenders.Add(rpcId, value); + } + + // 如果没有超时的请求,直接返回。 + + if (selfNetworkMessagingComponent.TimeoutRouteMessageSenders.Count == 0) + { + return; + } + + // 处理超时的请求,根据请求类型生成相应的响应消息,并进行处理。 + + foreach (var (rpcId, routeMessageSender) in selfNetworkMessagingComponent.TimeoutRouteMessageSenders) + { + selfNetworkMessagingComponent.ReturnMessageSender(rpcId, routeMessageSender); + } + + // 清空超时消息发送列表。 + + selfNetworkMessagingComponent.TimeoutRouteMessageSenders.Clear(); + } + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Message/Scheduler/OuterMessageScheduler.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Message/Scheduler/OuterMessageScheduler.cs new file mode 100644 index 0000000..2c963ff --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Message/Scheduler/OuterMessageScheduler.cs @@ -0,0 +1,366 @@ +using System; +using Fantasy.Async; +using Fantasy.Network; +using Fantasy.PacketParser.Interface; +#if FANTASY_NET +using System.Text; +using Fantasy.Network.Interface; +using Fantasy.Network.Route; +using Fantasy.PacketParser; +using Fantasy.Helper; +using Fantasy.InnerMessage; +using Fantasy.Roaming; +#endif + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + +// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract + +namespace Fantasy.Scheduler +{ + /// + /// 提供了一个机制来调度和处理外部网络消息。 + /// +#if FANTASY_UNITY + public sealed class OuterMessageScheduler : ANetworkMessageScheduler + { + public OuterMessageScheduler(Scene scene) : base(scene) { } + + /// + /// 在Unity环境下,处理外部消息的方法。 + /// + /// 网络会话。 + /// 消息封包信息。 + public override FTask Scheduler(Session session, APackInfo packInfo) + { + throw new NotSupportedException($"Received unsupported message protocolCode:{packInfo.ProtocolCode}"); + } + } +#endif +#if FANTASY_NET + internal sealed class OuterMessageScheduler(Scene scene) : ANetworkMessageScheduler(scene) + { + private readonly PingResponse _pingResponse = new PingResponse(); + public override async FTask Scheduler(Session session, APackInfo packInfo) + { + if (session.IsDisposed) + { + return; + } + + switch (packInfo.OpCodeIdStruct.Protocol) + { + case OpCodeType.OuterPingRequest: + { + // 注意心跳目前只有外网才才会有、内网之间不需要心跳。 + + session.LastReceiveTime = TimeHelper.Now; + _pingResponse.Now = session.LastReceiveTime; + + using (packInfo) + { + session.Send(_pingResponse, packInfo.RpcId); + } + + return; + } + case OpCodeType.OuterMessage: + case OpCodeType.OuterRequest: + { + var messageType = MessageDispatcherComponent.GetOpCodeType(packInfo.ProtocolCode); + + try + { + if (messageType == null) + { + throw new Exception($"可能遭受到恶意发包或没有协议定义ProtocolCode ProtocolCode:{packInfo.ProtocolCode}"); + } + + var message = packInfo.Deserialize(messageType); + MessageDispatcherComponent.MessageHandler(session, messageType, message, packInfo.RpcId, packInfo.ProtocolCode); + } + catch (Exception e) + { + Log.Error($"ANetworkMessageScheduler OuterResponse error messageProtocolCode:{packInfo.ProtocolCode} messageType:{messageType} SessionId {session.Id} IsDispose {session.IsDisposed} {e}"); + } + finally + { + packInfo.Dispose(); + } + + return; + } + case OpCodeType.OuterResponse: + { + using (packInfo) + { + var messageType = MessageDispatcherComponent.GetOpCodeType(packInfo.ProtocolCode); + + if (messageType == null) + { + throw new Exception($"可能遭受到恶意发包或没有协议定义ProtocolCode ProtocolCode:{packInfo.ProtocolCode}"); + } + + NetworkMessagingComponent.ResponseHandler(packInfo.RpcId, (IResponse)packInfo.Deserialize(messageType)); + } + + return; + } + case OpCodeType.OuterAddressableMessage: + { + var packInfoPackInfoId = packInfo.PackInfoId; + + try + { + var messageType = MessageDispatcherComponent.GetOpCodeType(packInfo.ProtocolCode); + + if (messageType == null) + { + throw new Exception($"OuterMessageScheduler error 可能遭受到恶意发包或没有协议定义ProtocolCode ProtocolCode:{packInfo.ProtocolCode}"); + } + + var addressableRouteComponent = session.AddressableRouteComponent; + + if (addressableRouteComponent == null) + { + throw new Exception("OuterMessageScheduler error session does not have an AddressableRouteComponent component"); + } + + await addressableRouteComponent.Send(messageType, packInfo); + } + finally + { + if (packInfo.PackInfoId == packInfoPackInfoId) + { + packInfo.Dispose(); + } + } + + return; + } + case OpCodeType.OuterAddressableRequest: + { + var packInfoPackInfoId = packInfo.PackInfoId; + + try + { + var messageType = MessageDispatcherComponent.GetOpCodeType(packInfo.ProtocolCode); + + if (messageType == null) + { + throw new Exception($"OuterMessageScheduler error 可能遭受到恶意发包或没有协议定义ProtocolCode ProtocolCode:{packInfo.ProtocolCode}"); + } + + var addressableRouteComponent = session.AddressableRouteComponent; + + if (addressableRouteComponent == null) + { + throw new Exception("OuterMessageScheduler error session does not have an AddressableRouteComponent component"); + } + + var rpcId = packInfo.RpcId; + var runtimeId = session.RuntimeId; + var response = await addressableRouteComponent.Call(messageType, packInfo); + // session可能已经断开了,所以这里需要判断 + if (session.RuntimeId == runtimeId) + { + session.Send(response, rpcId); + } + } + finally + { + if (packInfo.PackInfoId == packInfoPackInfoId) + { + packInfo.Dispose(); + } + } + + return; + } + case OpCodeType.OuterCustomRouteMessage: + { + var packInfoProtocolCode = packInfo.ProtocolCode; + var packInfoPackInfoId = packInfo.PackInfoId; + + try + { + if (!MessageDispatcherComponent.GetCustomRouteType(packInfoProtocolCode, out var routeType)) + { + throw new Exception($"OuterMessageScheduler error 可能遭受到恶意发包或没有协议定义ProtocolCode ProtocolCode:{packInfo.ProtocolCode}"); + } + + var messageType = MessageDispatcherComponent.GetOpCodeType(packInfo.ProtocolCode); + + if (messageType == null) + { + throw new Exception($"OuterMessageScheduler error 可能遭受到恶意发包或没有协议定义ProtocolCode ProtocolCode:{packInfo.ProtocolCode}"); + } + + var routeComponent = session.RouteComponent; + + if (routeComponent == null) + { + throw new Exception($"OuterMessageScheduler CustomRouteType session does not have an routeComponent component messageType:{messageType.FullName} ProtocolCode:{packInfo.ProtocolCode}"); + } + + if (!routeComponent.TryGetRouteId(routeType, out var routeId)) + { + throw new Exception($"OuterMessageScheduler RouteComponent cannot find RouteId with RouteType {routeType}"); + } + + NetworkMessagingComponent.SendInnerRoute(routeId, messageType, packInfo); + } + finally + { + if (packInfo.PackInfoId == packInfoPackInfoId) + { + packInfo.Dispose(); + } + } + + return; + } + case OpCodeType.OuterCustomRouteRequest: + { + var packInfoProtocolCode = packInfo.ProtocolCode; + var packInfoPackInfoId = packInfo.PackInfoId; + + try + { + if (!MessageDispatcherComponent.GetCustomRouteType(packInfoProtocolCode, out var routeType)) + { + throw new Exception($"OuterMessageScheduler error 可能遭受到恶意发包或没有协议定义ProtocolCode ProtocolCode:{packInfo.ProtocolCode}"); + } + + var messageType = MessageDispatcherComponent.GetOpCodeType(packInfo.ProtocolCode); + + if (messageType == null) + { + throw new Exception($"OuterMessageScheduler error 可能遭受到恶意发包或没有协议定义ProtocolCode ProtocolCode:{packInfo.ProtocolCode}"); + } + + var routeComponent = session.RouteComponent; + + if (routeComponent == null) + { + throw new Exception("OuterMessageScheduler CustomRouteType session does not have an routeComponent component"); + } + + if (!routeComponent.TryGetRouteId(routeType, out var routeId)) + { + throw new Exception($"OuterMessageScheduler RouteComponent cannot find RouteId with RouteType {routeType}"); + } + + var rpcId = packInfo.RpcId; + var runtimeId = session.RuntimeId; + var response = await NetworkMessagingComponent.CallInnerRoute(routeId, messageType, packInfo); + // session可能已经断开了,所以这里需要判断 + if (session.RuntimeId == runtimeId) + { + session.Send(response, rpcId); + } + } + finally + { + if (packInfo.PackInfoId == packInfoPackInfoId) + { + packInfo.Dispose(); + } + } + + return; + } + case OpCodeType.OuterRoamingMessage: + { + var packInfoProtocolCode = packInfo.ProtocolCode; + var packInfoPackInfoId = packInfo.PackInfoId; + + try + { + if (!MessageDispatcherComponent.GetCustomRouteType(packInfoProtocolCode, out var routeType)) + { + throw new Exception($"OuterMessageScheduler error 可能遭受到恶意发包或没有协议定义ProtocolCode ProtocolCode:{packInfo.ProtocolCode}"); + } + + var messageType = MessageDispatcherComponent.GetOpCodeType(packInfo.ProtocolCode); + + if (messageType == null) + { + throw new Exception($"OuterMessageScheduler error 可能遭受到恶意发包或没有协议定义ProtocolCode ProtocolCode:{packInfo.ProtocolCode}"); + } + + var sessionRoamingComponent = session.SessionRoamingComponent; + + if (sessionRoamingComponent == null) + { + throw new Exception($"OuterMessageScheduler Roaming session does not have an sessionRoamingComponent component messageType:{messageType.FullName} ProtocolCode:{packInfo.ProtocolCode}"); + } + + await sessionRoamingComponent.Send(routeType, messageType, packInfo); + } + finally + { + if (packInfo.PackInfoId == packInfoPackInfoId) + { + packInfo.Dispose(); + } + } + + return; + } + case OpCodeType.OuterRoamingRequest: + { + var packInfoProtocolCode = packInfo.ProtocolCode; + var packInfoPackInfoId = packInfo.PackInfoId; + + try + { + if (!MessageDispatcherComponent.GetCustomRouteType(packInfoProtocolCode, out var routeType)) + { + throw new Exception($"OuterMessageScheduler error 可能遭受到恶意发包或没有协议定义ProtocolCode ProtocolCode:{packInfo.ProtocolCode}"); + } + + var messageType = MessageDispatcherComponent.GetOpCodeType(packInfo.ProtocolCode); + + if (messageType == null) + { + throw new Exception($"OuterMessageScheduler error 可能遭受到恶意发包或没有协议定义ProtocolCode ProtocolCode:{packInfo.ProtocolCode}"); + } + + var sessionRoamingComponent = session.SessionRoamingComponent; + + if (sessionRoamingComponent == null) + { + throw new Exception("OuterMessageScheduler Roaming session does not have an sessionRoamingComponent component"); + } + + var rpcId = packInfo.RpcId; + var runtimeId = session.RuntimeId; + var response = await sessionRoamingComponent.Call(routeType, messageType, packInfo); + // session可能已经断开了,所以这里需要判断 + if (session.RuntimeId == runtimeId) + { + session.Send(response, rpcId); + } + } + finally + { + if (packInfo.PackInfoId == packInfoPackInfoId) + { + packInfo.Dispose(); + } + } + + return; + } + default: + { + var ipAddress = session.IsDisposed ? "null" : session.RemoteEndPoint.ToString(); + packInfo.Dispose(); + throw new NotSupportedException($"OuterMessageScheduler Received unsupported message protocolCode:{packInfo.ProtocolCode}\n1、请检查该协议所在的程序集是否在框架初始化的时候添加到框架中。\n2、如果看到这个消息表示你有可能用的老版本的导出工具,请更换为最新的导出工具。\n IP地址:{ipAddress}"); + } + } + } + } +#endif +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/Exception/ScanException.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/Exception/ScanException.cs new file mode 100644 index 0000000..43c695a --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/Exception/ScanException.cs @@ -0,0 +1,21 @@ +using System; + +namespace Fantasy.Network +{ + /// + /// 在扫描过程中发生的异常。 + /// + public class ScanException : Exception + { + /// + /// 初始化 类的新实例。 + /// + public ScanException() { } + + /// + /// 使用指定的错误消息初始化 类的新实例。 + /// + /// 错误消息。 + public ScanException(string msg) : base(msg) { } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/HTTP/HTTPServerNetwork.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/HTTP/HTTPServerNetwork.cs new file mode 100644 index 0000000..9ca4eba --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/HTTP/HTTPServerNetwork.cs @@ -0,0 +1,128 @@ +#if FANTASY_NET +using System.IO; +using System.Net; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using Fantasy.Assembly; +using Fantasy.Async; +using Fantasy.Network.Interface; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Server.Kestrel.Core; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +#pragma warning disable CS8604 // Possible null reference argument. + +// ReSharper disable PossibleMultipleEnumeration + +namespace Fantasy.Network.HTTP +{ + /// + /// HTTP服务器 + /// + public sealed class HTTPServerNetwork : ANetwork + { + /// + /// 初始化入口 + /// + /// + /// + /// + public void Initialize(NetworkTarget networkTarget, string bindIp, int port) + { + base.Initialize(NetworkType.Server, NetworkProtocolType.HTTP, networkTarget); + + try + { + StartAsync(bindIp, port); + } + catch (HttpListenerException e) + { + if (e.ErrorCode == 5) + { + var sb = new StringBuilder(); + sb.AppendLine("CMD管理员中输入下面其中一个命令,具体根据您是HTTPS或HTTP决定:"); + sb.AppendLine($"HTTP请输入如下:netsh http add urlacl url=http://{bindIp}:{port}/ user=Everyone"); + sb.AppendLine($"HTTPS请输入如下:netsh http add urlacl url=https://{bindIp}:{port}/ user=Everyone"); + throw new Exception(sb.ToString(), e); + } + + Log.Error(e); + } + catch (Exception e) + { + Log.Error(e); + } + } + + private void StartAsync(string bindIp, int port) + { + var builder = WebApplication.CreateBuilder(); + // 配置日志级别为 Warning 或更高 + builder.Logging.ClearProviders(); + builder.Logging.AddConsole(); + builder.Logging.SetMinimumLevel(LogLevel.Warning); + // 将Scene注册到 DI 容器中,传递给控制器 + builder.Services.AddSingleton(Scene); + // 注册Scene同步过滤器 + builder.Services.AddScoped(); + // 注册控制器服务 + var addControllers = builder.Services.AddControllers() + .AddJsonOptions(options => { options.JsonSerializerOptions.PropertyNamingPolicy = null; }); + foreach (var assembly in AssemblySystem.ForEachAssembly) + { + addControllers.AddApplicationPart(assembly); + } + var listenUrl = ""; + var app = builder.Build(); + // 检测当前路径下是否有证书文件 + var certificatePath = Path.Combine(AppContext.BaseDirectory, $"certificate{bindIp}{port}"); + if (Directory.Exists(certificatePath)) + { + // 加载包含证书链的 PEM 文件 + var pemCertChain = File.ReadAllText(Path.Combine(certificatePath, "chain.pem")); + var pemPrivateKey = File.ReadAllText(Path.Combine(certificatePath, "private-key.pem")); + // 配置 HTTPS 监听并使用证书 + builder.WebHost.ConfigureKestrel(kestrelServerOptions => + { + kestrelServerOptions.ConfigureHttpsDefaults(https => + { + https.ServerCertificate = X509Certificate2.CreateFromPem(pemCertChain, pemPrivateKey); + }); + }); + listenUrl = $"https://{bindIp}:{port}/"; + app.Urls.Add(listenUrl); + app.UseHttpsRedirection(); + } + else + { + // 不安全的HTTP地址 + listenUrl = $"http://{bindIp}:{port}/"; + app.Urls.Add(listenUrl); + } + // 启用开发者工具 + if (app.Environment.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + // 路由注册 + app.MapControllers(); + // 开启监听 + app.RunAsync(); + Log.Info($"SceneConfigId = {Scene.SceneConfigId} HTTPServer Listen {listenUrl}"); + } + + /// + /// 移除Channel + /// + /// + /// + public override void RemoveChannel(uint channelId) + { + throw new NotImplementedException(); + } + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/HTTP/SceneContextFilter.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/HTTP/SceneContextFilter.cs new file mode 100644 index 0000000..2688bdc --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/HTTP/SceneContextFilter.cs @@ -0,0 +1,54 @@ +#if FANTASY_NET +using Fantasy.Async; +using Microsoft.AspNetCore.Mvc.Filters; + +namespace Fantasy.Network.HTTP; + +/// +/// 让所有实现SceneContextFilter的控制器,都在执行的Scene下执行 +/// +public sealed class SceneContextFilter : IAsyncActionFilter +{ + private readonly Scene _scene; + + /// + /// 构造函数 + /// + /// + public SceneContextFilter(Scene scene) + { + _scene = scene; + } + + /// + /// OnActionExecutionAsync + /// + /// + /// + /// + public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) + { + var tcs = FTask.Create(); + + _scene.ThreadSynchronizationContext.Post(() => + { + Action().Coroutine(); + }); + + await tcs; + return; + + async FTask Action() + { + try + { + await next(); + } + finally + { + tcs.SetResult(); + } + } + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/Interface/AClientNetwork.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/Interface/AClientNetwork.cs new file mode 100644 index 0000000..322c811 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/Interface/AClientNetwork.cs @@ -0,0 +1,38 @@ +using System; +using System.IO; +using Fantasy.Serialize; + +// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract +#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + +namespace Fantasy.Network.Interface +{ + /// + /// 抽象客户端网络基类。 + /// + public abstract class AClientNetwork : ANetwork, INetworkChannel + { + protected bool IsInit; + public Session Session { get; protected set; } + public abstract Session Connect(string remoteAddress, Action onConnectComplete, Action onConnectFail, Action onConnectDisconnect, bool isHttps, int connectTimeout = 5000); + public abstract void Send(uint rpcId, long routeId, MemoryStreamBuffer memoryStream, IMessage message); + public override void Dispose() + { + IsInit = false; + + if (Session != null) + { + if (!Session.IsDisposed) + { + Session.Dispose(); + } + + Session = null; + } + + base.Dispose(); + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/Interface/ANetwork.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/Interface/ANetwork.cs new file mode 100644 index 0000000..fdb85f2 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/Interface/ANetwork.cs @@ -0,0 +1,149 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using Fantasy.Entitas; +using Fantasy.PacketParser; +using Fantasy.Scheduler; +using Fantasy.Serialize; + +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + +namespace Fantasy.Network.Interface +{ + /// + /// 抽象网络基类。 + /// + public abstract class ANetwork : Entity + { + private long _outerPackInfoId; + private Queue _outerPackInfoPool; + public readonly MemoryStreamBufferPool MemoryStreamBufferPool = new MemoryStreamBufferPool(); + + public NetworkType NetworkType { get; private set; } + public NetworkTarget NetworkTarget { get; private set; } + public NetworkProtocolType NetworkProtocolType { get; private set; } + public ANetworkMessageScheduler NetworkMessageScheduler { get; private set; } + + protected void Initialize(NetworkType networkType, NetworkProtocolType networkProtocolType, NetworkTarget networkTarget) + { + NetworkType = networkType; + NetworkTarget = networkTarget; + NetworkProtocolType = networkProtocolType; +#if FANTASY_NET + if (networkProtocolType == NetworkProtocolType.HTTP) + { + return; + } + if (networkTarget == NetworkTarget.Inner) + { + _innerPackInfoPool = new Queue(); + NetworkMessageScheduler = new InnerMessageScheduler(Scene); + return; + } +#endif + switch (networkType) + { + case NetworkType.Client: + { + _outerPackInfoPool = new Queue(); + NetworkMessageScheduler = new ClientMessageScheduler(Scene); + break; + } +#if FANTASY_NET + case NetworkType.Server: + { + _outerPackInfoPool = new Queue(); + NetworkMessageScheduler = new OuterMessageScheduler(Scene); + break; + } +#endif + } + } + + public abstract void RemoveChannel(uint channelId); + public OuterPackInfo RentOuterPackInfo() + { + if (_outerPackInfoPool.Count == 0) + { + return new OuterPackInfo() + { + PackInfoId = ++_outerPackInfoId + }; + } + + if (!_outerPackInfoPool.TryDequeue(out var outerPackInfo)) + { + return new OuterPackInfo() + { + PackInfoId = ++_outerPackInfoId + }; + } + + outerPackInfo.PackInfoId = ++_outerPackInfoId; + return outerPackInfo; + } + + public void ReturnOuterPackInfo(OuterPackInfo outerPackInfo) + { + if (_outerPackInfoPool.Count > 512) + { + // 池子里最多缓存256个、其实这样设置有点多了、其实用不了512个。 + // 反而设置越大内存会占用越多。 + return; + } + + _outerPackInfoPool.Enqueue(outerPackInfo); + } +#if FANTASY_NET + private long _innerPackInfoId; + private Queue _innerPackInfoPool; + public InnerPackInfo RentInnerPackInfo() + { + if (_innerPackInfoPool.Count == 0) + { + return new InnerPackInfo() + { + PackInfoId = ++_innerPackInfoId + }; + } + + if (!_innerPackInfoPool.TryDequeue(out var innerPackInfo)) + { + return new InnerPackInfo() + { + PackInfoId = ++_innerPackInfoId + }; + } + + innerPackInfo.PackInfoId = ++_innerPackInfoId; + return innerPackInfo; + } + + public void ReturnInnerPackInfo(InnerPackInfo innerPackInfo) + { + if (_innerPackInfoPool.Count > 256) + { + // 池子里最多缓存256个、其实这样设置有点多了、其实用不了256个。 + // 反而设置越大内存会占用越多。 + return; + } + + _innerPackInfoPool.Enqueue(innerPackInfo); + } +#endif + public override void Dispose() + { + NetworkType = NetworkType.None; + NetworkTarget = NetworkTarget.None; + NetworkProtocolType = NetworkProtocolType.None; + MemoryStreamBufferPool.Dispose(); + _outerPackInfoPool?.Clear(); +#if FANTASY_NET + _innerPackInfoPool?.Clear(); +#endif + base.Dispose(); + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/Interface/ANetworkServerChannel.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/Interface/ANetworkServerChannel.cs new file mode 100644 index 0000000..788a235 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/Interface/ANetworkServerChannel.cs @@ -0,0 +1,55 @@ +#if FANTASY_NET +using System.IO; +using System.Net; +using Fantasy.Serialize; + +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + +namespace Fantasy.Network.Interface +{ + public abstract class ANetworkServerChannel : INetworkChannel + { + /// + /// 获取通道的唯一标识 ID。 + /// + public readonly uint Id; + /// + /// 获取通道的远程终端点。 + /// + public readonly EndPoint RemoteEndPoint; + /// + /// 获取或设置通道所属的场景。 + /// + public Scene Scene { get; protected set; } + /// + /// 获取或设置通道所属的会话。 + /// + public Session Session { get; protected set; } + /// + /// 获取通道是否已经被释放。 + /// + public bool IsDisposed { get; protected set; } + + protected ANetworkServerChannel(ANetwork network, uint id, EndPoint remoteEndPoint) + { + Id = id; + Scene = network.Scene; + RemoteEndPoint = remoteEndPoint; + Session = Session.Create(network.NetworkMessageScheduler, this, network.NetworkTarget); + } + + public virtual void Dispose() + { + IsDisposed = true; + + if (!Session.IsDisposed) + { + Session.Dispose(); + } + } + + public abstract void Send(uint rpcId, long routeId, MemoryStreamBuffer memoryStream, IMessage message); + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/Interface/INetworkChannel.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/Interface/INetworkChannel.cs new file mode 100644 index 0000000..52f341a --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/Interface/INetworkChannel.cs @@ -0,0 +1,14 @@ +using System; +using System.IO; +using Fantasy.Serialize; + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +namespace Fantasy.Network.Interface +{ + public interface INetworkChannel : IDisposable + { + public Session Session { get;} + public bool IsDisposed { get;} + public void Send(uint rpcId, long routeId, MemoryStreamBuffer memoryStream, IMessage message); + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/KCP/Base/Kcp.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/KCP/Base/Kcp.cs new file mode 100644 index 0000000..aa1947d --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/KCP/Base/Kcp.cs @@ -0,0 +1,451 @@ +using System; +using System.Runtime.InteropServices; +using System.Threading; +using kcp; +using static kcp.KCP; + +#pragma warning disable CS8601 +#pragma warning disable CS8602 +#pragma warning disable CS8625 + +// ReSharper disable ALL + +namespace KCP +{ + /// + /// Kcp callback + /// + /// KCP output destination + /// KCP output size (excluding reserved) + public delegate void KcpCallback(byte[] buffer, int length); + + /// + /// Kcp + /// + public sealed unsafe class Kcp : IDisposable + { + /// + /// Kcp + /// + private IKCPCB* _kcp; + + /// + /// Output function + /// + private KcpCallback _output; + + /// + /// Buffer + /// + private byte[] _buffer; + + /// + /// Reserved overhead + /// + private int _reserved; + + /// + /// Disposed + /// + private int _disposed; + + /// + /// Structure + /// + /// ConversationId + /// Output + /// Reserved overhead + public Kcp(uint conv, KcpCallback output, int reserved) + { + _kcp = ikcp_create(conv, reserved, ref _buffer); + _output = output; + _reserved = reserved; + } + + /// + /// Set + /// + public bool IsSet => _kcp != null; + + /// + /// Conversation id + /// + public uint ConversationId => _kcp->conv; + + /// + /// Maximum transmission unit + /// + public uint MaximumTransmissionUnit => _kcp->mtu; + + /// + /// Maximum segment size + /// + public uint MaximumSegmentSize => _kcp->mss; + + /// + /// Connection state + /// + public uint State => _kcp->state; + + /// + /// The sequence number of the first unacknowledged packet + /// + public uint SendUna => _kcp->snd_una; + + /// + /// The sequence number for the next packet to be sent + /// + public uint SendNext => _kcp->snd_nxt; + + /// + /// The sequence number for the next packet expected to be received + /// + public uint ReceiveNext => _kcp->rcv_nxt; + + /// + /// Slow start threshold for congestion control + /// + public uint SlowStartThreshold => _kcp->ssthresh; + + /// + /// Round-trip time variance + /// + public int RxRttval => _kcp->rx_rttval; + + /// + /// Smoothed round-trip time + /// + public int RxSrtt => _kcp->rx_srtt; + + /// + /// Retransmission timeout + /// + public int RxRto => _kcp->rx_rto; + + /// + /// Minimum retransmission timeout + /// + public int RxMinrto => _kcp->rx_minrto; + + /// + /// Send window size + /// + public uint SendWindowSize => _kcp->snd_wnd; + + /// + /// Receive window size + /// + public uint ReceiveWindowSize => _kcp->rcv_wnd; + + /// + /// Remote window size + /// + public uint RemoteWindowSize => _kcp->rmt_wnd; + + /// + /// Congestion window size + /// + public uint CongestionWindowSize => _kcp->cwnd; + + /// + /// Probe variable for fast recovery + /// + public uint Probe => _kcp->probe; + + /// + /// Current timestamp + /// + public uint Current => _kcp->current; + + /// + /// Flush interval + /// + public uint Interval => _kcp->interval; + + /// + /// Timestamp for the next flush + /// + public uint TimestampFlush => _kcp->ts_flush; + + /// + /// Number of retransmissions + /// + public uint Transmissions => _kcp->xmit; + + /// + /// Number of packets in the receive buffer + /// + public uint ReceiveBufferCount => _kcp->nrcv_buf; + + /// + /// Number of packets in the receive queue + /// + public uint ReceiveQueueCount => _kcp->nrcv_que; + + /// + /// Number of packets wait to receive + /// + public uint WaitReceiveCount => _kcp->nrcv_buf + _kcp->nrcv_que; + + /// + /// Number of packets in the send buffer + /// + public uint SendBufferCount => _kcp->nsnd_buf; + + /// + /// Number of packets in the send queue + /// + public uint SendQueueCount => _kcp->nsnd_que; + + /// + /// Number of packets wait to send + /// + public uint WaitSendCount => _kcp->nsnd_buf + _kcp->nsnd_que; + + /// + /// Whether Nagle's algorithm is disabled + /// + public uint NoDelay => _kcp->nodelay; + + /// + /// Whether the KCP connection has been updated + /// + public uint Updated => _kcp->updated; + + /// + /// Timestamp for the next probe + /// + public uint TimestampProbe => _kcp->ts_probe; + + /// + /// Probe wait time + /// + public uint ProbeWait => _kcp->probe_wait; + + /// + /// Incremental increase + /// + public uint Increment => _kcp->incr; + + /// + /// Pointer to the acknowledge list + /// + public uint* AckList => _kcp->acklist; + + /// + /// Count of acknowledges + /// + public uint AckCount => _kcp->ackcount; + + /// + /// Number of acknowledge blocks + /// + public uint AckBlock => _kcp->ackblock; + + /// + /// Buffer + /// + public byte[] Buffer => _buffer; + + /// + /// Fast resend trigger count + /// + public int FastResend => _kcp->fastresend; + + /// + /// Fast resend limit + /// + public int FastResendLimit => _kcp->fastlimit; + + /// + /// Whether congestion control is disabled + /// + public int NoCongestionWindow => _kcp->nocwnd; + + /// + /// Whether stream mode is enabled + /// + public int StreamMode => _kcp->stream; + + /// + /// Output function pointer + /// + public KcpCallback Output => _output; + + /// + /// Reserved overhead + /// + public int Reserved => _reserved; + + /// + /// Dispose + /// + public void Dispose() + { + if (Interlocked.CompareExchange(ref _disposed, 1, 0) != 0) + return; + ikcp_release(_kcp); + _kcp = null; + _output = null; + _buffer = null; + GC.SuppressFinalize(this); + } + + /// + /// Set output + /// + /// Output + public void SetOutput(KcpCallback output) => _output = output; + + /// + /// Destructure + /// + ~Kcp() => Dispose(); + + /// + /// Send + /// + /// Buffer + /// Sent bytes + public int Send(ReadOnlySpan buffer) + { + fixed (byte* pinnedBuffer = &MemoryMarshal.GetReference(buffer)) + { + return ikcp_send(_kcp, pinnedBuffer, buffer.Length); + } + } + + /// + /// Send + /// + /// Buffer + /// Length + /// Sent bytes + public int Send(byte* buffer, int length) => ikcp_send(_kcp, buffer, length); + + /// + /// Input + /// + /// Buffer + /// Input bytes + public int Input(ReadOnlySpan buffer) + { + fixed (byte* pinnedBuffer = &MemoryMarshal.GetReference(buffer)) + { + return ikcp_input(_kcp, pinnedBuffer, buffer.Length); + } + } + + /// + /// Input + /// + /// Buffer + /// Length + /// Input bytes + public int Input(byte* buffer, int length) => ikcp_input(_kcp, buffer, length); + + /// + /// Peek size + /// + /// Peeked size + public int PeekSize() => ikcp_peeksize(_kcp); + + /// + /// Receive + /// + /// Buffer + /// Received bytes + public int Receive(Span buffer) + { + fixed (byte* pinnedBuffer = &MemoryMarshal.GetReference(buffer)) + { + return ikcp_recv(_kcp, pinnedBuffer, buffer.Length); + } + } + + /// + /// Receive + /// + /// Buffer + /// Length + /// Received bytes + public int Receive(byte* buffer, int length) => ikcp_recv(_kcp, buffer, length); + + /// + /// Update + /// + /// Timestamp + public void Update(uint current) + { + fixed (byte* ptr = &_buffer[_reserved]) + { + ikcp_update(_kcp, current, ptr, _buffer, _output); + } + } + + /// + /// Check + /// + /// Timestamp + /// Next flush timestamp + public uint Check(uint current) => ikcp_check(_kcp, current); + + /// + /// Flush + /// + public void Flush() + { + fixed (byte* ptr = &_buffer[_reserved]) + { + ikcp_flush(_kcp, ptr, _buffer, _output); + } + } + + /// + /// Set maximum transmission unit + /// + /// Maximum transmission unit + /// Set + public int SetMtu(int mtu) => ikcp_setmtu(_kcp, mtu, _reserved, ref _buffer); + + /// + /// Set flush interval + /// + /// Flush interval + public void SetInterval(int interval) => ikcp_interval(_kcp, interval); + + /// + /// Set no delay + /// + /// Whether Nagle's algorithm is disabled + /// Flush interval + /// Fast resend trigger count + /// No congestion window + public void SetNoDelay(int nodelay, int interval, int resend, int nc) => ikcp_nodelay(_kcp, nodelay, interval, resend, nc); + + /// + /// Set window size + /// + /// Send window size + /// Receive window size + public void SetWindowSize(int sndwnd, int rcvwnd) => ikcp_wndsize(_kcp, sndwnd, rcvwnd); + + /// + /// Set fast resend limit + /// + /// Fast resend limit + public void SetFastResendLimit(int fastlimit) => _kcp->fastlimit = Math.Clamp(fastlimit, 0, 5); + + /// + /// Set whether stream mode is enabled + /// + /// Whether stream mode is enabled + public void SetStreamMode(int stream) => _kcp->stream = stream == 1 ? 1 : 0; + + /// + /// Set minimum retransmission timeout + /// + /// Minimum retransmission timeout + public void SetMinrto(int minrto) => _kcp->rx_minrto = (int)Math.Clamp(minrto, 1, IKCP_RTO_MAX); + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/KCP/Base/c/kcp.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/KCP/Base/c/kcp.cs new file mode 100644 index 0000000..a202cfd --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/KCP/Base/c/kcp.cs @@ -0,0 +1,1367 @@ +#if NET6_0_OR_GREATER +using System.Numerics; +#endif +using System.Runtime.CompilerServices; +using KCP; + +#pragma warning disable CA2211 +#pragma warning disable CS1591 +#pragma warning disable CS8602 +#pragma warning disable CS8632 + +// ReSharper disable ALL + +namespace kcp +{ + /// + /// https://github.com/skywind3000/kcp + /// + public static unsafe partial class KCP + { + //===================================================================== + // KCP BASIC + //===================================================================== + public const uint IKCP_RTO_NDL = 30; // no delay min rto + public const uint IKCP_RTO_MIN = 100; // normal min rto + public const uint IKCP_RTO_DEF = 200; + public const uint IKCP_RTO_MAX = 60000; + public const uint IKCP_CMD_PUSH = 81; // cmd: push data + public const uint IKCP_CMD_ACK = 82; // cmd: ack + public const uint IKCP_CMD_WASK = 83; // cmd: window probe (ask) + public const uint IKCP_CMD_WINS = 84; // cmd: window size (tell) + public const uint IKCP_ASK_SEND = 1; // need to send IKCP_CMD_WASK + public const uint IKCP_ASK_TELL = 2; // need to send IKCP_CMD_WINS + public const uint IKCP_WND_SND = 32; + public const uint IKCP_WND_RCV = 128; // must >= max fragment size + public const uint IKCP_MTU_DEF = 1400; + public const uint IKCP_ACK_FAST = 3; + public const uint IKCP_INTERVAL = 100; + public const uint IKCP_OVERHEAD = 24; + public const uint IKCP_DEADLINK = 20; + public const uint IKCP_THRESH_INIT = 2; + public const uint IKCP_THRESH_MIN = 2; + public const uint IKCP_PROBE_INIT = 7000; // 7 secs to probe window size + public const uint IKCP_PROBE_LIMIT = 120000; // up to 120 secs to probe window + public const uint IKCP_FASTACK_LIMIT = 5; // max times to trigger fastack + + //--------------------------------------------------------------------- + // encode / decode + //--------------------------------------------------------------------- + + /* encode 8 bits unsigned int */ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static byte* ikcp_encode8u(byte* p, byte c) + { + *(byte*)p++ = c; + return p; + } + + /* decode 8 bits unsigned int */ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static byte* ikcp_decode8u(byte* p, byte* c) + { + *c = *(byte*)p++; + return p; + } + + /* encode 16 bits unsigned int (lsb) */ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static byte* ikcp_encode16u(byte* p, ushort w) + { +#if IWORDS_BIG_ENDIAN || IWORDS_MUST_ALIGN + *(byte*)(p + 0) = (byte)(w & 255); + *(byte*)(p + 1) = (byte)(w >> 8); +#else + memcpy(p, &w, (nuint)2); +#endif + p += 2; + return p; + } + + /* decode 16 bits unsigned int (lsb) */ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static byte* ikcp_decode16u(byte* p, ushort* w) + { +#if IWORDS_BIG_ENDIAN || IWORDS_MUST_ALIGN + *w = *(byte*)(p + 1); + *w = (ushort)(*(byte*)(p + 0) + (*w << 8)); +#else + memcpy(w, p, (nuint)2); +#endif + p += 2; + return p; + } + + /* encode 32 bits unsigned int (lsb) */ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static byte* ikcp_encode32u(byte* p, uint l) + { +#if IWORDS_BIG_ENDIAN || IWORDS_MUST_ALIGN + *(byte*)(p + 0) = (byte)((l >> 0) & 0xff); + *(byte*)(p + 1) = (byte)((l >> 8) & 0xff); + *(byte*)(p + 2) = (byte)((l >> 16) & 0xff); + *(byte*)(p + 3) = (byte)((l >> 24) & 0xff); +#else + memcpy(p, &l, (nuint)4); +#endif + p += 4; + return p; + } + + /* decode 32 bits unsigned int (lsb) */ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static byte* ikcp_decode32u(byte* p, uint* l) + { +#if IWORDS_BIG_ENDIAN || IWORDS_MUST_ALIGN + *l = *(byte*)(p + 3); + *l = *(byte*)(p + 2) + (*l << 8); + *l = *(byte*)(p + 1) + (*l << 8); + *l = *(byte*)(p + 0) + (*l << 8); +#else + memcpy(l, p, (nuint)4); +#endif + p += 4; + return p; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint _imin_(uint a, uint b) + { + return a <= b ? a : b; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint _imax_(uint a, uint b) + { + return a >= b ? a : b; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint _ibound_(uint lower, uint middle, uint upper) + { + return _imin_(_imax_(lower, middle), upper); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint _iceilpow2_(uint x) + { +#if NET6_0_OR_GREATER + return BitOperations.RoundUpToPowerOf2(x); +#else + --x; + x |= x >> 1; + x |= x >> 2; + x |= x >> 4; + x |= x >> 8; + x |= x >> 16; + ++x; + return x; +#endif + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static long _itimediff(uint later, uint earlier) + { + return ((int)(later - earlier)); + } + + //--------------------------------------------------------------------- + // manage segment + //--------------------------------------------------------------------- + public static delegate* managed ikcp_malloc_hook = null; + public static delegate* managed ikcp_free_hook = null; + + // internal malloc + public static void* ikcp_malloc(nuint size) + { + if (ikcp_malloc_hook != null) + return ikcp_malloc_hook(size); + return malloc(size); + } + + // internal free + public static void ikcp_free(void* ptr) + { + if (ikcp_free_hook != null) + { + ikcp_free_hook(ptr); + } + else + { + free(ptr); + } + } + + // redefine allocator + public static void ikcp_allocator(delegate* managed new_malloc, delegate* managed new_free) + { + ikcp_malloc_hook = new_malloc; + ikcp_free_hook = new_free; + } + + // allocate a new kcp segment + public static IKCPSEG* ikcp_segment_new(IKCPCB* kcp, int size) + { + return (IKCPSEG*)ikcp_malloc((nuint)(sizeof(IKCPSEG) + size)); + } + + // delete a segment + public static void ikcp_segment_delete(IKCPCB* kcp, IKCPSEG* seg) + { + ikcp_free(seg); + } + + // output segment + public static void ikcp_output(IKCPCB* kcp, int size, byte[] destination, KcpCallback output) + { + assert(kcp != null); + assert(output != null); + + if (size == 0) return; + output(destination, size); + } + + //--------------------------------------------------------------------- + // create a new kcpcb + //--------------------------------------------------------------------- + public static IKCPCB* ikcp_create(uint conv, int reserved, ref byte[] buffer) + { + IKCPCB* kcp = (IKCPCB*)ikcp_malloc((nuint)sizeof(IKCPCB)); + if (kcp == null) return null; + kcp->conv = conv; + kcp->snd_una = 0; + kcp->snd_nxt = 0; + kcp->rcv_nxt = 0; + kcp->ts_recent = 0; + kcp->ts_lastack = 0; + kcp->ts_probe = 0; + kcp->probe_wait = 0; + kcp->snd_wnd = IKCP_WND_SND; + kcp->rcv_wnd = IKCP_WND_RCV; + kcp->rmt_wnd = IKCP_WND_RCV; + kcp->cwnd = 0; + kcp->incr = 0; + kcp->probe = 0; + kcp->mtu = IKCP_MTU_DEF; + kcp->mss = kcp->mtu - IKCP_OVERHEAD; + kcp->stream = 0; + + buffer = new byte[(reserved + kcp->mtu + IKCP_OVERHEAD) * 3]; + + iqueue_init(&kcp->snd_queue); + iqueue_init(&kcp->rcv_queue); + iqueue_init(&kcp->snd_buf); + iqueue_init(&kcp->rcv_buf); + kcp->nrcv_buf = 0; + kcp->nsnd_buf = 0; + kcp->nrcv_que = 0; + kcp->nsnd_que = 0; + kcp->state = 0; + kcp->acklist = null; + kcp->ackblock = 0; + kcp->ackcount = 0; + kcp->rx_srtt = 0; + kcp->rx_rttval = 0; + kcp->rx_rto = (int)IKCP_RTO_DEF; + kcp->rx_minrto = (int)IKCP_RTO_MIN; + kcp->current = 0; + kcp->interval = IKCP_INTERVAL; + kcp->ts_flush = IKCP_INTERVAL; + kcp->nodelay = 0; + kcp->updated = 0; + kcp->ssthresh = IKCP_THRESH_INIT; + kcp->fastresend = 0; + kcp->fastlimit = (int)IKCP_FASTACK_LIMIT; + kcp->nocwnd = 0; + kcp->xmit = 0; + kcp->dead_link = IKCP_DEADLINK; + + return kcp; + } + + //--------------------------------------------------------------------- + // release a new kcpcb + //--------------------------------------------------------------------- + public static void ikcp_release(IKCPCB* kcp) + { + assert(kcp != null); + if (kcp != null) + { + IKCPSEG* seg; + while (!iqueue_is_empty(&kcp->snd_buf)) + { + seg = iqueue_entry(kcp->snd_buf.next); + iqueue_del(&seg->node); + ikcp_segment_delete(kcp, seg); + } + + while (!iqueue_is_empty(&kcp->rcv_buf)) + { + seg = iqueue_entry(kcp->rcv_buf.next); + iqueue_del(&seg->node); + ikcp_segment_delete(kcp, seg); + } + + while (!iqueue_is_empty(&kcp->snd_queue)) + { + seg = iqueue_entry(kcp->snd_queue.next); + iqueue_del(&seg->node); + ikcp_segment_delete(kcp, seg); + } + + while (!iqueue_is_empty(&kcp->rcv_queue)) + { + seg = iqueue_entry(kcp->rcv_queue.next); + iqueue_del(&seg->node); + ikcp_segment_delete(kcp, seg); + } + + if (kcp->acklist != null) + { + ikcp_free(kcp->acklist); + } + + kcp->nrcv_buf = 0; + kcp->nsnd_buf = 0; + kcp->nrcv_que = 0; + kcp->nsnd_que = 0; + kcp->ackcount = 0; + kcp->acklist = null; + ikcp_free(kcp); + } + } + + //--------------------------------------------------------------------- + // user/upper level recv: returns size, returns below zero for EAGAIN + //--------------------------------------------------------------------- + public static int ikcp_recv(IKCPCB* kcp, byte* buffer, int len) + { + IQUEUEHEAD* p; + int ispeek = (len < 0) ? 1 : 0; + int peeksize; + int recover = 0; + IKCPSEG* seg; + assert(kcp != null); + + if (iqueue_is_empty(&kcp->rcv_queue)) + return -1; + + if (len < 0) len = -len; + + peeksize = ikcp_peeksize(kcp); + + if (peeksize < 0) + return -2; + + if (peeksize > len) + return -3; + + if (kcp->nrcv_que >= kcp->rcv_wnd) + recover = 1; + + // merge fragment + for (len = 0, p = kcp->rcv_queue.next; p != &kcp->rcv_queue;) + { + int fragment; + seg = iqueue_entry(p); + p = p->next; + + if (buffer != null) + { + memcpy(buffer, seg->data, (nuint)seg->len); + buffer += seg->len; + } + + len += (int)seg->len; + fragment = (int)seg->frg; + + if (ispeek == 0) + { + iqueue_del(&seg->node); + ikcp_segment_delete(kcp, seg); + kcp->nrcv_que--; + } + + if (fragment == 0) + break; + } + + assert(len == peeksize); + + // move available data from rcv_buf -> rcv_queue + while (!iqueue_is_empty(&kcp->rcv_buf)) + { + seg = iqueue_entry(kcp->rcv_buf.next); + if (seg->sn == kcp->rcv_nxt && kcp->nrcv_que < kcp->rcv_wnd) + { + iqueue_del(&seg->node); + kcp->nrcv_buf--; + iqueue_add_tail(&seg->node, &kcp->rcv_queue); + kcp->nrcv_que++; + kcp->rcv_nxt++; + } + else + { + break; + } + } + + // fast recover + if (kcp->nrcv_que < kcp->rcv_wnd && (recover != 0)) + { + // ready to send back IKCP_CMD_WINS in ikcp_flush + // tell remote my window size + kcp->probe |= IKCP_ASK_TELL; + } + + return len; + } + + //--------------------------------------------------------------------- + // peek data size + //--------------------------------------------------------------------- + public static int ikcp_peeksize(IKCPCB* kcp) + { + IQUEUEHEAD* p; + IKCPSEG* seg; + int length = 0; + + assert(kcp != null); + + if (iqueue_is_empty(&kcp->rcv_queue)) return -1; + + seg = iqueue_entry(kcp->rcv_queue.next); + if (seg->frg == 0) return (int)seg->len; + + if (kcp->nrcv_que < seg->frg + 1) return -1; + + for (p = kcp->rcv_queue.next; p != &kcp->rcv_queue; p = p->next) + { + seg = iqueue_entry(p); + length += (int)seg->len; + if (seg->frg == 0) break; + } + + return length; + } + + //--------------------------------------------------------------------- + // user/upper level send, returns below zero for error + //--------------------------------------------------------------------- + public static int ikcp_send(IKCPCB* kcp, byte* buffer, int len) + { + IKCPSEG* seg; + int count, i; + int sent = 0; + + assert(kcp->mss > 0); + if (len < 0) return -1; + + // append to previous segment in streaming mode (if possible) + if (kcp->stream != 0) + { + if (!iqueue_is_empty(&kcp->snd_queue)) + { + IKCPSEG* old = iqueue_entry(kcp->snd_queue.prev); + if (old->len < kcp->mss) + { + int capacity = (int)(kcp->mss - old->len); + int extend = (len < capacity) ? len : capacity; + seg = ikcp_segment_new(kcp, (int)(old->len + extend)); + assert(seg != null); + if (seg == null) + { + return -2; + } + + iqueue_add_tail(&seg->node, &kcp->snd_queue); + memcpy(seg->data, old->data, (nuint)old->len); + if (buffer != null) + { + memcpy(seg->data + old->len, buffer, (nuint)extend); + buffer += extend; + } + + seg->len = (uint)(old->len + extend); + seg->frg = 0; + len -= extend; + iqueue_del_init(&old->node); + ikcp_segment_delete(kcp, old); + sent = extend; + } + } + + if (len <= 0) + { + return sent; + } + } + + if (len <= (int)kcp->mss) count = 1; + else count = (int)((len + kcp->mss - 1) / kcp->mss); + + if (kcp->stream == 0 && count > 255) return -2; + + if (count >= (int)kcp->rcv_wnd) + { + if (kcp->stream != 0 && sent > 0) + return sent; + return -2; + } + + if (count == 0) count = 1; + + // fragment + for (i = 0; i < count; i++) + { + int size = len > (int)kcp->mss ? (int)kcp->mss : len; + seg = ikcp_segment_new(kcp, size); + assert(seg != null); + if (seg == null) + { + return -2; + } + + if ((buffer != null) && len > 0) + { + memcpy(seg->data, buffer, (nuint)size); + } + + seg->len = (uint)size; + seg->frg = (uint)((kcp->stream == 0) ? (count - i - 1) : 0); + iqueue_init(&seg->node); + iqueue_add_tail(&seg->node, &kcp->snd_queue); + kcp->nsnd_que++; + if (buffer != null) + { + buffer += size; + } + + len -= size; + sent += size; + } + + return sent; + } + + //--------------------------------------------------------------------- + // parse ack + //--------------------------------------------------------------------- + public static void ikcp_update_ack(IKCPCB* kcp, int rtt) + { + int rto = 0; + if (kcp->rx_srtt == 0) + { + kcp->rx_srtt = rtt; + kcp->rx_rttval = rtt / 2; + } + else + { + long delta = rtt - kcp->rx_srtt; + if (delta < 0) delta = -delta; + kcp->rx_rttval = (int)((3 * kcp->rx_rttval + delta) / 4); + kcp->rx_srtt = (7 * kcp->rx_srtt + rtt) / 8; + if (kcp->rx_srtt < 1) kcp->rx_srtt = 1; + } + + rto = (int)(kcp->rx_srtt + _imax_(kcp->interval, (uint)(4 * kcp->rx_rttval))); + kcp->rx_rto = (int)_ibound_((uint)kcp->rx_minrto, (uint)rto, IKCP_RTO_MAX); + } + + public static void ikcp_shrink_buf(IKCPCB* kcp) + { + IQUEUEHEAD* p = kcp->snd_buf.next; + if (p != &kcp->snd_buf) + { + IKCPSEG* seg = iqueue_entry(p); + kcp->snd_una = seg->sn; + } + else + { + kcp->snd_una = kcp->snd_nxt; + } + } + + public static void ikcp_parse_ack(IKCPCB* kcp, uint sn) + { + IQUEUEHEAD* p, next; + + if (_itimediff(sn, kcp->snd_una) < 0 || _itimediff(sn, kcp->snd_nxt) >= 0) + return; + + for (p = kcp->snd_buf.next; p != &kcp->snd_buf; p = next) + { + IKCPSEG* seg = iqueue_entry(p); + next = p->next; + if (sn == seg->sn) + { + iqueue_del(p); + ikcp_segment_delete(kcp, seg); + kcp->nsnd_buf--; + break; + } + + if (_itimediff(sn, seg->sn) < 0) + { + break; + } + } + } + + public static void ikcp_parse_una(IKCPCB* kcp, uint una) + { + IQUEUEHEAD* p, next; + for (p = kcp->snd_buf.next; p != &kcp->snd_buf; p = next) + { + IKCPSEG* seg = iqueue_entry(p); + next = p->next; + if (_itimediff(una, seg->sn) > 0) + { + iqueue_del(p); + ikcp_segment_delete(kcp, seg); + kcp->nsnd_buf--; + } + else + { + break; + } + } + } + + public static void ikcp_parse_fastack(IKCPCB* kcp, uint sn, uint ts) + { + IQUEUEHEAD* p, next; + + if (_itimediff(sn, kcp->snd_una) < 0 || _itimediff(sn, kcp->snd_nxt) >= 0) + return; + + for (p = kcp->snd_buf.next; p != &kcp->snd_buf; p = next) + { + IKCPSEG* seg = iqueue_entry(p); + next = p->next; + if (_itimediff(sn, seg->sn) < 0) + { + break; + } + else if (sn != seg->sn) + { +#if !IKCP_FASTACK_CONSERVE + seg->fastack++; +#else + if (_itimediff(ts, seg->ts) >= 0) + seg->fastack++; +#endif + } + } + } + + //--------------------------------------------------------------------- + // ack append + //--------------------------------------------------------------------- + public static void ikcp_ack_push(IKCPCB* kcp, uint sn, uint ts) + { + uint newsize = kcp->ackcount + 1; + uint* ptr; + + if (newsize > kcp->ackblock) + { + uint* acklist; + uint newblock; + + newblock = newsize <= 8 ? 8 : _iceilpow2_(newsize); + acklist = (uint*)ikcp_malloc((nuint)(newblock * sizeof(uint) * 2)); + + if (acklist == null) + { + assert(acklist != null); + abort(); + } + + if (kcp->acklist != null) + { + uint x; + for (x = 0; x < kcp->ackcount; x++) + { + acklist[x * 2 + 0] = kcp->acklist[x * 2 + 0]; + acklist[x * 2 + 1] = kcp->acklist[x * 2 + 1]; + } + + ikcp_free(kcp->acklist); + } + + kcp->acklist = acklist; + kcp->ackblock = newblock; + } + + ptr = &kcp->acklist[kcp->ackcount * 2]; + ptr[0] = sn; + ptr[1] = ts; + kcp->ackcount++; + } + + public static void ikcp_ack_get(IKCPCB* kcp, int p, uint* sn, uint* ts) + { + if (sn != null) sn[0] = kcp->acklist[p * 2 + 0]; + if (ts != null) ts[0] = kcp->acklist[p * 2 + 1]; + } + + //--------------------------------------------------------------------- + // parse data + //--------------------------------------------------------------------- + public static void ikcp_parse_data(IKCPCB* kcp, IKCPSEG* newseg) + { + IQUEUEHEAD* p, prev; + uint sn = newseg->sn; + int repeat = 0; + + if (_itimediff(sn, kcp->rcv_nxt + kcp->rcv_wnd) >= 0 || + _itimediff(sn, kcp->rcv_nxt) < 0) + { + ikcp_segment_delete(kcp, newseg); + return; + } + + for (p = kcp->rcv_buf.prev; p != &kcp->rcv_buf; p = prev) + { + IKCPSEG* seg = iqueue_entry(p); + prev = p->prev; + if (seg->sn == sn) + { + repeat = 1; + break; + } + + if (_itimediff(sn, seg->sn) > 0) + { + break; + } + } + + if (repeat == 0) + { + iqueue_init(&newseg->node); + iqueue_add(&newseg->node, p); + kcp->nrcv_buf++; + } + else + { + ikcp_segment_delete(kcp, newseg); + } + + // move available data from rcv_buf -> rcv_queue + while (!iqueue_is_empty(&kcp->rcv_buf)) + { + IKCPSEG* seg = iqueue_entry(kcp->rcv_buf.next); + if (seg->sn == kcp->rcv_nxt && kcp->nrcv_que < kcp->rcv_wnd) + { + iqueue_del(&seg->node); + kcp->nrcv_buf--; + iqueue_add_tail(&seg->node, &kcp->rcv_queue); + kcp->nrcv_que++; + kcp->rcv_nxt++; + } + else + { + break; + } + } + } + + //--------------------------------------------------------------------- + // input data + //--------------------------------------------------------------------- + public static int ikcp_input(IKCPCB* kcp, byte* data, long size) + { + uint prev_una = kcp->snd_una; + uint maxack = 0, latest_ts = 0; + int flag = 0; + + if (data == null || (int)size < (int)IKCP_OVERHEAD) return -1; + + while (true) + { + uint ts, sn, len, una, conv; + ushort wnd; + byte cmd, frg; + IKCPSEG* seg; + + if (size < (int)IKCP_OVERHEAD) break; + + data = ikcp_decode32u(data, &conv); + if (conv != kcp->conv) return -1; + + data = ikcp_decode8u(data, &cmd); + data = ikcp_decode8u(data, &frg); + data = ikcp_decode16u(data, &wnd); + data = ikcp_decode32u(data, &ts); + data = ikcp_decode32u(data, &sn); + data = ikcp_decode32u(data, &una); + data = ikcp_decode32u(data, &len); + + size -= IKCP_OVERHEAD; + + if ((long)size < (long)len || (int)len < 0) return -2; + + if (cmd != IKCP_CMD_PUSH && cmd != IKCP_CMD_ACK && + cmd != IKCP_CMD_WASK && cmd != IKCP_CMD_WINS) + return -3; + + kcp->rmt_wnd = wnd; + ikcp_parse_una(kcp, una); + ikcp_shrink_buf(kcp); + + if (cmd == IKCP_CMD_ACK) + { + if (_itimediff(kcp->current, ts) >= 0) + { + ikcp_update_ack(kcp, (int)_itimediff(kcp->current, ts)); + } + + ikcp_parse_ack(kcp, sn); + ikcp_shrink_buf(kcp); + if (flag == 0) + { + flag = 1; + maxack = sn; + latest_ts = ts; + } + else + { + if (_itimediff(sn, maxack) > 0) + { +#if !IKCP_FASTACK_CONSERVE + maxack = sn; + latest_ts = ts; +#else + if (_itimediff(ts, latest_ts) > 0) + { + maxack = sn; + latest_ts = ts; + } +#endif + } + } + } + else if (cmd == IKCP_CMD_PUSH) + { + if (_itimediff(sn, kcp->rcv_nxt + kcp->rcv_wnd) < 0) + { + ikcp_ack_push(kcp, sn, ts); + if (_itimediff(sn, kcp->rcv_nxt) >= 0) + { + seg = ikcp_segment_new(kcp, (int)len); + seg->conv = conv; + seg->cmd = cmd; + seg->frg = frg; + seg->wnd = wnd; + seg->ts = ts; + seg->sn = sn; + seg->una = una; + seg->len = len; + + if (len > 0) + { + memcpy(seg->data, data, (nuint)len); + } + + ikcp_parse_data(kcp, seg); + } + } + } + else if (cmd == IKCP_CMD_WASK) + { + // ready to send back IKCP_CMD_WINS in ikcp_flush + // tell remote my window size + kcp->probe |= IKCP_ASK_TELL; + } + else if (cmd == IKCP_CMD_WINS) + { + // do nothing + } + else + { + return -3; + } + + data += len; + size -= len; + } + + if (flag != 0) + { + ikcp_parse_fastack(kcp, maxack, latest_ts); + } + + if (_itimediff(kcp->snd_una, prev_una) > 0) + { + if (kcp->cwnd < kcp->rmt_wnd) + { + uint mss = kcp->mss; + if (kcp->cwnd < kcp->ssthresh) + { + kcp->cwnd++; + kcp->incr += mss; + } + else + { + if (kcp->incr < mss) kcp->incr = mss; + kcp->incr += (mss * mss) / kcp->incr + (mss / 16); + if ((kcp->cwnd + 1) * mss <= kcp->incr) + { + kcp->cwnd = (kcp->incr + mss - 1) / ((mss > 0) ? mss : 1); + } + } + + if (kcp->cwnd > kcp->rmt_wnd) + { + kcp->cwnd = kcp->rmt_wnd; + kcp->incr = kcp->rmt_wnd * mss; + } + } + } + + return 0; + } + + //--------------------------------------------------------------------- + // ikcp_encode_seg + //--------------------------------------------------------------------- + public static byte* ikcp_encode_seg(byte* ptr, IKCPSEG* seg) + { + ptr = ikcp_encode32u(ptr, seg->conv); + ptr = ikcp_encode8u(ptr, (byte)seg->cmd); + ptr = ikcp_encode8u(ptr, (byte)seg->frg); + ptr = ikcp_encode16u(ptr, (ushort)seg->wnd); + ptr = ikcp_encode32u(ptr, seg->ts); + ptr = ikcp_encode32u(ptr, seg->sn); + ptr = ikcp_encode32u(ptr, seg->una); + ptr = ikcp_encode32u(ptr, seg->len); + return ptr; + } + + public static int ikcp_wnd_unused(IKCPCB* kcp) + { + if (kcp->nrcv_que < kcp->rcv_wnd) + { + return (int)(kcp->rcv_wnd - kcp->nrcv_que); + } + + return 0; + } + + //--------------------------------------------------------------------- + // ikcp_flush + //--------------------------------------------------------------------- + public static void ikcp_flush(IKCPCB* kcp, byte* buffer, byte[] destination, KcpCallback output) + { + uint current = kcp->current; + byte* ptr = buffer; + int count, size, i; + uint resent, cwnd; + uint rtomin; + IQUEUEHEAD* p; + int change = 0; + int lost = 0; + IKCPSEG seg; + + // 'ikcp_update' haven't been called. + if (kcp->updated == 0) return; + + seg.conv = kcp->conv; + seg.cmd = IKCP_CMD_ACK; + seg.frg = 0; + seg.wnd = (uint)ikcp_wnd_unused(kcp); + seg.una = kcp->rcv_nxt; + seg.len = 0; + seg.sn = 0; + seg.ts = 0; + + // flush acknowledges + count = (int)kcp->ackcount; + for (i = 0; i < count; i++) + { + size = (int)(ptr - buffer); + if (size + (int)IKCP_OVERHEAD > (int)kcp->mtu) + { + ikcp_output(kcp, size, destination, output); + ptr = buffer; + } + + ikcp_ack_get(kcp, i, &seg.sn, &seg.ts); + ptr = ikcp_encode_seg(ptr, &seg); + } + + kcp->ackcount = 0; + + // probe window size (if remote window size equals zero) + if (kcp->rmt_wnd == 0) + { + if (kcp->probe_wait == 0) + { + kcp->probe_wait = IKCP_PROBE_INIT; + kcp->ts_probe = kcp->current + kcp->probe_wait; + } + else + { + if (_itimediff(kcp->current, kcp->ts_probe) >= 0) + { + if (kcp->probe_wait < IKCP_PROBE_INIT) + kcp->probe_wait = IKCP_PROBE_INIT; + kcp->probe_wait += kcp->probe_wait / 2; + if (kcp->probe_wait > IKCP_PROBE_LIMIT) + kcp->probe_wait = IKCP_PROBE_LIMIT; + kcp->ts_probe = kcp->current + kcp->probe_wait; + kcp->probe |= IKCP_ASK_SEND; + } + } + } + else + { + kcp->ts_probe = 0; + kcp->probe_wait = 0; + } + + // flush window probing commands + if ((kcp->probe & IKCP_ASK_SEND) != 0) + { + seg.cmd = IKCP_CMD_WASK; + size = (int)(ptr - buffer); + if (size + (int)IKCP_OVERHEAD > (int)kcp->mtu) + { + ikcp_output(kcp, size, destination, output); + ptr = buffer; + } + + ptr = ikcp_encode_seg(ptr, &seg); + } + + // flush window probing commands + if ((kcp->probe & IKCP_ASK_TELL) != 0) + { + seg.cmd = IKCP_CMD_WINS; + size = (int)(ptr - buffer); + if (size + (int)IKCP_OVERHEAD > (int)kcp->mtu) + { + ikcp_output(kcp, size, destination, output); + ptr = buffer; + } + + ptr = ikcp_encode_seg(ptr, &seg); + } + + kcp->probe = 0; + + // calculate window size + cwnd = _imin_(kcp->snd_wnd, kcp->rmt_wnd); + if (kcp->nocwnd == 0) cwnd = _imin_(kcp->cwnd, cwnd); + + // move data from snd_queue to snd_buf + while (_itimediff(kcp->snd_nxt, kcp->snd_una + cwnd) < 0) + { + IKCPSEG* newseg; + if (iqueue_is_empty(&kcp->snd_queue)) break; + + newseg = iqueue_entry(kcp->snd_queue.next); + + iqueue_del(&newseg->node); + iqueue_add_tail(&newseg->node, &kcp->snd_buf); + kcp->nsnd_que--; + kcp->nsnd_buf++; + + newseg->conv = kcp->conv; + newseg->cmd = IKCP_CMD_PUSH; + newseg->wnd = seg.wnd; + newseg->ts = current; + newseg->sn = kcp->snd_nxt++; + newseg->una = kcp->rcv_nxt; + newseg->resendts = current; + newseg->rto = (uint)kcp->rx_rto; + newseg->fastack = 0; + newseg->xmit = 0; + } + + // calculate resent + resent = (kcp->fastresend > 0) ? (uint)kcp->fastresend : 0xffffffff; + rtomin = (uint)((kcp->nodelay == 0) ? (kcp->rx_rto >> 3) : 0); + + // flush data segments + for (p = kcp->snd_buf.next; p != &kcp->snd_buf; p = p->next) + { + IKCPSEG* segment = iqueue_entry(p); + int needsend = 0; + if (segment->xmit == 0) + { + needsend = 1; + segment->xmit++; + segment->rto = (uint)kcp->rx_rto; + segment->resendts = current + segment->rto + rtomin; + } + else if (_itimediff(current, segment->resendts) >= 0) + { + needsend = 1; + segment->xmit++; + kcp->xmit++; + if (kcp->nodelay == 0) + { + segment->rto += _imax_(segment->rto, (uint)kcp->rx_rto); + } + else + { + int step = (kcp->nodelay < 2) ? ((int)(segment->rto)) : kcp->rx_rto; + segment->rto += (uint)(step / 2); + } + + segment->resendts = current + segment->rto; + lost = 1; + } + else if (segment->fastack >= resent) + { + if ((int)segment->xmit <= kcp->fastlimit || + kcp->fastlimit <= 0) + { + needsend = 1; + segment->xmit++; + segment->fastack = 0; + segment->resendts = current + segment->rto; + change++; + } + } + + if (needsend != 0) + { + int need; + segment->ts = current; + segment->wnd = seg.wnd; + segment->una = kcp->rcv_nxt; + + size = (int)(ptr - buffer); + need = (int)(IKCP_OVERHEAD + segment->len); + + if (size + need > (int)kcp->mtu) + { + ikcp_output(kcp, size, destination, output); + ptr = buffer; + } + + ptr = ikcp_encode_seg(ptr, segment); + + if (segment->len > 0) + { + memcpy(ptr, segment->data, (nuint)segment->len); + ptr += segment->len; + } + + if (segment->xmit >= kcp->dead_link) + { + kcp->state = unchecked((uint)(-1)); + } + } + } + + // flash remain segments + size = (int)(ptr - buffer); + if (size > 0) + { + ikcp_output(kcp, size, destination, output); + } + + // update ssthresh + if (change != 0) + { + uint inflight = kcp->snd_nxt - kcp->snd_una; + kcp->ssthresh = inflight / 2; + if (kcp->ssthresh < IKCP_THRESH_MIN) + kcp->ssthresh = IKCP_THRESH_MIN; + kcp->cwnd = kcp->ssthresh + resent; + kcp->incr = kcp->cwnd * kcp->mss; + } + + if (lost != 0) + { + kcp->ssthresh = cwnd / 2; + if (kcp->ssthresh < IKCP_THRESH_MIN) + kcp->ssthresh = IKCP_THRESH_MIN; + kcp->cwnd = 1; + kcp->incr = kcp->mss; + } + + if (kcp->cwnd < 1) + { + kcp->cwnd = 1; + kcp->incr = kcp->mss; + } + } + + public static void ikcp_update(IKCPCB* kcp, uint current, byte* buffer, byte[] destination, KcpCallback output) + { + int slap; + + kcp->current = current; + + if (kcp->updated == 0) + { + kcp->updated = 1; + kcp->ts_flush = kcp->current; + } + + slap = (int)_itimediff(kcp->current, kcp->ts_flush); + + if (slap >= 10000 || slap < -10000) + { + kcp->ts_flush = kcp->current; + slap = 0; + } + + if (slap >= 0) + { + kcp->ts_flush += kcp->interval; + if (_itimediff(kcp->current, kcp->ts_flush) >= 0) + { + kcp->ts_flush = kcp->current + kcp->interval; + } + + ikcp_flush(kcp, buffer, destination, output); + } + } + + //--------------------------------------------------------------------- + // Determine when should you invoke ikcp_update: + // returns when you should invoke ikcp_update in millisec, if there + // is no ikcp_input/_send calling. you can call ikcp_update in that + // time, instead of call update repeatly. + // Important to reduce unnacessary ikcp_update invoking. use it to + // schedule ikcp_update (eg. implementing an epoll-like mechanism, + // or optimize ikcp_update when handling massive kcp connections) + //--------------------------------------------------------------------- + public static uint ikcp_check(IKCPCB* kcp, uint current) + { + uint ts_flush = kcp->ts_flush; + int tm_flush = 0x7fffffff; + int tm_packet = 0x7fffffff; + uint minimal = 0; + IQUEUEHEAD* p; + + if (kcp->updated == 0) + { + return current; + } + + if (_itimediff(current, ts_flush) >= 10000 || + _itimediff(current, ts_flush) < -10000) + { + ts_flush = current; + } + + if (_itimediff(current, ts_flush) >= 0) + { + return current; + } + + tm_flush = (int)_itimediff(ts_flush, current); + + for (p = kcp->snd_buf.next; p != &kcp->snd_buf; p = p->next) + { + IKCPSEG* seg = iqueue_entry(p); + int diff = (int)_itimediff(seg->resendts, current); + if (diff <= 0) + { + return current; + } + + if (diff < tm_packet) tm_packet = diff; + } + + minimal = (uint)(tm_packet < tm_flush ? tm_packet : tm_flush); + if (minimal >= kcp->interval) minimal = kcp->interval; + + return current + minimal; + } + + public static int ikcp_setmtu(IKCPCB* kcp, int mtu, int reserved, ref byte[] buffer) + { + if (mtu < 50 || mtu < (int)IKCP_OVERHEAD) + return -1; + buffer = new byte[(reserved + mtu + IKCP_OVERHEAD) * 3]; + if (buffer == null) + return -2; + kcp->mtu = (uint)mtu; + kcp->mss = kcp->mtu - IKCP_OVERHEAD; + return 0; + } + + public static int ikcp_interval(IKCPCB* kcp, int interval) + { + if (interval > 5000) interval = 5000; + else if (interval < 10) interval = 10; + kcp->interval = (uint)interval; + return 0; + } + + public static int ikcp_nodelay(IKCPCB* kcp, int nodelay, int interval, int resend, int nc) + { + if (nodelay >= 0) + { + kcp->nodelay = (uint)nodelay; + if (nodelay != 0) + { + kcp->rx_minrto = (int)IKCP_RTO_NDL; + } + else + { + kcp->rx_minrto = (int)IKCP_RTO_MIN; + } + } + + if (interval >= 0) + { + if (interval > 5000) interval = 5000; + else if (interval < 10) interval = 10; + kcp->interval = (uint)interval; + } + + if (resend >= 0) + { + kcp->fastresend = resend; + } + + if (nc >= 0) + { + kcp->nocwnd = nc; + } + + return 0; + } + + public static int ikcp_wndsize(IKCPCB* kcp, int sndwnd, int rcvwnd) + { + if (kcp != null) + { + if (sndwnd > 0) + { + kcp->snd_wnd = (uint)sndwnd; + } + + if (rcvwnd > 0) + { + // must >= max fragment size + kcp->rcv_wnd = _imax_((uint)rcvwnd, IKCP_WND_RCV); + } + } + + return 0; + } + + public static int ikcp_waitsnd(IKCPCB* kcp) + { + return (int)(kcp->nsnd_buf + kcp->nsnd_que); + } + + // read conv + public static uint ikcp_getconv(void* ptr) + { + uint conv; + ikcp_decode32u((byte*)ptr, &conv); + return conv; + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/KCP/Base/define/system.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/KCP/Base/define/system.cs new file mode 100644 index 0000000..053b8c5 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/KCP/Base/define/system.cs @@ -0,0 +1,41 @@ +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +#pragma warning disable CS1591 + +// ReSharper disable ALL + +namespace kcp +{ + public static unsafe partial class KCP + { + public static void* malloc(nuint size) + { +#if NET6_0_OR_GREATER + return NativeMemory.Alloc((nuint)size); +#else + return (void*)Marshal.AllocHGlobal((nint)size); +#endif + } + + public static void free(void* memory) + { +#if NET6_0_OR_GREATER + NativeMemory.Free(memory); +#else + Marshal.FreeHGlobal((nint)memory); +#endif + } + + public static void memcpy(void* dst, void* src, nuint size) => Unsafe.CopyBlockUnaligned(dst, src, (uint)size); + + public static void memset(void* dst, byte val, nuint size) => Unsafe.InitBlockUnaligned(dst, val, (uint)size); + + [Conditional("DEBUG")] + public static void assert(bool condition) => Debug.Assert(condition); + + public static void abort() => Environment.Exit(-1); + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/KCP/Base/include/kcp.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/KCP/Base/include/kcp.cs new file mode 100644 index 0000000..98a83d9 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/KCP/Base/include/kcp.cs @@ -0,0 +1,127 @@ +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +#pragma warning disable CS1591 + +// ReSharper disable ALL + +namespace kcp +{ + [StructLayout(LayoutKind.Sequential)] + public unsafe struct IQUEUEHEAD + { + public IQUEUEHEAD* next; + public IQUEUEHEAD* prev; + } + + public static unsafe partial class KCP + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void iqueue_init(IQUEUEHEAD* head) + { + head->next = head; + head->prev = head; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T* iqueue_entry(IQUEUEHEAD* ptr) where T : unmanaged => ((T*)(((byte*)((T*)ptr)))); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void iqueue_add(IQUEUEHEAD* node, IQUEUEHEAD* head) + { + node->prev = head; + node->next = head->next; + head->next->prev = node; + head->next = node; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void iqueue_add_tail(IQUEUEHEAD* node, IQUEUEHEAD* head) + { + node->prev = head->prev; + node->next = head; + head->prev->next = node; + head->prev = node; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void iqueue_del(IQUEUEHEAD* entry) + { + entry->next->prev = entry->prev; + entry->prev->next = entry->next; + entry->next = (IQUEUEHEAD*)0; + entry->prev = (IQUEUEHEAD*)0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void iqueue_del_init(IQUEUEHEAD* entry) + { + iqueue_del(entry); + iqueue_init(entry); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool iqueue_is_empty(IQUEUEHEAD* entry) => entry == entry->next; + } + + [StructLayout(LayoutKind.Sequential)] + public unsafe struct IKCPSEG + { + public IQUEUEHEAD node; + public uint conv; + public uint cmd; + public uint frg; + public uint wnd; + public uint ts; + public uint sn; + public uint una; + public uint len; + public uint resendts; + public uint rto; + public uint fastack; + public uint xmit; + public fixed byte data[1]; + } + + [StructLayout(LayoutKind.Sequential)] + public unsafe struct IKCPCB + { + public uint conv, mtu, mss, state; + public uint snd_una, snd_nxt, rcv_nxt; + public uint ts_recent, ts_lastack, ssthresh; + public int rx_rttval, rx_srtt, rx_rto, rx_minrto; + public uint snd_wnd, rcv_wnd, rmt_wnd, cwnd, probe; + public uint current, interval, ts_flush, xmit; + public uint nrcv_buf, nsnd_buf; + public uint nrcv_que, nsnd_que; + public uint nodelay, updated; + public uint ts_probe, probe_wait; + public uint dead_link, incr; + public IQUEUEHEAD snd_queue; + public IQUEUEHEAD rcv_queue; + public IQUEUEHEAD snd_buf; + public IQUEUEHEAD rcv_buf; + public uint* acklist; + public uint ackcount; + public uint ackblock; + public int fastresend; + public int fastlimit; + public int nocwnd, stream; + } + + public static partial class KCP + { + public const uint IKCP_LOG_OUTPUT = 1; + public const uint IKCP_LOG_INPUT = 2; + public const uint IKCP_LOG_SEND = 4; + public const uint IKCP_LOG_RECV = 8; + public const uint IKCP_LOG_IN_DATA = 16; + public const uint IKCP_LOG_IN_ACK = 32; + public const uint IKCP_LOG_IN_PROBE = 64; + public const uint IKCP_LOG_IN_WINS = 128; + public const uint IKCP_LOG_OUT_DATA = 256; + public const uint IKCP_LOG_OUT_ACK = 512; + public const uint IKCP_LOG_OUT_PROBE = 1024; + public const uint IKCP_LOG_OUT_WINS = 2048; + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/KCP/Client/KCPClientNetwork.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/KCP/Client/KCPClientNetwork.cs new file mode 100644 index 0000000..a400c34 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/KCP/Client/KCPClientNetwork.cs @@ -0,0 +1,697 @@ +#if !FANTASY_WEBGL +using System; +using System.Buffers; +using System.Collections.Generic; +using System.IO; +using System.IO.Pipelines; +using System.Net; +using System.Net.Sockets; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Security.Cryptography; +using System.Threading; +using Fantasy.Async; +using Fantasy.Entitas.Interface; +using Fantasy.Helper; +using Fantasy.Network.Interface; +using Fantasy.PacketParser; +using Fantasy.Serialize; +using KCP; +// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract +// ReSharper disable PossibleNullReferenceException +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. + +#pragma warning disable CS8622 // Nullability of reference types in type of parameter doesn't match the target delegate (possibly because of nullability attributes). + +#pragma warning disable CS8602 // Dereference of a possibly null reference. +namespace Fantasy.Network.KCP +{ + public sealed class KCPClientNetworkUpdateSystem : UpdateSystem + { + protected override void Update(KCPClientNetwork self) + { + self.CheckUpdate(); + } + } + public sealed class KCPClientNetwork : AClientNetwork + { + private Kcp _kcp; + private Socket _socket; + private int _maxSndWnd; + private long _startTime; + private bool _isConnected; + private bool _isDisconnect; + private uint _updateMinTime; + private bool _isInnerDispose; + private long _connectTimeoutId; + private bool _allowWraparound = true; + private IPEndPoint _remoteAddress; + private BufferPacketParser _packetParser; + private readonly Pipe _pipe = new Pipe(); + private readonly byte[] _sendBuff = new byte[5]; + private readonly byte[] _receiveBuffer = new byte[Packet.PacketBodyMaxLength + 20]; + private readonly List _updateTimeOutTime = new List(); + private readonly SortedSet _updateTimer = new SortedSet(); + private readonly SocketAsyncEventArgs _connectEventArgs = new SocketAsyncEventArgs(); + private readonly Queue _messageCache = new Queue(); + private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); +#if FANTASY_UNITY + private readonly EndPoint _ipEndPoint = new IPEndPoint(IPAddress.Any, 0); +#endif + private event Action OnConnectFail; + private event Action OnConnectComplete; + private event Action OnConnectDisconnect; + public uint ChannelId { get; private set; } + private uint TimeNow => (uint) (TimeHelper.Now - _startTime); + + public void Initialize(NetworkTarget networkTarget) + { + base.Initialize(NetworkType.Client, NetworkProtocolType.KCP, networkTarget); + _packetParser = PacketParserFactory.CreateClientBufferPacket(this); + } + + public override void Dispose() + { + if (IsDisposed || _isInnerDispose) + { + return; + } + + try + { + _isInnerDispose = true; + + if (!_isDisconnect) + { + SendDisconnect(); + } + + ClearConnectTimeout(); + + if (!_cancellationTokenSource.IsCancellationRequested) + { + try + { + _cancellationTokenSource.Cancel(); + } + catch (OperationCanceledException) + { + // 通常情况下,此处的异常可以忽略 + } + } + + OnConnectDisconnect?.Invoke(); + _kcp.Dispose(); + + if (_socket.Connected) + { + _socket.Close(); + } + + _packetParser.Dispose(); + ChannelId = 0; + _isConnected = false; + _messageCache.Clear(); + } + catch (Exception e) + { + Log.Error(e); + } + finally + { + base.Dispose(); + } + } + + #region Connect + + public override Session Connect(string remoteAddress, Action onConnectComplete, Action onConnectFail, Action onConnectDisconnect, bool isHttps, int connectTimeout = 5000) + { + if (IsInit) + { + throw new NotSupportedException($"KCPClientNetwork Has already been initialized. If you want to call Connect again, please re instantiate it."); + } + + IsInit = true; + _startTime = TimeHelper.Now; + ChannelId = CreateChannelId(); + _remoteAddress = NetworkHelper.GetIPEndPoint(remoteAddress); + OnConnectFail = onConnectFail; + OnConnectComplete = onConnectComplete; + OnConnectDisconnect = onConnectDisconnect; + _connectEventArgs.Completed += OnConnectSocketCompleted; + _connectTimeoutId = Scene.TimerComponent.Net.OnceTimer(connectTimeout, () => + { + OnConnectFail?.Invoke(); + Dispose(); + }); + _connectEventArgs.RemoteEndPoint = _remoteAddress; + _socket = new Socket(_remoteAddress.AddressFamily, SocketType.Dgram, ProtocolType.Udp); + _socket.Blocking = false; + _socket.SetSocketBufferToOsLimit(); + _socket.SetSioUdpConnReset(); + _socket.Bind(new IPEndPoint(IPAddress.Any, 0)); + _kcp = KCPFactory.Create(NetworkTarget, ChannelId, KcpSpanCallback, out var kcpSettings); + _maxSndWnd = kcpSettings.MaxSendWindowSize; + + if (!_socket.ConnectAsync(_connectEventArgs)) + { + try + { + OnReceiveSocketComplete(); + } + catch (Exception e) + { + Log.Error(e); + OnConnectFail?.Invoke(); + } + } + + Session = Session.Create(this, _remoteAddress); + return Session; + } + + private void OnConnectSocketCompleted(object sender, SocketAsyncEventArgs asyncEventArgs) + { + if (_cancellationTokenSource.IsCancellationRequested) + { + return; + } + + if (asyncEventArgs.LastOperation == SocketAsyncOperation.Connect) + { + if (asyncEventArgs.SocketError == SocketError.Success) + { + Scene.ThreadSynchronizationContext.Post(OnReceiveSocketComplete); + } + else + { + Scene.ThreadSynchronizationContext.Post(() => + { + OnConnectFail?.Invoke(); + Dispose(); + }); + } + } + } + + private void OnReceiveSocketComplete() + { + SendRequestConnection(); + ReadPipeDataAsync().Coroutine(); + ReceiveSocketAsync().Coroutine(); + } + + #endregion + + #region ReceiveSocket + + private async FTask ReceiveSocketAsync() + { + while (!_cancellationTokenSource.IsCancellationRequested) + { + try + { + var memory = _pipe.Writer.GetMemory(8192); +#if FANTASY_UNITY + MemoryMarshal.TryGetArray(memory, out ArraySegment arraySegment); + var result = await _socket.ReceiveFromAsync(arraySegment, SocketFlags.None, _ipEndPoint); + _pipe.Writer.Advance(result.ReceivedBytes); + await _pipe.Writer.FlushAsync(); +#else + var result = await _socket.ReceiveAsync(memory, SocketFlags.None, _cancellationTokenSource.Token); + _pipe.Writer.Advance(result); + await _pipe.Writer.FlushAsync(); +#endif + } + catch (SocketException) + { + Dispose(); + break; + } + catch (OperationCanceledException) + { + break; + } + catch (ObjectDisposedException) + { + Dispose(); + break; + } + catch (Exception ex) + { + Log.Error($"Unexpected exception: {ex.Message}"); + } + } + + await _pipe.Writer.CompleteAsync(); + } + + #endregion + + #region ReceivePipeData + + private async FTask ReadPipeDataAsync() + { + var pipeReader = _pipe.Reader; + while (!_cancellationTokenSource.IsCancellationRequested) + { + ReadResult result = default; + + try + { + result = await pipeReader.ReadAsync(_cancellationTokenSource.Token); + } + catch (OperationCanceledException) + { + // 出现这个异常表示取消了_cancellationTokenSource。一般Channel断开会取消。 + break; + } + + var buffer = result.Buffer; + var consumed = buffer.Start; + var examined = buffer.End; + + while (TryReadMessage(ref buffer, out var header, out var channelId, out var message)) + { + ReceiveData(ref header, ref channelId, ref message); + consumed = buffer.Start; + } + + if (result.IsCompleted) + { + break; + } + + pipeReader.AdvanceTo(consumed, examined); + } + } + + private unsafe bool TryReadMessage(ref ReadOnlySequence buffer, out KcpHeader header, out uint channelId, out ReadOnlyMemory message) + { + if (buffer.Length < 5) + { + channelId = 0; + message = default; + header = KcpHeader.None; + if (buffer.Length > 0) + { + buffer = buffer.Slice(buffer.Length); + } + return false; + } + + var readOnlyMemory = buffer.First; + + if (MemoryMarshal.TryGetArray(readOnlyMemory, out var arraySegment)) + { + fixed (byte* bytePointer = &arraySegment.Array[arraySegment.Offset]) + { + header = (KcpHeader)bytePointer[0]; + channelId = Unsafe.ReadUnaligned(ref bytePointer[1]); + } + } + else + { + // 如果无法获取数组段,回退到安全代码来执行。这种情况几乎不会发生、为了保险还是写一下了。 + var firstSpan = readOnlyMemory.Span; + header = (KcpHeader)firstSpan[0]; + channelId = MemoryMarshal.Read(firstSpan.Slice(1, 4)); + + } + + message = readOnlyMemory.Slice(5); + buffer = buffer.Slice(readOnlyMemory.Length); + return true; + } + + private void ReceiveData(ref KcpHeader header, ref uint channelId, ref ReadOnlyMemory buffer) + { + switch (header) + { + // 发送握手给服务器 + case KcpHeader.RepeatChannelId: + { + // 到这里是客户端的channelId再服务器上已经存在、需要重新生成一个再次尝试连接 + ChannelId = CreateChannelId(); + SendRequestConnection(); + break; + } + // 收到服务器发送会来的确认握手 + case KcpHeader.WaitConfirmConnection: + { + if (channelId != ChannelId) + { + break; + } + + ClearConnectTimeout(); + SendConfirmConnection(); + OnConnectComplete?.Invoke(); + _isConnected = true; + while (_messageCache.TryDequeue(out var memoryStream)) + { + SendMemoryStream(memoryStream); + } + break; + } + // 收到服务器发送的消息 + case KcpHeader.ReceiveData: + { + if (buffer.Length == 5) + { + Log.Warning($"KCP Server KcpHeader.Data buffer.Length == 5"); + break; + } + + if (channelId != ChannelId) + { + break; + } + + Input(buffer); + break; + } + // 接收到服务器的断开连接消息 + case KcpHeader.Disconnect: + { + if (channelId != ChannelId) + { + break; + } + + _isDisconnect = true; + Dispose(); + break; + } + } + } + + private void Input(ReadOnlyMemory buffer) + { + _kcp.Input(buffer.Span); + AddToUpdate(0); + + while (!_cancellationTokenSource.IsCancellationRequested) + { + try + { + var peekSize = _kcp.PeekSize(); + + if (peekSize < 0) + { + return; + } + + var receiveCount = _kcp.Receive(_receiveBuffer.AsSpan(0, peekSize)); + + if (receiveCount != peekSize) + { + return; + } + + if (!_packetParser.UnPack(_receiveBuffer, ref receiveCount, out var packInfo)) + { + continue; + } + + Session.Receive(packInfo); + } + catch (ScanException e) + { + Log.Debug($"RemoteAddress:{_remoteAddress} \n{e}"); + Dispose(); + } + catch (Exception e) + { + Log.Error(e); + } + } + } + + #endregion + + #region Update + + public void CheckUpdate() + { + var nowTime = TimeNow; + _allowWraparound = nowTime < _updateMinTime; + + if (IsTimeGreaterThan(nowTime, _updateMinTime) && _updateTimer.Count > 0) + { + foreach (var timeId in _updateTimer) + { + if (IsTimeGreaterThan(timeId, nowTime)) + { + _updateMinTime = timeId; + break; + } + + _updateTimeOutTime.Add(timeId); + } + + foreach (var timeId in _updateTimeOutTime) + { + _updateTimer.Remove(timeId); + KcpUpdate(); + } + + _updateTimeOutTime.Clear(); + } + + _allowWraparound = true; + } + + private void AddToUpdate(uint tillTime) + { + if (tillTime == 0) + { + KcpUpdate(); + return; + } + + if (IsTimeGreaterThan(_updateMinTime, tillTime) || _updateMinTime == 0) + { + _updateMinTime = tillTime; + } + + _updateTimer.Add(tillTime); + } + + private void KcpUpdate() + { + var nowTime = TimeNow; + + try + { + _kcp.Update(nowTime); + } + catch (Exception e) + { + Log.Error(e); + } + + AddToUpdate(_kcp.Check(nowTime)); + } + + private const uint HalfMaxUint = uint.MaxValue / 2; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool IsTimeGreaterThan(uint timeId, uint nowTime) + { + if (!_allowWraparound) + { + return timeId > nowTime; + } + var diff = timeId - nowTime; + // 如果 diff 的值在 [0, HalfMaxUint] 范围内,说明 timeId 是在 nowTime 之后或相等。 + // 如果 diff 的值在 (HalfMaxUint, uint.MaxValue] 范围内,说明 timeId 是在 nowTime 之前(时间回绕的情况)。 + return diff < HalfMaxUint || diff == HalfMaxUint; + } + + #endregion + + #region Send + + private const byte KcpHeaderDisconnect = (byte)KcpHeader.Disconnect; + private const byte KcpHeaderReceiveData = (byte)KcpHeader.ReceiveData; + private const byte KcpHeaderRequestConnection = (byte)KcpHeader.RequestConnection; + private const byte KcpHeaderConfirmConnection = (byte)KcpHeader.ConfirmConnection; + + public override void Send(uint rpcId, long routeId, MemoryStreamBuffer memoryStream, IMessage message) + { + if (_cancellationTokenSource.IsCancellationRequested) + { + return; + } + + var buffer = _packetParser.Pack(ref rpcId, ref routeId, memoryStream, message); + + if (!_isConnected) + { + _messageCache.Enqueue(buffer); + return; + } + + SendMemoryStream(buffer); + } + + private void SendMemoryStream(MemoryStreamBuffer memoryStream) + { + if (_kcp.WaitSendCount > _maxSndWnd) + { + // 检查等待发送的消息,如果超出两倍窗口大小,KCP作者给的建议是要断开连接 + Log.Warning($"ERR_KcpWaitSendSizeTooLarge {_kcp.WaitSendCount} > {_maxSndWnd}"); + Dispose(); + return; + } + + try + { + _kcp.Send(memoryStream.GetBuffer().AsSpan(0, (int)memoryStream.Position)); + AddToUpdate(0); + } + finally + { + if (memoryStream.MemoryStreamBufferSource == MemoryStreamBufferSource.Pack) + { + MemoryStreamBufferPool.ReturnMemoryStream(memoryStream); + } + } + } + + private unsafe void SendRequestConnection() + { + try + { + fixed (byte* p = _sendBuff) + { + p[0] = KcpHeaderRequestConnection; + *(uint*)(p + 1) = ChannelId; + } + + SendAsync(_sendBuff, 0, 5); + } + catch (Exception e) + { + Log.Error(e); + } + } + + private unsafe void SendConfirmConnection() + { + try + { + fixed (byte* p = _sendBuff) + { + p[0] = KcpHeaderConfirmConnection; + *(uint*)(p + 1) = ChannelId; + } + + SendAsync(_sendBuff, 0, 5); + } + catch (Exception e) + { + Log.Error(e); + } + } + + private unsafe void SendDisconnect() + { + try + { + fixed (byte* p = _sendBuff) + { + p[0] = KcpHeaderDisconnect; + *(uint*)(p + 1) = ChannelId; + } + + SendAsync(_sendBuff, 0, 5); + } + catch (Exception e) + { + Log.Error(e); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void SendAsync(byte[] buffer, int offset, int count) + { + try + { + _socket.Send(new ArraySegment(buffer, offset, count), SocketFlags.None); + } + catch (ArgumentException ex) + { + Log.Error($"ArgumentException: {ex.Message}"); // 处理参数错误 + } + catch (SocketException) + { + //Log.Error($"SocketException: {ex.Message}"); // 处理网络错误 + Dispose(); + } + catch (ObjectDisposedException ex) + { + Log.Error($"ObjectDisposedException: {ex.Message}"); // 处理套接字已关闭的情况 + Dispose(); + } + catch (InvalidOperationException ex) + { + Log.Error($"InvalidOperationException: {ex.Message}"); // 处理无效操作 + } + catch (Exception ex) + { + Log.Error($"Exception: {ex.Message}"); // 捕获其他异常 + } + } + + private unsafe void KcpSpanCallback(byte[] buffer, int count) + { + if (IsDisposed) + { + return; + } + + if (count == 0) + { + throw new Exception("KcpOutput count 0"); + } + + fixed (byte* p = buffer) + { + p[0] = KcpHeaderReceiveData; + *(uint*)(p + 1) = ChannelId; + } + + SendAsync(buffer, 0, count + 5); + } + + #endregion + + public override void RemoveChannel(uint channelId) + { + Dispose(); + } + + private void ClearConnectTimeout() + { + if (_connectTimeoutId == 0) + { + return; + } + + Scene?.TimerComponent?.Net.Remove(ref _connectTimeoutId); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static unsafe uint CreateChannelId() + { + uint value; + RandomNumberGenerator.Fill(MemoryMarshal.CreateSpan(ref *(byte*)&value, 4)); + return 0xC0000000 | (value & int.MaxValue); + } + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/KCP/KCPSettings.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/KCP/KCPSettings.cs new file mode 100644 index 0000000..2a070a9 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/KCP/KCPSettings.cs @@ -0,0 +1,91 @@ +#if !FANTASY_WEBGL +using System; +using KCP; + +#pragma warning disable CS1591 +namespace Fantasy.Network.KCP +{ + public class KCPSettings + { + public int Mtu { get; private set; } + public int SendWindowSize { get; private set; } + public int ReceiveWindowSize { get; private set; } + public int MaxSendWindowSize { get; private set; } + + public static KCPSettings Create(NetworkTarget networkTarget) + { + var settings = new KCPSettings(); + + switch (networkTarget) + { + case NetworkTarget.Outer: + { + // 外网设置470的原因: + // 1、mtu设置过大有可能路由器过滤掉 + // 2、降低 mtu 到 470,同样数据虽然会发更多的包,但是小包在路由层优先级更高 + settings.Mtu = 470; +#if FANTASY_NET + settings.SendWindowSize = 8192; + settings.ReceiveWindowSize = 8192; + settings.MaxSendWindowSize = 8192 * 8192 * 7; +#endif +#if FANTASY_UNITY || FANTASY_CONSOLE + settings.SendWindowSize = 512; + settings.ReceiveWindowSize = 512; + settings.MaxSendWindowSize = 512 * 512 * 7; +#endif + + break; + } +#if FANTASY_NET + case NetworkTarget.Inner: + { + // 内网设置1400的原因 + // 1、一般都是同一台服务器来运行多个进程来处理 + // 2、内网每个进程跟其他进程只有一个通道进行发送、所以发送的数量会比较大 + // 3、如果不把窗口设置大点、会出现消息滞后。 + // 4、因为内网发送的可不只是外网转发数据、还有可能是其他进程的通讯 + settings.Mtu = 1200; + settings.SendWindowSize = 8192; + settings.ReceiveWindowSize = 8192; + settings.MaxSendWindowSize = 8192 * 8192 * 7; + break; + } +#endif + default: + { + throw new NotSupportedException($"KCPServerNetwork NotSupported NetworkType:{networkTarget}"); + } + } + + return settings; + } + } + + public static class KCPFactory + { + public const int FANTASY_KCP_RESERVED_HEAD = 5; + + public static Kcp Create(NetworkTarget networkTarget, uint conv, KcpCallback output, out KCPSettings kcpSettings) + { + var kcp = new Kcp(conv, output, FANTASY_KCP_RESERVED_HEAD); + kcpSettings = KCPSettings.Create(networkTarget); + kcp.SetNoDelay(1, 5, 2, 1); + kcp.SetWindowSize(kcpSettings.SendWindowSize, kcpSettings.ReceiveWindowSize); + kcp.SetMtu(kcpSettings.Mtu); + kcp.SetMinrto(30); + return kcp; + } + + public static Kcp Create(KCPSettings kcpSettings, uint conv, KcpCallback output) + { + var kcp = new Kcp(conv, output, FANTASY_KCP_RESERVED_HEAD); + kcp.SetNoDelay(1, 5, 2, 1); + kcp.SetWindowSize(kcpSettings.SendWindowSize, kcpSettings.ReceiveWindowSize); + kcp.SetMtu(kcpSettings.Mtu); + kcp.SetMinrto(30); + return kcp; + } + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/KCP/KcpHeader.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/KCP/KcpHeader.cs new file mode 100644 index 0000000..28ddace --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/KCP/KcpHeader.cs @@ -0,0 +1,14 @@ +namespace Fantasy.Network.KCP +#pragma warning disable CS1591 +{ + public enum KcpHeader : byte + { + None = 0x00, + RequestConnection = 0x01, + WaitConfirmConnection = 0x02, + ConfirmConnection = 0x03, + RepeatChannelId = 0x04, + ReceiveData = 0x06, + Disconnect = 0x07 + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/KCP/Server/KCPServerNetworkByArrayPool.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/KCP/Server/KCPServerNetworkByArrayPool.cs new file mode 100644 index 0000000..0fb4e29 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/KCP/Server/KCPServerNetworkByArrayPool.cs @@ -0,0 +1,575 @@ +// #if FANTASY_NET +// using System.Buffers; +// using System.Net; +// using System.Net.Sockets; +// using System.Runtime.CompilerServices; +// using System.Runtime.InteropServices; +// using Fantasy.Async; +// using Fantasy.DataStructure.Collection; +// using Fantasy.Entitas.Interface; +// using Fantasy.Helper; +// using Fantasy.Network.Interface; +// #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +// +// // ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract +// #pragma warning disable CS8604 // Possible null reference argument. +// #pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type. +// #pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. +// #pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. +// #pragma warning disable CS8602 // Dereference of a possibly null reference. +// +// #pragma warning disable CS8622 // Nullability of reference types in type of parameter doesn't match the target delegate (possibly because of nullability attributes). +// +// namespace Fantasy.Network.KCP +// { +// public sealed class KCPServerNetworkUpdateSystem : UpdateSystem +// { +// protected override void Update(KCPServerNetwork self) +// { +// self.Update(); +// } +// } +// +// public struct PendingConnection +// { +// public readonly uint ChannelId; +// public readonly uint TimeOutId; +// public readonly IPEndPoint RemoteEndPoint; +// +// public PendingConnection(uint channelId, IPEndPoint remoteEndPoint, uint time) +// { +// ChannelId = channelId; +// RemoteEndPoint = remoteEndPoint; +// TimeOutId = time + 10 * 1000; // 设置10秒超时,如果10秒内没有确认连接则删除。 +// } +// } +// +// public sealed class KCPServerNetwork : ANetwork +// { +// private Socket _socket; +// private Thread _thread; +// private long _startTime; +// private uint _updateMinTime; +// private uint _pendingMinTime; +// private bool _allowWraparound = true; +// +// private readonly byte[] _sendBuff = new byte[5]; +// private readonly List _pendingTimeOutTime = new List(); +// private readonly HashSet _updateChannels = new HashSet(); +// private readonly List _updateTimeOutTime = new List(); +// private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); +// private readonly SortedOneToManyList _updateTimer = new SortedOneToManyList(); +// +// private readonly Dictionary _pendingConnection = new Dictionary(); +// private readonly SortedOneToManyList _pendingConnectionTimeOut = new SortedOneToManyList(); +// private readonly Dictionary _connectionChannel = new Dictionary(); +// +// public KCPSettings Settings { get; private set; } +// +// private uint TimeNow => (uint)(TimeHelper.Now - _startTime); +// +// public void Initialize(NetworkTarget networkTarget, IPEndPoint address) +// { +// _startTime = TimeHelper.Now; +// Settings = KCPSettings.Create(networkTarget); +// base.Initialize(NetworkType.Server, NetworkProtocolType.KCP, networkTarget); +// _socket = new Socket(address.AddressFamily, SocketType.Dgram, ProtocolType.Udp); +// _socket.Blocking = false; +// _socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, false); +// if (address.AddressFamily == AddressFamily.InterNetworkV6) +// { +// _socket.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.IPv6Only, false); +// } +// +// _socket.Blocking = false; +// _socket.Bind(address); +// _socket.SetSocketBufferToOsLimit(); +// _socket.SetSioUdpConnReset(); +// _thread = new Thread(() => +// { +// ReceiveSocketAsync().Coroutine(); +// }); +// _thread.Start(); +// Log.Info($"SceneConfigId = {Scene.SceneConfigId} networkTarget = {networkTarget.ToString()} KCPServer Listen {address}"); +// } +// +// public override void Dispose() +// { +// if (IsDisposed) +// { +// return; +// } +// +// try +// { +// if (!_cancellationTokenSource.IsCancellationRequested) +// { +// try +// { +// _cancellationTokenSource.Cancel(); +// } +// catch (OperationCanceledException) +// { +// // 通常情况下,此处的异常可以忽略 +// } +// } +// +// foreach (var (_, channel) in _connectionChannel.ToArray()) +// { +// channel.Dispose(); +// } +// +// _connectionChannel.Clear(); +// _pendingConnection.Clear(); +// +// if (_socket != null) +// { +// _socket.Dispose(); +// _socket = null; +// } +// } +// catch (Exception e) +// { +// Log.Error(e); +// } +// finally +// { +// base.Dispose(); +// } +// } +// +// #region ReceiveSocket +// +// private const int BufferSize = 8192; +// private static readonly ArrayPool BufferPool = ArrayPool.Shared; +// private async FTask ReceiveSocketAsync() +// { +// var sceneThreadSynchronizationContext = Scene.ThreadSynchronizationContext; +// var remoteEndPoint = new IPEndPoint(IPAddress.Any, 0); +// +// while (!_cancellationTokenSource.IsCancellationRequested) +// { +// try +// { +// IPEndPoint receiveRemoteEndPoint = null; +// var buffer = BufferPool.Rent(BufferSize); +// var socketReceiveFromResult = await _socket.ReceiveFromAsync(buffer, SocketFlags.None, remoteEndPoint, _cancellationTokenSource.Token); +// var receivedBytes = socketReceiveFromResult.ReceivedBytes; +// +// switch (receivedBytes) +// { +// case 5: +// { +// switch ((KcpHeader)buffer[0]) +// { +// case KcpHeader.RequestConnection: +// case KcpHeader.ConfirmConnection: +// { +// receiveRemoteEndPoint = socketReceiveFromResult.RemoteEndPoint.Clone(); +// break; +// } +// } +// +// break; +// } +// case < 5: +// { +// continue; +// } +// } +// +// sceneThreadSynchronizationContext.Post(() => +// { +// ReceiveDataAsync(buffer, receivedBytes, receiveRemoteEndPoint); +// }); +// } +// catch (SocketException ex) +// { +// Log.Error($"Socket exception: {ex.Message}"); +// Dispose(); +// break; +// } +// catch (OperationCanceledException) +// { +// break; +// } +// catch (ObjectDisposedException) +// { +// Dispose(); +// break; +// } +// catch (Exception ex) +// { +// Log.Error($"Unexpected exception: {ex.Message}"); +// } +// } +// } +// +// #endregion +// +// #region ReceivePipeData +// +// private void ReceiveDataAsync(byte[] buffer, int receivedBytes, IPEndPoint remoteEndPoint) +// { +// try +// { +// var header = (KcpHeader)buffer[0]; +// var channelId = BitConverter.ToUInt32(buffer, 1); +// +// switch (header) +// { +// // 客户端请求建立KCP连接 +// case KcpHeader.RequestConnection: +// { +// if (_pendingConnection.TryGetValue(channelId, out var pendingConnection)) +// { +// if (!remoteEndPoint.IPEndPointEquals(pendingConnection.RemoteEndPoint)) +// { +// // 重复通道ID,向客户端发送重复通道ID消息 +// SendRepeatChannelId(ref channelId, remoteEndPoint); +// } +// +// break; +// } +// +// if (_connectionChannel.ContainsKey(channelId)) +// { +// // 已存在的通道ID,向客户端发送重复通道ID消息 +// SendRepeatChannelId(ref channelId, remoteEndPoint); +// break; +// } +// +// AddPendingConnection(ref channelId, remoteEndPoint); +// break; +// } +// // 客户端确认建立KCP连接 +// case KcpHeader.ConfirmConnection: +// { +// if (!ConfirmPendingConnection(ref channelId, remoteEndPoint)) +// { +// break; +// } +// +// AddConnection(ref channelId, remoteEndPoint); +// break; +// } +// // 接收KCP的数据 +// case KcpHeader.ReceiveData: +// { +// if (buffer.Length == 5) +// { +// Log.Warning($"KCP Server KcpHeader.Data buffer.Length == 5"); +// break; +// } +// +// if (_connectionChannel.TryGetValue(channelId, out var channel)) +// { +// channel.Input(buffer.AsMemory().Slice(5, receivedBytes - 5)); +// } +// +// break; +// } +// // 断开KCP连接 +// case KcpHeader.Disconnect: +// { +// // 断开不需要清楚PendingConnection让ClearPendingConnection自动清楚就可以了,并且不一定有Pending。 +// RemoveChannel(channelId); +// break; +// } +// } +// } +// finally +// { +// BufferPool.Return(buffer); +// } +// } +// +// #endregion +// +// #region Update +// +// public void Update() +// { +// var timeNow = TimeNow; +// _allowWraparound = timeNow < _updateMinTime; +// CheckUpdateTimerOut(ref timeNow); +// UpdateChannel(ref timeNow); +// PendingTimerOut(ref timeNow); +// _allowWraparound = true; +// } +// +// private void CheckUpdateTimerOut(ref uint nowTime) +// { +// if (_updateTimer.Count == 0) +// { +// return; +// } +// +// if (IsTimeGreaterThan(_updateMinTime, nowTime)) +// { +// return; +// } +// +// _updateTimeOutTime.Clear(); +// +// foreach (var kv in _updateTimer) +// { +// var timeId = kv.Key; +// +// if (IsTimeGreaterThan(timeId, nowTime)) +// { +// _updateMinTime = timeId; +// break; +// } +// +// _updateTimeOutTime.Add(timeId); +// } +// +// foreach (var timeId in _updateTimeOutTime) +// { +// foreach (var channelId in _updateTimer[timeId]) +// { +// _updateChannels.Add(channelId); +// } +// +// _updateTimer.RemoveKey(timeId); +// } +// } +// +// private void UpdateChannel(ref uint timeNow) +// { +// foreach (var channelId in _updateChannels) +// { +// if (!_connectionChannel.TryGetValue(channelId, out var channel)) +// { +// continue; +// } +// +// if (channel.IsDisposed) +// { +// _connectionChannel.Remove(channelId); +// continue; +// } +// +// channel.Kcp.Update(timeNow); +// AddUpdateChannel(channelId, channel.Kcp.Check(timeNow)); +// } +// +// _updateChannels.Clear(); +// } +// +// private void PendingTimerOut(ref uint timeNow) +// { +// if (_pendingConnectionTimeOut.Count == 0) +// { +// return; +// } +// +// if (IsTimeGreaterThan(_pendingMinTime, timeNow)) +// { +// return; +// } +// +// _pendingTimeOutTime.Clear(); +// +// foreach (var kv in _pendingConnectionTimeOut) +// { +// var timeId = kv.Key; +// +// if (IsTimeGreaterThan(timeId, timeNow)) +// { +// _pendingMinTime = timeId; +// break; +// } +// +// _pendingTimeOutTime.Add(timeId); +// } +// +// foreach (var timeId in _pendingTimeOutTime) +// { +// foreach (var channelId in _pendingConnectionTimeOut[timeId]) +// { +// _pendingConnection.Remove(channelId); +// } +// +// _pendingConnectionTimeOut.RemoveKey(timeId); +// } +// } +// +// public void AddUpdateChannel(uint channelId, uint tillTime) +// { +// if (tillTime == 0) +// { +// _updateChannels.Add(channelId); +// return; +// } +// +// if (IsTimeGreaterThan(_updateMinTime, tillTime)) +// { +// _updateMinTime = tillTime; +// } +// +// _updateTimer.Add(tillTime, channelId); +// } +// +// private const uint HalfMaxUint = uint.MaxValue / 2; +// +// [MethodImpl(MethodImplOptions.AggressiveInlining)] +// private bool IsTimeGreaterThan(uint timeId, uint nowTime) +// { +// if (!_allowWraparound) +// { +// return timeId > nowTime; +// } +// +// var diff = timeId - nowTime; +// // 如果 diff 的值在 [0, HalfMaxUint] 范围内,说明 timeId 是在 nowTime 之后或相等。 +// // 如果 diff 的值在 (HalfMaxUint, uint.MaxValue] 范围内,说明 timeId 是在 nowTime 之前(时间回绕的情况)。 +// return diff < HalfMaxUint || diff == HalfMaxUint; +// } +// +// #endregion +// +// #region Pending +// +// private void AddPendingConnection(ref uint channelId, IPEndPoint ipEndPoint) +// { +// var now = TimeNow; +// var pendingConnection = new PendingConnection(channelId, ipEndPoint, now); +// +// if (IsTimeGreaterThan(_pendingMinTime, pendingConnection.TimeOutId) || _pendingMinTime == 0) +// { +// _pendingMinTime = pendingConnection.TimeOutId; +// } +// +// _pendingConnection.Add(channelId, pendingConnection); +// _pendingConnectionTimeOut.Add(pendingConnection.TimeOutId, channelId); +// SendWaitConfirmConnection(ref channelId, ipEndPoint); +// } +// +// private bool ConfirmPendingConnection(ref uint channelId, EndPoint ipEndPoint) +// { +// if (!_pendingConnection.TryGetValue(channelId, out var pendingConnection)) +// { +// return false; +// } +// +// if (!ipEndPoint.IPEndPointEquals(pendingConnection.RemoteEndPoint)) +// { +// Log.Error($"KCPSocket syn address diff: {channelId} {pendingConnection.RemoteEndPoint} {ipEndPoint}"); +// return false; +// } +// +// _pendingConnection.Remove(channelId); +// _pendingConnectionTimeOut.RemoveValue(pendingConnection.TimeOutId, pendingConnection.ChannelId); +// #if FANTASY_DEVELOP +// Log.Debug($"KCPSocket _pendingConnection:{_pendingConnection.Count} _pendingConnectionTimer:{_pendingConnectionTimeOut.Count}"); +// #endif +// return true; +// } +// +// #endregion +// +// #region Connection +// +// private void AddConnection(ref uint channelId, IPEndPoint ipEndPoint) +// { +// var eventArgs = new KCPServerNetworkChannel(this, channelId, ipEndPoint); +// _connectionChannel.Add(channelId, eventArgs); +// #if FANTASY_DEVELOP +// Log.Debug($"AddConnection _connectionChannel:{_connectionChannel.Count()}"); +// #endif +// } +// +// public override void RemoveChannel(uint channelId) +// { +// if (!_connectionChannel.Remove(channelId, out var channel)) +// { +// return; +// } +// +// if (!channel.IsDisposed) +// { +// SendDisconnect(ref channelId, channel.RemoteEndPoint); +// channel.Dispose(); +// } +// #if FANTASY_DEVELOP +// Log.Debug($"RemoveChannel _connectionChannel:{_connectionChannel.Count()}"); +// #endif +// } +// +// #endregion +// +// #region Send +// +// private const byte KcpHeaderDisconnect = (byte)KcpHeader.Disconnect; +// private const byte KcpHeaderRepeatChannelId = (byte)KcpHeader.RepeatChannelId; +// private const byte KcpHeaderWaitConfirmConnection = (byte)KcpHeader.WaitConfirmConnection; +// +// private unsafe void SendDisconnect(ref uint channelId, EndPoint clientEndPoint) +// { +// fixed (byte* p = _sendBuff) +// { +// p[0] = KcpHeaderDisconnect; +// *(uint*)(p + 1) = channelId; +// } +// +// SendAsync(_sendBuff, 0, 5, clientEndPoint); +// } +// +// private unsafe void SendRepeatChannelId(ref uint channelId, EndPoint clientEndPoint) +// { +// fixed (byte* p = _sendBuff) +// { +// p[0] = KcpHeaderRepeatChannelId; +// *(uint*)(p + 1) = channelId; +// } +// +// SendAsync(_sendBuff, 0, 5, clientEndPoint); +// } +// +// private unsafe void SendWaitConfirmConnection(ref uint channelId, EndPoint clientEndPoint) +// { +// fixed (byte* p = _sendBuff) +// { +// p[0] = KcpHeaderWaitConfirmConnection; +// *(uint*)(p + 1) = channelId; +// } +// +// SendAsync(_sendBuff, 0, 5, clientEndPoint); +// } +// +// [MethodImpl(MethodImplOptions.AggressiveInlining)] +// public void SendAsync(byte[] buffer, int offset, int count, EndPoint endPoint) +// { +// try +// { +// _socket.SendTo(new ArraySegment(buffer, offset, count), SocketFlags.None, endPoint); +// } +// catch (ArgumentException ex) +// { +// Log.Error($"ArgumentException: {ex.Message}"); // 处理参数错误 +// } +// catch (SocketException) +// { +// //Log.Error($"SocketException: {ex.Message}"); // 处理网络错误 +// } +// catch (ObjectDisposedException) +// { +// // 处理套接字已关闭的情况 +// } +// catch (InvalidOperationException ex) +// { +// Log.Error($"InvalidOperationException: {ex.Message}"); // 处理无效操作 +// } +// catch (Exception ex) +// { +// Log.Error($"Exception: {ex.Message}"); // 捕获其他异常 +// } +// } +// +// #endregion +// } +// } +// +// #endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/KCP/Server/KCPServerNetworkByPipe.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/KCP/Server/KCPServerNetworkByPipe.cs new file mode 100644 index 0000000..54b550d --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/KCP/Server/KCPServerNetworkByPipe.cs @@ -0,0 +1,619 @@ +#if FANTASY_NET +using System.Buffers; +using System.IO.Pipelines; +using System.Net; +using System.Net.Sockets; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Fantasy.Async; +using Fantasy.DataStructure.Collection; +using Fantasy.Entitas.Interface; +using Fantasy.Helper; +using Fantasy.Network.Interface; +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + +// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract +#pragma warning disable CS8604 // Possible null reference argument. +#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type. +#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. +#pragma warning disable CS8602 // Dereference of a possibly null reference. + +#pragma warning disable CS8622 // Nullability of reference types in type of parameter doesn't match the target delegate (possibly because of nullability attributes). + +namespace Fantasy.Network.KCP +{ + public sealed class KCPServerNetworkUpdateSystem : UpdateSystem + { + protected override void Update(KCPServerNetwork self) + { + self.Update(); + } + } + + public struct PendingConnection + { + public readonly uint ChannelId; + public readonly uint TimeOutId; + public readonly IPEndPoint RemoteEndPoint; + + public PendingConnection(uint channelId, IPEndPoint remoteEndPoint, uint time) + { + ChannelId = channelId; + RemoteEndPoint = remoteEndPoint; + TimeOutId = time + 10 * 1000; // 设置10秒超时,如果10秒内没有确认连接则删除。 + } + } + + public sealed class KCPServerNetwork : ANetwork + { + private Socket _socket; + private long _startTime; + private uint _updateMinTime; + private uint _pendingMinTime; + private bool _allowWraparound = true; + private readonly Pipe _pipe = new Pipe(); + private readonly byte[] _sendBuff = new byte[5]; + private readonly List _pendingTimeOutTime = new List(); + private readonly HashSet _updateChannels = new HashSet(); + private readonly List _updateTimeOutTime = new List(); + private readonly Queue _endPoint = new Queue(); + private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); + private readonly SortedOneToManyList _updateTimer = new SortedOneToManyList(); + + private readonly Dictionary _pendingConnection = new Dictionary(); + private readonly SortedOneToManyList _pendingConnectionTimeOut = new SortedOneToManyList(); + private readonly Dictionary _connectionChannel = new Dictionary(); + + public KCPSettings Settings { get; private set; } + + private uint TimeNow => (uint)(TimeHelper.Now - _startTime); + + public void Initialize(NetworkTarget networkTarget, IPEndPoint address) + { + _startTime = TimeHelper.Now; + Settings = KCPSettings.Create(networkTarget); + base.Initialize(NetworkType.Server, NetworkProtocolType.KCP, networkTarget); + _socket = new Socket(address.AddressFamily, SocketType.Dgram, ProtocolType.Udp); + _socket.Blocking = false; + _socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, false); + if (address.AddressFamily == AddressFamily.InterNetworkV6) + { + _socket.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.IPv6Only, false); + } + + _socket.Blocking = false; + _socket.Bind(address); + _socket.SetSocketBufferToOsLimit(); + _socket.SetSioUdpConnReset(); + ReadPipeDataAsync().Coroutine(); + ReceiveSocketAsync().Coroutine(); + Log.Info($"SceneConfigId = {Scene.SceneConfigId} networkTarget = {networkTarget.ToString()} KCPServer Listen {address}"); + } + + public override void Dispose() + { + if (IsDisposed) + { + return; + } + + if (!_cancellationTokenSource.IsCancellationRequested) + { + try + { + _cancellationTokenSource.Cancel(); + } + catch (OperationCanceledException) + { + // 通常情况下,此处的异常可以忽略 + } + } + + foreach (var (_, channel) in _connectionChannel.ToArray()) + { + channel.Dispose(); + } + + _connectionChannel.Clear(); + _pendingConnection.Clear(); + + if (_socket != null) + { + _socket.Dispose(); + _socket = null; + } + + base.Dispose(); + } + + #region ReceiveSocket + + private async FTask ReceiveSocketAsync() + { + EndPoint remoteEndPoint = new IPEndPoint(IPAddress.Any, 0); + + while (!_cancellationTokenSource.IsCancellationRequested) + { + try + { + var memory = _pipe.Writer.GetMemory(8192); + var socketReceiveFromResult = await _socket.ReceiveFromAsync(memory, SocketFlags.None, remoteEndPoint, _cancellationTokenSource.Token); + var receivedBytes = socketReceiveFromResult.ReceivedBytes; + + if (receivedBytes == 5) + { + switch ((KcpHeader)memory.Span[0]) + { + case KcpHeader.RequestConnection: + case KcpHeader.ConfirmConnection: + { + _endPoint.Enqueue(socketReceiveFromResult.RemoteEndPoint.Clone()); + break; + } + } + } + + _pipe.Writer.Advance(receivedBytes); + await _pipe.Writer.FlushAsync(); + } + catch (SocketException ex) + { + Log.Error($"Socket exception: {ex.Message}"); + Dispose(); + break; + } + catch (OperationCanceledException) + { + break; + } + catch (ObjectDisposedException) + { + Dispose(); + break; + } + catch (Exception ex) + { + Log.Error($"Unexpected exception: {ex.Message}"); + } + } + + await _pipe.Writer.CompleteAsync(); + } + + #endregion + + #region ReceivePipeData + + private async FTask ReadPipeDataAsync() + { + var pipeReader = _pipe.Reader; + while (!_cancellationTokenSource.IsCancellationRequested) + { + ReadResult result = default; + + try + { + result = await pipeReader.ReadAsync(_cancellationTokenSource.Token); + } + catch (OperationCanceledException) + { + // 出现这个异常表示取消了_cancellationTokenSource。一般Channel断开会取消。 + break; + } + + var buffer = result.Buffer; + var consumed = buffer.Start; + var examined = buffer.End; + + while (TryReadMessage(ref buffer, out var header, out var channelId, out var message)) + { + ReceiveData(ref header, ref channelId, ref message); + consumed = buffer.Start; + } + + if (result.IsCompleted) + { + break; + } + + pipeReader.AdvanceTo(consumed, examined); + } + + await pipeReader.CompleteAsync(); + } + + private unsafe bool TryReadMessage(ref ReadOnlySequence buffer, out KcpHeader header, out uint channelId, out ReadOnlyMemory message) + { + if (buffer.Length < 5) + { + channelId = 0; + message = default; + header = KcpHeader.None; + if (buffer.Length > 0) + { + buffer = buffer.Slice(buffer.Length); + } + return false; + } + + var readOnlyMemory = buffer.First; + + if (MemoryMarshal.TryGetArray(readOnlyMemory, out var arraySegment)) + { + fixed (byte* bytePointer = &arraySegment.Array[arraySegment.Offset]) + { + header = (KcpHeader)bytePointer[0]; + channelId = Unsafe.ReadUnaligned(ref bytePointer[1]); + } + } + else + { + // 如果无法获取数组段,回退到安全代码来执行。这种情况几乎不会发生、为了保险还是写一下了。 + var firstSpan = readOnlyMemory.Span; + header = (KcpHeader)firstSpan[0]; + channelId = MemoryMarshal.Read(firstSpan.Slice(1, 4)); + } + + message = readOnlyMemory.Slice(5); + buffer = buffer.Slice(readOnlyMemory.Length); + return true; + } + + private void ReceiveData(ref KcpHeader header, ref uint channelId, ref ReadOnlyMemory buffer) + { + switch (header) + { + // 客户端请求建立KCP连接 + case KcpHeader.RequestConnection: + { + _endPoint.TryDequeue(out var ipEndPoint); + + if (_pendingConnection.TryGetValue(channelId, out var pendingConnection)) + { + if (!ipEndPoint.IPEndPointEquals(pendingConnection.RemoteEndPoint)) + { + // 重复通道ID,向客户端发送重复通道ID消息 + SendRepeatChannelId(ref channelId, ipEndPoint); + } + + break; + } + + if (_connectionChannel.ContainsKey(channelId)) + { + // 已存在的通道ID,向客户端发送重复通道ID消息 + SendRepeatChannelId(ref channelId, ipEndPoint); + break; + } + + AddPendingConnection(ref channelId, ipEndPoint); + break; + } + // 客户端确认建立KCP连接 + case KcpHeader.ConfirmConnection: + { + _endPoint.TryDequeue(out var ipEndPoint); + if (!ConfirmPendingConnection(ref channelId, ipEndPoint)) + { + break; + } + + AddConnection(ref channelId, ipEndPoint.Clone()); + break; + } + // 接收KCP的数据 + case KcpHeader.ReceiveData: + { + if (buffer.Length == 5) + { + Log.Warning($"KCP Server KcpHeader.Data buffer.Length == 5"); + break; + } + + if (_connectionChannel.TryGetValue(channelId, out var channel)) + { + channel.Input(buffer); + } + + break; + } + // 断开KCP连接 + case KcpHeader.Disconnect: + { + // 断开不需要清楚PendingConnection让ClearPendingConnection自动清楚就可以了,并且不一定有Pending。 + RemoveChannel(channelId); + break; + } + } + } + + #endregion + + #region Update + + public void Update() + { + var timeNow = TimeNow; + _allowWraparound = timeNow < _updateMinTime; + CheckUpdateTimerOut(ref timeNow); + UpdateChannel(ref timeNow); + PendingTimerOut(ref timeNow); + _allowWraparound = true; + } + + private void CheckUpdateTimerOut(ref uint nowTime) + { + if (_updateTimer.Count == 0) + { + return; + } + + if (IsTimeGreaterThan(_updateMinTime, nowTime)) + { + return; + } + + _updateTimeOutTime.Clear(); + + foreach (var kv in _updateTimer) + { + var timeId = kv.Key; + + if (IsTimeGreaterThan(timeId, nowTime)) + { + _updateMinTime = timeId; + break; + } + + _updateTimeOutTime.Add(timeId); + } + + foreach (var timeId in _updateTimeOutTime) + { + foreach (var channelId in _updateTimer[timeId]) + { + _updateChannels.Add(channelId); + } + + _updateTimer.RemoveKey(timeId); + } + } + + private void UpdateChannel(ref uint timeNow) + { + foreach (var channelId in _updateChannels) + { + if (!_connectionChannel.TryGetValue(channelId, out var channel)) + { + continue; + } + + if (channel.IsDisposed) + { + _connectionChannel.Remove(channelId); + continue; + } + + channel.Kcp.Update(timeNow); + AddUpdateChannel(channelId, channel.Kcp.Check(timeNow)); + } + + _updateChannels.Clear(); + } + + private void PendingTimerOut(ref uint timeNow) + { + if (_pendingConnectionTimeOut.Count == 0) + { + return; + } + + if (IsTimeGreaterThan(_pendingMinTime, timeNow)) + { + return; + } + + _pendingTimeOutTime.Clear(); + + foreach (var kv in _pendingConnectionTimeOut) + { + var timeId = kv.Key; + + if (IsTimeGreaterThan(timeId, timeNow)) + { + _pendingMinTime = timeId; + break; + } + + _pendingTimeOutTime.Add(timeId); + } + + foreach (var timeId in _pendingTimeOutTime) + { + foreach (var channelId in _pendingConnectionTimeOut[timeId]) + { + _pendingConnection.Remove(channelId); + } + + _pendingConnectionTimeOut.RemoveKey(timeId); + } + } + + public void AddUpdateChannel(uint channelId, uint tillTime) + { + if (tillTime == 0) + { + _updateChannels.Add(channelId); + return; + } + + if (IsTimeGreaterThan(_updateMinTime, tillTime)) + { + _updateMinTime = tillTime; + } + + _updateTimer.Add(tillTime, channelId); + } + + private const uint HalfMaxUint = uint.MaxValue / 2; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool IsTimeGreaterThan(uint timeId, uint nowTime) + { + if (!_allowWraparound) + { + return timeId > nowTime; + } + + var diff = timeId - nowTime; + // 如果 diff 的值在 [0, HalfMaxUint] 范围内,说明 timeId 是在 nowTime 之后或相等。 + // 如果 diff 的值在 (HalfMaxUint, uint.MaxValue] 范围内,说明 timeId 是在 nowTime 之前(时间回绕的情况)。 + return diff < HalfMaxUint || diff == HalfMaxUint; + } + + #endregion + + #region Pending + + private void AddPendingConnection(ref uint channelId, IPEndPoint ipEndPoint) + { + var now = TimeNow; + var pendingConnection = new PendingConnection(channelId, ipEndPoint, now); + + if (IsTimeGreaterThan(_pendingMinTime, pendingConnection.TimeOutId) || _pendingMinTime == 0) + { + _pendingMinTime = pendingConnection.TimeOutId; + } + + _pendingConnection.Add(channelId, pendingConnection); + _pendingConnectionTimeOut.Add(pendingConnection.TimeOutId, channelId); + SendWaitConfirmConnection(ref channelId, ipEndPoint); + } + + private bool ConfirmPendingConnection(ref uint channelId, EndPoint ipEndPoint) + { + if (!_pendingConnection.TryGetValue(channelId, out var pendingConnection)) + { + return false; + } + + if (!ipEndPoint.IPEndPointEquals(pendingConnection.RemoteEndPoint)) + { + Log.Error($"KCPSocket syn address diff: {channelId} {pendingConnection.RemoteEndPoint} {ipEndPoint}"); + return false; + } + + _pendingConnection.Remove(channelId); + _pendingConnectionTimeOut.RemoveValue(pendingConnection.TimeOutId, pendingConnection.ChannelId); +#if FANTASY_DEVELOP + Log.Debug($"KCPSocket _pendingConnection:{_pendingConnection.Count} _pendingConnectionTimer:{_pendingConnectionTimeOut.Count}"); +#endif + return true; + } + + #endregion + + #region Connection + + private void AddConnection(ref uint channelId, IPEndPoint ipEndPoint) + { + var eventArgs = new KCPServerNetworkChannel(this, channelId, ipEndPoint); + _connectionChannel.Add(channelId, eventArgs); +#if FANTASY_DEVELOP + Log.Debug($"AddConnection _connectionChannel:{_connectionChannel.Count()}"); +#endif + } + + public override void RemoveChannel(uint channelId) + { + if (!_connectionChannel.Remove(channelId, out var channel)) + { + return; + } + + if (!channel.IsDisposed) + { + SendDisconnect(ref channelId, channel.RemoteEndPoint); + channel.Dispose(); + } +#if FANTASY_DEVELOP + Log.Debug($"RemoveChannel _connectionChannel:{_connectionChannel.Count()}"); +#endif + } + + #endregion + + #region Send + + private const byte KcpHeaderDisconnect = (byte)KcpHeader.Disconnect; + private const byte KcpHeaderRepeatChannelId = (byte)KcpHeader.RepeatChannelId; + private const byte KcpHeaderWaitConfirmConnection = (byte)KcpHeader.WaitConfirmConnection; + + private unsafe void SendDisconnect(ref uint channelId, EndPoint clientEndPoint) + { + fixed (byte* p = _sendBuff) + { + p[0] = KcpHeaderDisconnect; + *(uint*)(p + 1) = channelId; + } + + SendAsync(_sendBuff, 0, 5, clientEndPoint); + } + + private unsafe void SendRepeatChannelId(ref uint channelId, EndPoint clientEndPoint) + { + fixed (byte* p = _sendBuff) + { + p[0] = KcpHeaderRepeatChannelId; + *(uint*)(p + 1) = channelId; + } + + SendAsync(_sendBuff, 0, 5, clientEndPoint); + } + + private unsafe void SendWaitConfirmConnection(ref uint channelId, EndPoint clientEndPoint) + { + fixed (byte* p = _sendBuff) + { + p[0] = KcpHeaderWaitConfirmConnection; + *(uint*)(p + 1) = channelId; + } + + SendAsync(_sendBuff, 0, 5, clientEndPoint); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SendAsync(byte[] buffer, int offset, int count, EndPoint endPoint) + { + try + { + _socket.SendTo(new ArraySegment(buffer, offset, count), SocketFlags.None, endPoint); + } + catch (ArgumentException ex) + { + Log.Error($"ArgumentException: {ex.Message}"); // 处理参数错误 + } + catch (SocketException) + { + //Log.Error($"SocketException: {ex.Message}"); // 处理网络错误 + } + catch (ObjectDisposedException) + { + // 处理套接字已关闭的情况 + } + catch (InvalidOperationException ex) + { + Log.Error($"InvalidOperationException: {ex.Message}"); // 处理无效操作 + } + catch (Exception ex) + { + Log.Error($"Exception: {ex.Message}"); // 捕获其他异常 + } + } + + #endregion + } +} + +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/KCP/Server/KCPServerNetworkChannel.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/KCP/Server/KCPServerNetworkChannel.cs new file mode 100644 index 0000000..19db7a7 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/KCP/Server/KCPServerNetworkChannel.cs @@ -0,0 +1,165 @@ +#if FANTASY_NET +using System.Net; +using System.Net.Sockets; +using System.Runtime.CompilerServices; +using Fantasy.Helper; +using Fantasy.Network.Interface; +using Fantasy.PacketParser; +using Fantasy.Serialize; +using KCP; +// ReSharper disable ParameterHidesMember +#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + +namespace Fantasy.Network.KCP +{ + /// + /// KCP 服务器网络通道,用于处理服务器与客户端之间的数据通信。 + /// + public class KCPServerNetworkChannel : ANetworkServerChannel + { + private bool _isInnerDispose; + private readonly int _maxSndWnd; + private KCPServerNetwork _kcpServerNetwork; + private readonly BufferPacketParser _packetParser; + private readonly byte[] _receiveBuffer = new byte[Packet.PacketBodyMaxLength + 20]; + public Kcp Kcp { get; private set; } + public uint ChannelId { get; private set; } + + public KCPServerNetworkChannel(KCPServerNetwork network, uint channelId, IPEndPoint ipEndPoint) : base(network, channelId, ipEndPoint) + { + _kcpServerNetwork = network; + ChannelId = channelId; + _maxSndWnd = network.Settings.MaxSendWindowSize; + Kcp = KCPFactory.Create(network.Settings, ChannelId, KcpSpanCallback); + _packetParser = PacketParserFactory.CreateServerBufferPacket(network); + } + + public override void Dispose() + { + if (IsDisposed || _isInnerDispose) + { + return; + } + + try + { + _isInnerDispose = true; + _kcpServerNetwork.RemoveChannel(Id); + IsDisposed = true; + Kcp.Dispose(); + Kcp = null; + ChannelId = 0; + _kcpServerNetwork = null; + } + catch (Exception e) + { + Log.Error(e); + } + finally + { + base.Dispose(); + } + } + + public void Input(ReadOnlyMemory buffer) + { + Kcp.Input(buffer.Span); + _kcpServerNetwork.AddUpdateChannel(ChannelId, 0); + + while (!IsDisposed) + { + try + { + var peekSize = Kcp.PeekSize(); + + if (peekSize < 0) + { + return; + } + + var receiveCount = Kcp.Receive(_receiveBuffer.AsSpan(0, peekSize)); + + if (receiveCount != peekSize) + { + return; + } + + if (!_packetParser.UnPack(_receiveBuffer, ref receiveCount, out var packInfo)) + { + continue; + } + + Session.Receive(packInfo); + } + catch (ScanException e) + { + Log.Debug($"RemoteAddress:{RemoteEndPoint} \n{e}"); + Dispose(); + } + catch (Exception e) + { + Log.Error(e); + } + } + } + + public override void Send(uint rpcId, long routeId, MemoryStreamBuffer memoryStream, IMessage message) + { + if (IsDisposed) + { + return; + } + + if (Kcp.WaitSendCount > _maxSndWnd) + { + // 检查等待发送的消息,如果超出两倍窗口大小,KCP作者给的建议是要断开连接 + Log.Warning($"ERR_KcpWaitSendSizeTooLarge {Kcp.WaitSendCount} > {_maxSndWnd}"); + Dispose(); + return; + } + + var buffer = _packetParser.Pack(ref rpcId, ref routeId, memoryStream, message); + Kcp.Send(buffer.GetBuffer().AsSpan(0, (int)buffer.Position)); + + if (buffer.MemoryStreamBufferSource == MemoryStreamBufferSource.Pack) + { + _kcpServerNetwork.MemoryStreamBufferPool.ReturnMemoryStream(buffer); + } + + _kcpServerNetwork.AddUpdateChannel(ChannelId, 0); + } + + private const byte KcpHeaderReceiveData = (byte)KcpHeader.ReceiveData; + + private unsafe void KcpSpanCallback(byte[] buffer, int count) + { + if (IsDisposed) + { + return; + } + + try + { + if (count == 0) + { + throw new Exception("KcpOutput count 0"); + } + + fixed (byte* p = buffer) + { + p[0] = KcpHeaderReceiveData; + *(uint*)(p + 1) = ChannelId; + } + + _kcpServerNetwork.SendAsync(buffer, 0, count + 5, RemoteEndPoint); + } + catch (Exception e) + { + Log.Error(e); + } + } + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/NetworkProtocolFactory.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/NetworkProtocolFactory.cs new file mode 100644 index 0000000..c7b6ecc --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/NetworkProtocolFactory.cs @@ -0,0 +1,95 @@ +using System; +using System.Net; +using Fantasy.Entitas; +using Fantasy.Helper; +using Fantasy.Network.Interface; +#if !FANTASY_WEBGL +using Fantasy.Network.TCP; +using Fantasy.Network.KCP; +#endif +#if FANTASY_NET +using Fantasy.Network.HTTP; +#endif +using Fantasy.Network.WebSocket; + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + +namespace Fantasy.Network +{ + internal static class NetworkProtocolFactory + { +#if FANTASY_NET + public static ANetwork CreateServer(Scene scene, NetworkProtocolType protocolType, NetworkTarget networkTarget, string bindIp, int port) + { + switch (protocolType) + { + case NetworkProtocolType.TCP: + { + var network = Entity.Create(scene, false, false); + var address = NetworkHelper.ToIPEndPoint(bindIp, port); + network.Initialize(networkTarget, address); + return network; + } + case NetworkProtocolType.KCP: + { + var network = Entity.Create(scene, false, true); + var address = NetworkHelper.ToIPEndPoint(bindIp, port); + network.Initialize(networkTarget, address); + return network; + } + case NetworkProtocolType.WebSocket: + { + var network = Entity.Create(scene, false, true); + network.Initialize(networkTarget, bindIp, port); + return network; + } + case NetworkProtocolType.HTTP: + { + var network = Entity.Create(scene, false, true); + network.Initialize(networkTarget, bindIp, port); + return network; + } + default: + { + throw new NotSupportedException($"Unsupported NetworkProtocolType:{protocolType}"); + } + } + } +#endif + public static AClientNetwork CreateClient(Scene scene, NetworkProtocolType protocolType, NetworkTarget networkTarget) + { +#if !FANTASY_WEBGL + switch (protocolType) + { + case NetworkProtocolType.TCP: + { + var network = Entity.Create(scene, false, false); + network.Initialize(networkTarget); + return network; + } + case NetworkProtocolType.KCP: + { + var network = Entity.Create(scene, false, true); + network.Initialize(networkTarget); + return network; + } + case NetworkProtocolType.WebSocket: + { + var network = Entity.Create(scene, false, true); + network.Initialize(networkTarget); + return network; + } + default: + { + throw new NotSupportedException($"Unsupported NetworkProtocolType:{protocolType}"); + } + } +#else + // Webgl平台只能用这个协议。 + var network = Entity.Create(scene, false, true); + network.Initialize(networkTarget); + return network; +#endif + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/NetworkProtocolType.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/NetworkProtocolType.cs new file mode 100644 index 0000000..55a04c9 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/NetworkProtocolType.cs @@ -0,0 +1,69 @@ +namespace Fantasy.Network +{ + /// + /// 网络服务器类型 + /// + public enum NetworkType + { + /// + /// 默认 + /// + None = 0, + /// + /// 客户端网络 + /// + Client = 1, +#if FANTASY_NET + /// + /// 服务器网络 + /// + Server = 2 +#endif + } + /// + /// 网络服务的目标 + /// + public enum NetworkTarget + { + /// + /// 默认 + /// + None = 0, + /// + /// 对外 + /// + Outer = 1, +#if FANTASY_NET + /// + /// 对内 + /// + Inner = 2 +#endif + } + /// + /// 支持的网络协议 + /// + public enum NetworkProtocolType + { + /// + /// 默认 + /// + None = 0, + /// + /// KCP + /// + KCP = 1, + /// + /// TCP + /// + TCP = 2, + /// + /// WebSocket + /// + WebSocket = 3, + /// + /// HTTP + /// + HTTP = 4, + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/NetworkThreadComponent.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/NetworkThreadComponent.cs new file mode 100644 index 0000000..c0c166b --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/NetworkThreadComponent.cs @@ -0,0 +1,100 @@ +#if !FANTASY_WEBGL +using System; +using System.Collections.Generic; +using System.Threading; +using Fantasy.Entitas; +// ReSharper disable ForCanBeConvertedToForeach +#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. + +namespace Fantasy.Network +{ + internal interface INetworkThreadUpdate + { + void Update(); + } + + /// + /// 网络线程组件 + /// + internal sealed class NetworkThreadComponent : Entity + { + private Thread _netWorkThread; + internal ThreadSynchronizationContext SynchronizationContext { get; private set; } + private readonly List _updates = new List(); + + internal NetworkThreadComponent Initialize() + { + SynchronizationContext = new ThreadSynchronizationContext(); + _netWorkThread = new Thread(Update) + { + IsBackground = true + }; + _netWorkThread.Start(); + return this; + } + + public override void Dispose() + { + if (IsDisposed) + { + return; + } + + SynchronizationContext.Post(() => + { + _updates.Clear(); + _netWorkThread.Join(); + _netWorkThread = null; + SynchronizationContext = null; + }); + + base.Dispose(); + } + + private void Update() + { + // 将同步上下文设置为网络线程的上下文,以确保操作在正确的线程上下文中执行。 + System.Threading.SynchronizationContext.SetSynchronizationContext(SynchronizationContext); + // 循环执行 + while (!IsDisposed) + { + for (var i = 0; i < _updates.Count; i++) + { + try + { + _updates[i].Update(); + } + catch (Exception e) + { + Log.Error(e); + } + } + SynchronizationContext.Update(); + Thread.Sleep(1); + } + } + + internal void AddNetworkThreadUpdate(INetworkThreadUpdate update) + { + SynchronizationContext.Post(() => + { + if (_updates.Contains(update)) + { + Log.Warning($"{update.GetType().FullName} Network thread update is already running"); + return; + } + _updates.Add(update); + }); + } + + internal void RemoveNetworkThreadUpdate(INetworkThreadUpdate update) + { + SynchronizationContext.Post(() => + { + _updates.Remove(update); + }); + } + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/TCP/Client/TCPClientNetwork.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/TCP/Client/TCPClientNetwork.cs new file mode 100644 index 0000000..233fb7b --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/TCP/Client/TCPClientNetwork.cs @@ -0,0 +1,413 @@ +#if !FANTASY_WEBGL +using System; +using System.Buffers; +using System.Collections.Generic; +using System.IO; +using System.IO.Pipelines; +using System.Net; +using System.Net.Sockets; +using System.Runtime.InteropServices; +using System.Threading; +using Fantasy.Async; +using Fantasy.Helper; +using Fantasy.Network.Interface; +using Fantasy.PacketParser; +using Fantasy.Serialize; +// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract +#pragma warning disable CS8602 // Dereference of a possibly null reference. +#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. +#pragma warning disable CS8604 // Possible null reference argument. +#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type. +#pragma warning disable CS8622 // Nullability of reference types in type of parameter doesn't match the target delegate (possibly because of nullability attributes). + +namespace Fantasy.Network.TCP +{ + public sealed class TCPClientNetwork : AClientNetwork + { + private bool _isSending; + private bool _isInnerDispose; + private long _connectTimeoutId; + private Socket _socket; + private IPEndPoint _remoteEndPoint; + private SocketAsyncEventArgs _sendArgs; + private ReadOnlyMemoryPacketParser _packetParser; + private readonly Pipe _pipe = new Pipe(); + private readonly Queue _sendBuffers = new Queue(); + private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); + + private Action _onConnectFail; + private Action _onConnectComplete; + private Action _onConnectDisconnect; + + public uint ChannelId { get; private set; } + + public void Initialize(NetworkTarget networkTarget) + { + base.Initialize(NetworkType.Client, NetworkProtocolType.TCP, networkTarget); + } + + public override void Dispose() + { + if (IsDisposed || _isInnerDispose) + { + return; + } + + try + { + _isSending = false; + _isInnerDispose = true; + ClearConnectTimeout(); + + if (!_cancellationTokenSource.IsCancellationRequested) + { + try + { + _cancellationTokenSource.Cancel(); + } + catch (OperationCanceledException) + { + // 通常情况下,此处的异常可以忽略 + } + } + + _onConnectDisconnect?.Invoke(); + + if (_socket.Connected) + { + _socket.Close(); + _socket = null; + } + + _sendBuffers.Clear(); + _packetParser?.Dispose(); + ChannelId = 0; + _sendArgs = null; + } + catch (Exception e) + { + Log.Error(e); + } + finally + { + base.Dispose(); + } + } + + /// + /// 连接到远程服务器。 + /// + /// 远程服务器的终端点。 + /// 连接成功时的回调。 + /// 连接失败时的回调。 + /// 连接断开时的回调。 + /// + /// 连接超时时间,单位:毫秒。 + /// 连接的会话。 + public override Session Connect(string remoteAddress, Action onConnectComplete, Action onConnectFail, Action onConnectDisconnect, bool isHttps, int connectTimeout = 5000) + { + // 如果已经初始化过一次,抛出异常,要求重新实例化 + + if (IsInit) + { + throw new NotSupportedException("TCPClientNetwork Has already been initialized. If you want to call Connect again, please re instantiate it."); + } + + IsInit = true; + _isSending = false; + _onConnectFail = onConnectFail; + _onConnectComplete = onConnectComplete; + _onConnectDisconnect = onConnectDisconnect; + // 设置连接超时定时器 + _connectTimeoutId = Scene.TimerComponent.Net.OnceTimer(connectTimeout, () => + { + _onConnectFail?.Invoke(); + Dispose(); + }); + _packetParser = PacketParserFactory.CreateClientReadOnlyMemoryPacket(this); + _remoteEndPoint = NetworkHelper.GetIPEndPoint(remoteAddress); + _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + _socket.NoDelay = true; + _socket.SetSocketBufferToOsLimit(); + _sendArgs = new SocketAsyncEventArgs(); + _sendArgs.Completed += OnSendCompleted; + var outArgs = new SocketAsyncEventArgs + { + RemoteEndPoint = _remoteEndPoint + }; + outArgs.Completed += OnConnectSocketCompleted; + + if (!_socket.ConnectAsync(outArgs)) + { + OnReceiveSocketComplete(); + } + + Session = Session.Create(this, _remoteEndPoint); + return Session; + } + + private void OnConnectSocketCompleted(object sender, SocketAsyncEventArgs asyncEventArgs) + { + if (_cancellationTokenSource.IsCancellationRequested) + { + return; + } + + if (asyncEventArgs.LastOperation == SocketAsyncOperation.Connect) + { + if (asyncEventArgs.SocketError == SocketError.Success) + { + Scene.ThreadSynchronizationContext.Post(OnReceiveSocketComplete); + } + else + { + Scene.ThreadSynchronizationContext.Post(() => + { + _onConnectFail?.Invoke(); + Dispose(); + }); + } + } + } + + private void OnReceiveSocketComplete() + { + ClearConnectTimeout(); + _onConnectComplete?.Invoke(); + ReadPipeDataAsync().Coroutine(); + ReceiveSocketAsync().Coroutine(); + } + + #region ReceiveSocket + + private async FTask ReceiveSocketAsync() + { + while (!_cancellationTokenSource.IsCancellationRequested) + { + try + { + var memory = _pipe.Writer.GetMemory(8192); +#if UNITY_2021 + // Unity2021.3.14f有个恶心的问题,使用ReceiveAsync会导致memory不能正确写入 + // 所有只能使用ReceiveFromAsync来接收消息,但ReceiveFromAsync只有一个接受ArraySegment的接口。 + MemoryMarshal.TryGetArray(memory, out ArraySegment arraySegment); + var result = await _socket.ReceiveFromAsync(arraySegment, SocketFlags.None, _remoteEndPoint); + _pipe.Writer.Advance(result.ReceivedBytes); +#else + var count = await _socket.ReceiveAsync(memory, SocketFlags.None, _cancellationTokenSource.Token); + _pipe.Writer.Advance(count); +#endif + await _pipe.Writer.FlushAsync(); + } + catch (SocketException) + { + Dispose(); + break; + } + catch (OperationCanceledException) + { + break; + } + catch (ObjectDisposedException) + { + Dispose(); + break; + } + catch (Exception ex) + { + Log.Error($"Unexpected exception: {ex.Message}"); + } + } + + await _pipe.Writer.CompleteAsync(); + } + + #endregion + + #region ReceivePipeData + + private async FTask ReadPipeDataAsync() + { + var pipeReader = _pipe.Reader; + while (!_cancellationTokenSource.IsCancellationRequested) + { + ReadResult result = default; + + try + { + result = await pipeReader.ReadAsync(_cancellationTokenSource.Token); + } + catch (OperationCanceledException) + { + // 出现这个异常表示取消了_cancellationTokenSource。一般Channel断开会取消。 + break; + } + + var buffer = result.Buffer; + var consumed = buffer.Start; + var examined = buffer.End; + + while (TryReadMessage(ref buffer, out var message)) + { + ReceiveData(ref message); + consumed = buffer.Start; + } + + if (result.IsCompleted) + { + break; + } + + pipeReader.AdvanceTo(consumed, examined); + } + + await pipeReader.CompleteAsync(); + } + + private bool TryReadMessage(ref ReadOnlySequence buffer, out ReadOnlyMemory message) + { + if (buffer.Length == 0) + { + message = default; + return false; + } + + message = buffer.First; + + if (message.Length == 0) + { + message = default; + return false; + } + + buffer = buffer.Slice(message.Length); + return true; + } + + private void ReceiveData(ref ReadOnlyMemory buffer) + { + try + { + while (_packetParser.UnPack(ref buffer, out var packInfo)) + { + if (_cancellationTokenSource.IsCancellationRequested) + { + return; + } + Session.Receive(packInfo); + } + } + catch (ScanException e) + { + Log.Warning(e.Message); + Dispose(); + } + catch (Exception e) + { + Log.Error(e); + Dispose(); + } + } + + #endregion + + #region Send + + public override void Send(uint rpcId, long routeId, MemoryStreamBuffer memoryStream, IMessage message) + { + _sendBuffers.Enqueue(_packetParser.Pack(ref rpcId, ref routeId, memoryStream, message)); + + if (!_isSending) + { + Send(); + } + } + + private void Send() + { + if (_isSending || IsDisposed) + { + return; + } + + _isSending = true; + + while (_sendBuffers.Count > 0) + { + var memoryStreamBuffer = _sendBuffers.Dequeue(); + _sendArgs.UserToken = memoryStreamBuffer; + _sendArgs.SetBuffer(new ArraySegment(memoryStreamBuffer.GetBuffer(), 0, (int)memoryStreamBuffer.Position)); + + try + { + if (_socket.SendAsync(_sendArgs)) + { + break; + } + + ReturnMemoryStream(memoryStreamBuffer); + } + catch + { + _isSending = false; + return; + } + } + + _isSending = false; + } + + private void ReturnMemoryStream(MemoryStreamBuffer memoryStream) + { + if (memoryStream.MemoryStreamBufferSource == MemoryStreamBufferSource.Pack) + { + MemoryStreamBufferPool.ReturnMemoryStream(memoryStream); + } + } + + private void OnSendCompleted(object sender, SocketAsyncEventArgs asyncEventArgs) + { + if (asyncEventArgs.SocketError != SocketError.Success || asyncEventArgs.BytesTransferred == 0) + { + _isSending = false; + return; + } + + var memoryStreamBuffer = (MemoryStreamBuffer)asyncEventArgs.UserToken; + Scene.ThreadSynchronizationContext.Post(() => + { + ReturnMemoryStream(memoryStreamBuffer); + + if (_sendBuffers.Count > 0) + { + Send(); + } + else + { + _isSending = false; + } + }); + } + + #endregion + + public override void RemoveChannel(uint channelId) + { + Dispose(); + } + + private void ClearConnectTimeout() + { + if (_connectTimeoutId == 0) + { + return; + } + + Scene?.TimerComponent?.Net?.Remove(ref _connectTimeoutId); + } + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/TCP/Server/TCPServerNetwork.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/TCP/Server/TCPServerNetwork.cs new file mode 100644 index 0000000..3e8efb4 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/TCP/Server/TCPServerNetwork.cs @@ -0,0 +1,161 @@ +#if FANTASY_NET +using System.Net; +using System.Net.Sockets; +using Fantasy.Helper; +using Fantasy.Network.Interface; +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + +// ReSharper disable GCSuppressFinalizeForTypeWithoutDestructor +#pragma warning disable CS8622 // Nullability of reference types in type of parameter doesn't match the target delegate (possibly because of nullability attributes). +#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. + +namespace Fantasy.Network.TCP +{ + public sealed class TCPServerNetwork : ANetwork + { + private Random _random; + private Socket _socket; + private SocketAsyncEventArgs _acceptAsync; + private readonly Dictionary _connectionChannel = new Dictionary(); + + public void Initialize(NetworkTarget networkTarget, IPEndPoint address) + { + base.Initialize(NetworkType.Server, NetworkProtocolType.TCP, networkTarget); + _random = new Random(); + _acceptAsync = new SocketAsyncEventArgs(); + _socket = new Socket(address.AddressFamily, SocketType.Stream, ProtocolType.Tcp); + _socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, false); + + if (address.AddressFamily == AddressFamily.InterNetworkV6) + { + _socket.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.IPv6Only, false); + } + + _socket.Bind(address); + _socket.Listen(int.MaxValue); + _socket.SetSocketBufferToOsLimit(); + Log.Info($"SceneConfigId = {Scene.SceneConfigId} networkTarget = {networkTarget.ToString()} TCPServer Listen {address}"); + _acceptAsync.Completed += OnCompleted; + AcceptAsync(); + } + + public override void Dispose() + { + if (IsDisposed) + { + return; + } + + try + { + foreach (var networkChannel in _connectionChannel.Values.ToArray()) + { + networkChannel.Dispose(); + } + + _connectionChannel.Clear(); + _random = null; + _socket.Dispose(); + _socket = null; + _acceptAsync.Dispose(); + _acceptAsync = null; + GC.SuppressFinalize(this); + } + catch (Exception e) + { + Log.Error(e); + } + finally + { + base.Dispose(); + } + } + + private void AcceptAsync() + { + _acceptAsync.AcceptSocket = null; + + if (_socket.AcceptAsync(_acceptAsync)) + { + return; + } + + OnAcceptComplete(_acceptAsync); + } + + private void OnAcceptComplete(SocketAsyncEventArgs asyncEventArgs) + { + if (asyncEventArgs.AcceptSocket == null) + { + return; + } + + if (asyncEventArgs.SocketError != SocketError.Success) + { + Log.Error($"Socket Accept Error: {_acceptAsync.SocketError}"); + return; + } + + try + { + uint channelId; + do + { + channelId = 0xC0000000 | (uint)_random.Next(); + } while (_connectionChannel.ContainsKey(channelId)); + + _connectionChannel.Add(channelId, new TCPServerNetworkChannel(this, asyncEventArgs.AcceptSocket, channelId)); + } + catch (Exception e) + { + Log.Error(e); + } + finally + { + AcceptAsync(); + } + } + + public override void RemoveChannel(uint channelId) + { + if (IsDisposed || !_connectionChannel.Remove(channelId, out var channel)) + { + return; + } + + if (channel.IsDisposed) + { + return; + } + + channel.Dispose(); + } + + #region 网络线程(由Socket底层产生的线程) + + private void OnCompleted(object sender, SocketAsyncEventArgs asyncEventArgs) + { + switch (asyncEventArgs.LastOperation) + { + case SocketAsyncOperation.Accept: + { + Scene.ThreadSynchronizationContext.Post(() => + { + OnAcceptComplete(asyncEventArgs); + }); + break; + } + default: + { + throw new Exception($"Socket Accept Error: {asyncEventArgs.LastOperation}"); + } + } + } + + #endregion + } +} +#endif + + diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/TCP/Server/TCPServerNetworkChannel.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/TCP/Server/TCPServerNetworkChannel.cs new file mode 100644 index 0000000..15f3f66 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/TCP/Server/TCPServerNetworkChannel.cs @@ -0,0 +1,294 @@ +#if FANTASY_NET +using System.Buffers; +using System.IO.Pipelines; +using System.Net.Sockets; +using Fantasy.Async; +using Fantasy.Network.Interface; +using Fantasy.PacketParser; +using Fantasy.Serialize; +// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract +#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + +#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type. +#pragma warning disable CS8602 // Dereference of a possibly null reference. + +#pragma warning disable CS8604 // Possible null reference argument. +#pragma warning disable CS8622 // Nullability of reference types in type of parameter doesn't match the target delegate (possibly because of nullability attributes). + +namespace Fantasy.Network.TCP +{ + public sealed class TCPServerNetworkChannel : ANetworkServerChannel + { + private bool _isSending; + private bool _isInnerDispose; + private readonly Socket _socket; + private readonly ANetwork _network; + private readonly Pipe _pipe = new Pipe(); + private readonly SocketAsyncEventArgs _sendArgs; + private readonly ReadOnlyMemoryPacketParser _packetParser; + private readonly Queue _sendBuffers = new Queue(); + private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); + + public TCPServerNetworkChannel(ANetwork network, Socket socket, uint id) : base(network, id, socket.RemoteEndPoint) + { + _socket = socket; + _network = network; + _socket.NoDelay = true; + _sendArgs = new SocketAsyncEventArgs(); + _sendArgs.Completed += OnSendCompletedHandler; + _packetParser = PacketParserFactory.CreateServerReadOnlyMemoryPacket(network); + ReadPipeDataAsync().Coroutine(); + ReceiveSocketAsync().Coroutine(); + } + + public override void Dispose() + { + if (IsDisposed || _isInnerDispose) + { + return; + } + + _isInnerDispose = true; + _network.RemoveChannel(Id); + + if (!_cancellationTokenSource.IsCancellationRequested) + { + try + { + _cancellationTokenSource.Cancel(); + } + catch (OperationCanceledException) + { + // 通常情况下,此处的异常可以忽略 + } + } + + if (_socket != null) + { + _socket.Shutdown(SocketShutdown.Both); + _socket.Close(); + } + + _sendBuffers.Clear(); + _packetParser.Dispose(); + _isSending = false; + base.Dispose(); + } + + #region ReceiveSocket + + private async FTask ReceiveSocketAsync() + { + while (!_cancellationTokenSource.IsCancellationRequested) + { + try + { + var memory = _pipe.Writer.GetMemory(8192); + var count = await _socket.ReceiveAsync(memory, SocketFlags.None, _cancellationTokenSource.Token); + + if (count == 0) + { + Dispose(); + return; + } + + _pipe.Writer.Advance(count); + await _pipe.Writer.FlushAsync(); + } + catch (SocketException) + { + Dispose(); + break; + } + catch (OperationCanceledException) + { + break; + } + catch (ObjectDisposedException) + { + Dispose(); + break; + } + catch (Exception ex) + { + Log.Error($"Unexpected exception: {ex.Message}"); + } + } + + await _pipe.Writer.CompleteAsync(); + } + + #endregion + + #region ReceivePipeData + + private async FTask ReadPipeDataAsync() + { + var pipeReader = _pipe.Reader; + while (!_cancellationTokenSource.IsCancellationRequested) + { + ReadResult result = default; + + try + { + result = await pipeReader.ReadAsync(_cancellationTokenSource.Token); + } + catch (OperationCanceledException) + { + // 出现这个异常表示取消了_cancellationTokenSource。一般Channel断开会取消。 + break; + } + + var buffer = result.Buffer; + var consumed = buffer.Start; + var examined = buffer.End; + + while (TryReadMessage(ref buffer, out var message)) + { + ReceiveData(ref message); + consumed = buffer.Start; + } + + if (result.IsCompleted) + { + break; + } + + pipeReader.AdvanceTo(consumed, examined); + } + + await pipeReader.CompleteAsync(); + } + + private bool TryReadMessage(ref ReadOnlySequence buffer, out ReadOnlyMemory message) + { + if (buffer.Length == 0) + { + message = default; + return false; + } + + message = buffer.First; + + if (message.Length == 0) + { + message = default; + return false; + } + + buffer = buffer.Slice(message.Length); + return true; + } + + private void ReceiveData(ref ReadOnlyMemory buffer) + { + try + { + while (_packetParser.UnPack(ref buffer, out var packInfo)) + { + if (_cancellationTokenSource.IsCancellationRequested) + { + return; + } + + Session.Receive(packInfo); + } + } + catch (ScanException e) + { + Log.Warning($"RemoteAddress:{RemoteEndPoint} \n{e}"); + Dispose(); + } + catch (Exception e) + { + Log.Error($"RemoteAddress:{RemoteEndPoint} \n{e}"); + Dispose(); + } + } + + #endregion + + #region Send + + public override void Send(uint rpcId, long routeId, MemoryStreamBuffer memoryStream, IMessage message) + { + _sendBuffers.Enqueue(_packetParser.Pack(ref rpcId, ref routeId, memoryStream, message)); + + if (!_isSending) + { + Send(); + } + } + + private void Send() + { + if (_isSending || IsDisposed) + { + return; + } + + _isSending = true; + + while (_sendBuffers.Count > 0) + { + var memoryStreamBuffer = _sendBuffers.Dequeue(); + _sendArgs.UserToken = memoryStreamBuffer; + _sendArgs.SetBuffer(new ArraySegment(memoryStreamBuffer.GetBuffer(), 0, (int)memoryStreamBuffer.Position)); + + try + { + if (_socket.SendAsync(_sendArgs)) + { + break; + } + + ReturnMemoryStream(memoryStreamBuffer); + } + catch + { + _isSending = false; + return; + } + } + + _isSending = false; + } + + private void ReturnMemoryStream(MemoryStreamBuffer memoryStream) + { + if (memoryStream.MemoryStreamBufferSource == MemoryStreamBufferSource.Pack) + { + _network.MemoryStreamBufferPool.ReturnMemoryStream(memoryStream); + } + } + + private void OnSendCompletedHandler(object sender, SocketAsyncEventArgs asyncEventArgs) + { + if (asyncEventArgs.SocketError != SocketError.Success || asyncEventArgs.BytesTransferred == 0) + { + _isSending = false; + return; + } + + var memoryStreamBuffer = (MemoryStreamBuffer)asyncEventArgs.UserToken; + + Scene.ThreadSynchronizationContext.Post(() => + { + ReturnMemoryStream(memoryStreamBuffer); + + if (_sendBuffers.Count > 0) + { + Send(); + } + else + { + _isSending = false; + } + }); + } + + #endregion + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/WebSocket/Client/WebSocketClientNetwork.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/WebSocket/Client/WebSocketClientNetwork.cs new file mode 100644 index 0000000..45e69e8 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/WebSocket/Client/WebSocketClientNetwork.cs @@ -0,0 +1,345 @@ +#if FANTASY_NET || FANTASY_CONSOLE +using System.Buffers; +using System.IO.Pipelines; +using System.Net.WebSockets; +using Fantasy.Async; +using Fantasy.Helper; +using Fantasy.Network.Interface; +using Fantasy.PacketParser; +using Fantasy.Serialize; +// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +#pragma warning disable CS8603 // Possible null reference return. +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. +#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. +namespace Fantasy.Network.WebSocket +{ + public sealed class WebSocketClientNetwork : AClientNetwork + { + private bool _isSending; + private bool _isInnerDispose; + private long _connectTimeoutId; + private ClientWebSocket _clientWebSocket; + private ReadOnlyMemoryPacketParser _packetParser; + private readonly Pipe _pipe = new Pipe(); + private readonly Queue _sendBuffers = new Queue(); + private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); + + private Action _onConnectFail; + private Action _onConnectComplete; + private Action _onConnectDisconnect; + + public void Initialize(NetworkTarget networkTarget) + { + base.Initialize(NetworkType.Client, NetworkProtocolType.WebSocket, networkTarget); + _packetParser = PacketParserFactory.CreateClientReadOnlyMemoryPacket(this); + } + + public override void Dispose() + { + if (IsDisposed || _isInnerDispose) + { + return; + } + + try + { + _isInnerDispose = true; + + if (!_cancellationTokenSource.IsCancellationRequested) + { + try + { + _cancellationTokenSource.Cancel(); + } + catch (OperationCanceledException) + { + // 通常情况下,此处的异常可以忽略 + } + } + + ClearConnectTimeout(); + WebSocketClientDisposeAsync().Coroutine(); + _onConnectDisconnect?.Invoke(); + _packetParser.Dispose(); + _packetParser = null; + _isSending = false; + } + catch (Exception e) + { + Log.Error(e); + } + finally + { + base.Dispose(); + } + } + + private async FTask WebSocketClientDisposeAsync() + { + if (_clientWebSocket == null) + { + return; + } + + await _clientWebSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", CancellationToken.None); + _clientWebSocket.Dispose(); + _clientWebSocket = null; + } + + public override Session Connect(string remoteAddress, Action onConnectComplete, Action onConnectFail, Action onConnectDisconnect, bool isHttps, int connectTimeout = 5000) + { + if (IsInit) + { + throw new NotSupportedException( + $"WebSocketClientNetwork Id:{Id} Has already been initialized. If you want to call Connect again, please re instantiate it."); + } + + IsInit = true; + _onConnectFail = onConnectFail; + _onConnectComplete = onConnectComplete; + _onConnectDisconnect = onConnectDisconnect; + // 设置连接超时定时器 + _connectTimeoutId = Scene.TimerComponent.Net.OnceTimer(connectTimeout, () => + { + _onConnectFail?.Invoke(); + Dispose(); + }); + + _clientWebSocket = new ClientWebSocket(); + var webSocketAddress = WebSocketHelper.GetWebSocketAddress(remoteAddress, isHttps); + + try + { + _clientWebSocket.ConnectAsync(new Uri(webSocketAddress), _cancellationTokenSource.Token).Wait(); + + if (_cancellationTokenSource.IsCancellationRequested) + { + return null; + } + } + catch (WebSocketException wse) + { + Log.Error($"WebSocket error: {wse.Message}"); + Dispose(); + return null; + } + catch (Exception e) + { + Log.Error($"An error occurred: {e.Message}"); + Dispose(); + return null; + } + + ClearConnectTimeout(); + ReadPipeDataAsync().Coroutine(); + ReceiveSocketAsync().Coroutine(); + _onConnectComplete?.Invoke(); + Session = Session.Create(this, null); + return Session; + } + + #region ReceiveSocket + + private async FTask ReceiveSocketAsync() + { + while (!_cancellationTokenSource.IsCancellationRequested) + { + try + { + var memory = _pipe.Writer.GetMemory(8192); + // 这里接收的数据不一定是一个完整的包。如果大于8192就会分成多个包。 + var receiveResult = await _clientWebSocket.ReceiveAsync(memory, _cancellationTokenSource.Token); + + if (receiveResult.MessageType == WebSocketMessageType.Close) + { + break; + } + + var count = receiveResult.Count; + + if (count > 0) + { + await PipeWriterFlushAsync(count); + } + } + catch (OperationCanceledException) + { + break; + } + catch (ObjectDisposedException) + { + Dispose(); + break; + } + // 这个先暂时注释掉,因为有些时候会出现WebSocketException + // 因为会出现这个挥手的错误,下个版本处理一下。 + // The remote party closed the WebSocket connection without completing the close handshake. + // catch (WebSocketException wse) + // { + // Log.Error($"WebSocket error: {wse.Message}"); + // Dispose(); + // break; + // } + catch (Exception e) + { + Log.Error(e); + } + } + + await _pipe.Writer.CompleteAsync(); + } + + private async FTask PipeWriterFlushAsync(int count) + { + _pipe.Writer.Advance(count); + await _pipe.Writer.FlushAsync(); + } + + #endregion + + #region ReceivePipeData + + private async FTask ReadPipeDataAsync() + { + var pipeReader = _pipe.Reader; + while (!_cancellationTokenSource.IsCancellationRequested) + { + ReadResult result = default; + + try + { + result = await pipeReader.ReadAsync(_cancellationTokenSource.Token); + } + catch (OperationCanceledException) + { + // 出现这个异常表示取消了_cancellationTokenSource。一般Channel断开会取消。 + break; + } + + var buffer = result.Buffer; + var consumed = buffer.Start; + var examined = buffer.End; + + while (TryReadMessage(ref buffer, out var message)) + { + ReceiveData(ref message); + consumed = buffer.Start; + } + + if (result.IsCompleted) + { + break; + } + + pipeReader.AdvanceTo(consumed, examined); + } + + await pipeReader.CompleteAsync(); + } + + private bool TryReadMessage(ref ReadOnlySequence buffer, out ReadOnlyMemory message) + { + if (buffer.Length == 0) + { + message = default; + return false; + } + + message = buffer.First; + + if (message.Length == 0) + { + message = default; + return false; + } + + buffer = buffer.Slice(message.Length); + return true; + } + + private void ReceiveData(ref ReadOnlyMemory buffer) + { + try + { + while (_packetParser.UnPack(ref buffer, out var packInfo)) + { + if (_cancellationTokenSource.IsCancellationRequested) + { + return; + } + + Session.Receive(packInfo); + } + } + catch (ScanException e) + { + Log.Warning(e.Message); + Dispose(); + } + catch (Exception e) + { + Log.Error(e); + Dispose(); + } + } + + #endregion + + #region Send + + public override void Send(uint rpcId, long routeId, MemoryStreamBuffer memoryStream, IMessage message) + { + _sendBuffers.Enqueue(_packetParser.Pack(ref rpcId, ref routeId, memoryStream, message)); + + if (!_isSending) + { + Send().Coroutine(); + } + } + + private async FTask Send() + { + if (_isSending || IsDisposed) + { + return; + } + + _isSending = true; + + while (_isSending) + { + if (!_sendBuffers.TryDequeue(out var memoryStream)) + { + _isSending = false; + return; + } + + await _clientWebSocket.SendAsync(new ArraySegment(memoryStream.GetBuffer(), 0, (int)memoryStream.Position), WebSocketMessageType.Binary, true, _cancellationTokenSource.Token); + + if (memoryStream.MemoryStreamBufferSource == MemoryStreamBufferSource.Pack) + { + MemoryStreamBufferPool.ReturnMemoryStream(memoryStream); + } + } + } + + #endregion + + public override void RemoveChannel(uint channelId) + { + Dispose(); + } + + private void ClearConnectTimeout() + { + if (_connectTimeoutId == 0) + { + return; + } + + Scene?.TimerComponent?.Net?.Remove(ref _connectTimeoutId); + } + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/WebSocket/Client/WebSocketClientNetworkWebgl.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/WebSocket/Client/WebSocketClientNetworkWebgl.cs new file mode 100644 index 0000000..36ef84d --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/WebSocket/Client/WebSocketClientNetworkWebgl.cs @@ -0,0 +1,190 @@ +#if !FANTASY_NET && !FANTASY_CONSOLE +using System; +using System.Collections.Generic; +using System.IO; +using Fantasy.Helper; +using Fantasy.Network.Interface; +using Fantasy.PacketParser; +using Fantasy.Serialize; +using UnityWebSocket; + +namespace Fantasy.Network.WebSocket +{ + // 因为webgl的限制、注定这个要是在游戏主线程里。所以这个库不会再其他线程执行的。 + // WebGL:在WebGL环境下运行 + // 另外不是运行在WebGL环境下,也没必要使用WebSocket协议了。完全可以使用TCP或KCP运行。同样也不会有那个队列产生的GC。 + public class WebSocketClientNetwork : AClientNetwork + { + private UnityWebSocket.WebSocket _webSocket; + private bool _isInnerDispose; + private bool _isConnected; + private long _connectTimeoutId; + private BufferPacketParser _packetParser; + private readonly Queue _messageCache = new Queue(); + + private Action _onConnectFail; + private Action _onConnectComplete; + private Action _onConnectDisconnect; + + public void Initialize(NetworkTarget networkTarget) + { + base.Initialize(NetworkType.Client, NetworkProtocolType.WebSocket, networkTarget); + _packetParser = PacketParserFactory.CreateClient(this); + } + + public override void Dispose() + { + if (IsDisposed || _isInnerDispose) + { + return; + } + + try + { + _isInnerDispose = true; + ClearConnectTimeout(); + + if (_webSocket != null && _webSocket.ReadyState != WebSocketState.Closed) + { + _onConnectDisconnect?.Invoke(); + _webSocket.CloseAsync(); + } + + _packetParser.Dispose(); + _messageCache.Clear(); + } + catch (Exception e) + { + Log.Error(e); + } + finally + { + base.Dispose(); + } + } + + public override Session Connect(string remoteAddress, Action onConnectComplete, Action onConnectFail, Action onConnectDisconnect, bool isHttps, int connectTimeout = 5000) + { + // 如果已经初始化过一次,抛出异常,要求重新实例化 + + if (IsInit) + { + throw new NotSupportedException($"WebSocketClientNetwork Id:{Id} Has already been initialized. If you want to call Connect again, please re instantiate it."); + } + + IsInit = true; + _onConnectFail = onConnectFail; + _onConnectComplete = onConnectComplete; + _onConnectDisconnect = onConnectDisconnect; + _connectTimeoutId = Scene.TimerComponent.Net.OnceTimer(connectTimeout, () => + { + _onConnectFail?.Invoke(); + Dispose(); + }); + var webSocketAddress = WebSocketHelper.GetWebSocketAddress(remoteAddress, isHttps); + _webSocket = new UnityWebSocket.WebSocket(webSocketAddress); + _webSocket.OnOpen += OnNetworkConnectComplete; + _webSocket.OnMessage += OnReceiveComplete; + _webSocket.OnClose += (sender, args) => + { + _onConnectDisconnect?.Invoke(); + Dispose(); + }; + _webSocket.ConnectAsync(); + Session = Session.Create(this, null); + return Session; + } + + private void OnNetworkConnectComplete(object sender, OpenEventArgs e) + { + if (IsDisposed) + { + return; + } + + _isConnected = true; + ClearConnectTimeout(); + _onConnectComplete?.Invoke(); + + while (_messageCache.TryDequeue(out var memoryStream)) + { + Send(memoryStream); + } + } + + #region Receive + + private void OnReceiveComplete(object sender, MessageEventArgs e) + { + try + { + // WebSocket 协议已经在协议层面处理了消息的边界问题,因此不需要额外的粘包处理逻辑。 + // 所以如果解包的时候出现任何错误只能是恶意攻击造成的。 + var rawDataLength = e.RawData.Length; + _packetParser.UnPack(e.RawData, ref rawDataLength, out var packInfo); + Session.Receive(packInfo); + } + catch (ScanException ex) + { + Log.Warning($"{ex}"); + Dispose(); + } + catch (Exception ex) + { + Log.Error($"{ex}"); + Dispose(); + } + } + + #endregion + + #region Send + + public override void Send(uint rpcId, long routeId, MemoryStreamBuffer memoryStream, IMessage message) + { + if (IsDisposed) + { + return; + } + + var buffer = _packetParser.Pack(ref rpcId, ref routeId, memoryStream, message); + + if (!_isConnected) + { + _messageCache.Enqueue(buffer); + return; + } + + Send(buffer); + } + + private void Send(MemoryStreamBuffer memoryStream) + { + _webSocket.SendAsync(memoryStream.GetBuffer(), 0, (int)memoryStream.Position); +#if !UNITY_EDITOR && UNITY_WEBGL + if (memoryStream.MemoryStreamBufferSource == MemoryStreamBufferSource.Pack) + { + MemoryStreamBufferPool.ReturnMemoryStream(memoryStream); + } +#endif + } + + #endregion + + public override void RemoveChannel(uint channelId) + { + Dispose(); + } + + private void ClearConnectTimeout() + { + if (_connectTimeoutId == 0) + { + return; + } + + Scene?.TimerComponent?.Net?.Remove(ref _connectTimeoutId); + } + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/WebSocket/Server/WebSocketServerNetwork.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/WebSocket/Server/WebSocketServerNetwork.cs new file mode 100644 index 0000000..09feb2b --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/WebSocket/Server/WebSocketServerNetwork.cs @@ -0,0 +1,112 @@ +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +#if FANTASY_NET +using System.Net; +using System.Security.Cryptography.X509Certificates; +using Fantasy.Async; +using Fantasy.Network.Interface; + +// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract +// ReSharper disable PossibleMultipleEnumeration +#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. +namespace Fantasy.Network.WebSocket; + +public class WebSocketServerNetwork : ANetwork +{ + private Random _random; + private HttpListener _httpListener; + private readonly Dictionary _connectionChannel = new Dictionary(); + + public void Initialize(NetworkTarget networkTarget, string bindIp, int port) + { + base.Initialize(NetworkType.Server, NetworkProtocolType.WebSocket, networkTarget); + + try + { + _random = new Random(); + _httpListener = new HttpListener(); + StartAcceptAsync(bindIp, port).Coroutine(); + } + catch (HttpListenerException e) + { + if (e.ErrorCode == 5) + { + throw new Exception($"CMD管理员中输入: netsh http add urlacl url=http://*:8080/ user=Everyone", e); + } + + Log.Error(e); + } + catch (Exception e) + { + Log.Error(e); + } + } + + public override void Dispose() + { + if (IsDisposed) + { + return; + } + + if (_httpListener != null) + { + _httpListener.Close(); + _httpListener = null; + } + + foreach (var channel in _connectionChannel.Values.ToArray()) + { + channel.Dispose(); + } + + _connectionChannel.Clear(); + base.Dispose(); + } + + private async FTask StartAcceptAsync(string bindIp, int port) + { + var listenUrl = ""; + var certificatePath = Path.Combine(AppContext.BaseDirectory, $"certificate{bindIp}{port}"); + listenUrl = Directory.Exists(certificatePath) ? $"https://{bindIp}:{port}/" : $"http://{bindIp}:{port}/"; + _httpListener.Prefixes.Add(listenUrl); + _httpListener.Start(); + Log.Info($"SceneConfigId = {Scene.SceneConfigId} WebSocketServer Listen {listenUrl}"); + while (!IsDisposed) + { + try + { + var httpListenerContext = await _httpListener.GetContextAsync(); + var webSocketContext = await httpListenerContext.AcceptWebSocketAsync(null); + var channelId = 0xC0000000 | (uint) _random.Next(); + + while (_connectionChannel.ContainsKey(channelId)) + { + channelId = 0xC0000000 | (uint) _random.Next(); + } + + _connectionChannel.Add(channelId, new WebSocketServerNetworkChannel(this, channelId, webSocketContext, httpListenerContext.Request.RemoteEndPoint)); + } + catch (Exception e) + { + Log.Error(e); + } + } + } + + public override void RemoveChannel(uint channelId) + { + if (IsDisposed || !_connectionChannel.Remove(channelId, out var channel)) + { + return; + } + + if (channel.IsDisposed) + { + return; + } + + channel.Dispose(); + } +} +#endif diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/WebSocket/Server/WebSocketServerNetworkChannel.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/WebSocket/Server/WebSocketServerNetworkChannel.cs new file mode 100644 index 0000000..3b8ee90 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Protocol/WebSocket/Server/WebSocketServerNetworkChannel.cs @@ -0,0 +1,253 @@ +#if FANTASY_NET +using System.Buffers; +using System.IO.Pipelines; +using System.Net; +using System.Net.Sockets; +using System.Net.WebSockets; +using Fantasy.Async; +using Fantasy.Network.Interface; +using Fantasy.PacketParser; +using Fantasy.Serialize; +#pragma warning disable CS8602 // Dereference of a possibly null reference. +#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type. +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + +namespace Fantasy.Network.WebSocket; + +public sealed class WebSocketServerNetworkChannel : ANetworkServerChannel +{ + private bool _isSending; + private bool _isInnerDispose; + private readonly Pipe _pipe = new Pipe(); + private readonly System.Net.WebSockets.WebSocket _webSocket; + private readonly WebSocketServerNetwork _network; + private readonly ReadOnlyMemoryPacketParser _packetParser; + private readonly Queue _sendBuffers = new Queue(); + private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); + + public WebSocketServerNetworkChannel(ANetwork network, uint id, HttpListenerWebSocketContext httpListenerWebSocketContext, IPEndPoint remoteEndPoint) : base(network, id, remoteEndPoint) + { + _network = (WebSocketServerNetwork)network; + _webSocket = httpListenerWebSocketContext.WebSocket; + _packetParser = PacketParserFactory.CreateServerReadOnlyMemoryPacket(network); + ReadPipeDataAsync().Coroutine(); + ReceiveSocketAsync().Coroutine(); + } + + public override void Dispose() + { + if (IsDisposed || _isInnerDispose) + { + return; + } + + _isInnerDispose = true; + if (!_cancellationTokenSource.IsCancellationRequested) + { + try + { + _cancellationTokenSource.Cancel(); + } + catch (OperationCanceledException) + { + // 通常情况下,此处的异常可以忽略 + } + } + _sendBuffers.Clear(); + _network.RemoveChannel(Id); + if (_webSocket.State == WebSocketState.Open || _webSocket.State == WebSocketState.CloseReceived) + { + _webSocket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "Normal Closure", + _cancellationTokenSource.Token).GetAwaiter().GetResult(); + } + _webSocket.Dispose(); + _isSending = false; + base.Dispose(); + } + + #region ReceiveSocket + + private async FTask ReceiveSocketAsync() + { + while (!_cancellationTokenSource.IsCancellationRequested) + { + try + { + var memory = _pipe.Writer.GetMemory(8192); + // 这里接收的数据不一定是一个完整的包。如果大于8192就会分成多个包。 + var receiveResult = await _webSocket.ReceiveAsync(memory, _cancellationTokenSource.Token); + + if (receiveResult.MessageType == WebSocketMessageType.Close) + { + Dispose(); + break; + } + + var count = receiveResult.Count; + + if (count > 0) + { + await PipeWriterFlushAsync(count); + } + } + catch (OperationCanceledException) + { + break; + } + catch (ObjectDisposedException) + { + Dispose(); + break; + } + catch (WebSocketException) + { + // Log.Error($"WebSocket error: {wse.Message}"); + Dispose(); + break; + } + catch (Exception e) + { + Log.Error(e); + } + } + + await _pipe.Writer.CompleteAsync(); + } + + private async FTask PipeWriterFlushAsync(int count) + { + _pipe.Writer.Advance(count); + await _pipe.Writer.FlushAsync(); + } + + #endregion + + #region ReceivePipeData + + private async FTask ReadPipeDataAsync() + { + var pipeReader = _pipe.Reader; + while (!_cancellationTokenSource.IsCancellationRequested) + { + ReadResult result = default; + + try + { + result = await pipeReader.ReadAsync(_cancellationTokenSource.Token); + } + catch (OperationCanceledException) + { + // 出现这个异常表示取消了_cancellationTokenSource。一般Channel断开会取消。 + break; + } + + var buffer = result.Buffer; + var consumed = buffer.Start; + var examined = buffer.End; + + while (TryReadMessage(ref buffer, out var message)) + { + ReceiveData(ref message); + consumed = buffer.Start; + } + + if (result.IsCompleted) + { + break; + } + + pipeReader.AdvanceTo(consumed, examined); + } + + await pipeReader.CompleteAsync(); + } + + private bool TryReadMessage(ref ReadOnlySequence buffer, out ReadOnlyMemory message) + { + if (buffer.Length == 0) + { + message = default; + return false; + } + + message = buffer.First; + + if (message.Length == 0) + { + message = default; + return false; + } + + buffer = buffer.Slice(message.Length); + return true; + } + + private void ReceiveData(ref ReadOnlyMemory buffer) + { + try + { + while (_packetParser.UnPack(ref buffer, out var packInfo)) + { + if (_cancellationTokenSource.IsCancellationRequested) + { + return; + } + + Session.Receive(packInfo); + } + } + catch (ScanException e) + { + Log.Warning($"RemoteAddress:{RemoteEndPoint} \n{e}"); + Dispose(); + } + catch (Exception e) + { + Log.Error($"RemoteAddress:{RemoteEndPoint} \n{e}"); + Dispose(); + } + } + + #endregion + + #region Send + + public override void Send(uint rpcId, long routeId, MemoryStreamBuffer memoryStream, IMessage message) + { + _sendBuffers.Enqueue(_packetParser.Pack(ref rpcId, ref routeId, memoryStream, message)); + + if (!_isSending) + { + Send().Coroutine(); + } + } + + private async FTask Send() + { + if (_isSending || IsDisposed) + { + return; + } + + _isSending = true; + + while (_isSending) + { + if (!_sendBuffers.TryDequeue(out var memoryStream)) + { + _isSending = false; + return; + } + + await _webSocket.SendAsync(new ArraySegment(memoryStream.GetBuffer(), 0, (int)memoryStream.Position), WebSocketMessageType.Binary, true, _cancellationTokenSource.Token); + + if (memoryStream.MemoryStreamBufferSource == MemoryStreamBufferSource.Pack) + { + _network.MemoryStreamBufferPool.ReturnMemoryStream(memoryStream); + } + } + } + + #endregion +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Roaming/Component/RoamingComponent.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Roaming/Component/RoamingComponent.cs new file mode 100644 index 0000000..a42d582 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Roaming/Component/RoamingComponent.cs @@ -0,0 +1,254 @@ +#if FANTASY_NET +using System.Runtime.CompilerServices; +using Fantasy.Async; +using Fantasy.Entitas; +using Fantasy.Timer; +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. +// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract +#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. +#pragma warning disable CS8601 // Possible null reference assignment. +#pragma warning disable CS8603 // Possible null reference return. + +namespace Fantasy.Network.Roaming; + +/// +/// 漫游组件,用来管理当然Scene下的所有漫游消息。 +/// 大多数是在Gate这样的转发服务器上创建的。 +/// +public sealed class RoamingComponent : Entity +{ + private TimerSchedulerNet _timerSchedulerNet; + private readonly Dictionary _sessionRoamingComponents = new(); + private readonly Dictionary _delayRemoveTaskId = new(); + + internal RoamingComponent Initialize() + { + _timerSchedulerNet = Scene.TimerComponent.Net; + return this; + } + + /// + /// 销毁方法。 + /// + public override void Dispose() + { + DisposeAsync().Coroutine(); + } + + private async FTask DisposeAsync() + { + foreach (var (_,taskId) in _delayRemoveTaskId) + { + _timerSchedulerNet.Remove(taskId); + } + + _delayRemoveTaskId.Clear(); + + foreach (var (_, sessionRoamingComponent) in _sessionRoamingComponents) + { + await sessionRoamingComponent.UnLink(); + } + + _sessionRoamingComponents.Clear(); + _timerSchedulerNet = null; + base.Dispose(); + } + + /// + /// 给Session会话增加漫游功能 + /// 如果指定的roamingId已经存在漫游,会把这个漫游功能和当前Session会话关联起来。 + /// + /// + /// 自定义roamingId,这个Id在漫游中并没有实际作用,但用户可以用这个id来进行标记。。 + /// 是否在Session断开的时候自动断开漫游功能。 + /// 如果开启了自定断开漫游功能需要设置一个延迟多久执行断开。 + /// 创建成功会返回SessionRoamingComponent组件,这个组件提供漫游的所有功能。 + public SessionRoamingComponent Create(Session session, long roamingId, bool isAutoDispose, int delayRemove) + { + if (session.SessionRoamingComponent != null) + { + if (session.SessionRoamingComponent.Id != roamingId) + { + Log.Error("The current session has created a SessionRoamingComponent."); + return null; + } + } + else + { + if (!_sessionRoamingComponents.TryGetValue(roamingId, out var sessionRoamingComponent)) + { + sessionRoamingComponent = Entity.Create(Scene, roamingId, true, true); + sessionRoamingComponent.Initialize(session); + _sessionRoamingComponents.Add(roamingId, sessionRoamingComponent); + } + } + + if (isAutoDispose) + { + session.AddComponent(roamingId).DelayRemove = delayRemove; + } + + return session.SessionRoamingComponent; + } + + /// + /// 获取当前Session会话的漫游组件 + /// + /// + /// + public SessionRoamingComponent Get(Session session) + { + var sessionRoamingFlgComponent = session.GetComponent(); + + if (sessionRoamingFlgComponent != null) + { + if (_sessionRoamingComponents.TryGetValue(sessionRoamingFlgComponent.Id, out var sessionRoamingComponent)) + { + return sessionRoamingComponent; + } + + Log.Error($"There is no SessionRoamingComponent with roamingId: {sessionRoamingFlgComponent.Id}."); + } + else + { + Log.Error("The current session has not created a roaming session yet, so you need to create one first."); + } + + return null; + } + + /// + /// 获取当前Session会话的漫游组件 + /// + /// + /// + /// + public bool TryGet(Session session, out SessionRoamingComponent sessionRoamingComponent) + { + var sessionRoamingFlgComponent = session.GetComponent(); + + if (sessionRoamingFlgComponent != null) + { + return _sessionRoamingComponents.TryGetValue(sessionRoamingFlgComponent.Id, out sessionRoamingComponent); + } + + sessionRoamingComponent = null; + return false; + } + + /// + /// 移除一个漫游 + /// + /// + /// 要移除的RoamingType,默认不设置是移除所有漫游。 + /// 当设置了延迟移除时间后,会在设置的时间后再进行移除。 + public async FTask Remove(long roamingId, int roamingType, int delayRemove = 0) + { + if (_delayRemoveTaskId.Remove(roamingId, out var taskId)) + { + _timerSchedulerNet.Remove(taskId); + } + + if (delayRemove <= 0) + { + await InnerRemove(roamingId, roamingType); + return; + } + + taskId = _timerSchedulerNet.OnceTimer(delayRemove, () => + { InnerRemove(roamingId, roamingType).Coroutine(); }); + _delayRemoveTaskId.Add(roamingId, taskId); + } + + private async FTask InnerRemove(long roamingId, int roamingType) + { + if (!_sessionRoamingComponents.Remove(roamingId, out var sessionRoamingComponent)) + { + return; + } + + await sessionRoamingComponent.UnLink(roamingType); + sessionRoamingComponent.Dispose(); + _delayRemoveTaskId.Remove(roamingId); + } +} + +/// +/// 漫游Roaming帮助类 +/// +public static class RoamingHelper +{ + /// + /// 给Session会话增加漫游功能 + /// 如果指定的roamingId已经存在漫游,会把这个漫游功能和当前Session会话关联起来。 + /// + /// + /// 自定义roamingId,这个Id在漫游中并没有实际作用,但用户可以用这个id来进行标记。 + /// 是否在Session断开的时候自动断开漫游功能。 + /// 如果开启了自定断开漫游功能需要设置一个延迟多久执行断开。 + /// 创建成功会返回SessionRoamingComponent组件,这个组件提供漫游的所有功能。 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static SessionRoamingComponent CreateRoaming(this Session session, long roamingId, bool isAutoDispose = true, int delayRemove = 1000 * 60 * 3) + { + return session.Scene.RoamingComponent.Create(session, roamingId, isAutoDispose, delayRemove); + } + + /// + /// 获取当前Session会话的漫游组件 + /// + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static SessionRoamingComponent GetRoaming(this Session session) + { + return session.Scene.RoamingComponent.Get(session); + } + + /// + /// 获取当前Session会话的漫游组件 + /// + /// + /// + /// 如果返回为false表示没有获取到漫游组件。 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryGetRoaming(this Session session, out SessionRoamingComponent sessionRoamingComponent) + { + return session.Scene.RoamingComponent.TryGet(session, out sessionRoamingComponent); + } + + /// + /// 移除一个漫游 + /// + /// + /// 要移除的RoamingType,默认不设置是移除所有漫游。 + /// 当设置了延迟移除时间后,会在设置的时间后再进行移除。 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static async FTask RemoveRoaming(this Session session, int roamingType = 0, int delayRemove = 0) + { + if (session.SessionRoamingComponent == null || session.SessionRoamingComponent.Id == 0) + { + return; + } + + await session.Scene.RoamingComponent.Remove(session.SessionRoamingComponent.Id, roamingType, delayRemove); + } + + /// + /// 移除一个漫游 + /// + /// + /// + /// 要移除的RoamingType,默认不设置是移除所有漫游。 + /// 当设置了延迟移除时间后,会在设置的时间后再进行移除。 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static async FTask RemoveRoaming(Scene scene, long roamingId, int roamingType = 0, int delayRemove = 0) + { + if (roamingId == 0) + { + return; + } + + await scene.RoamingComponent.Remove(roamingId, roamingType, delayRemove); + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Roaming/Component/SessionRoamingComponent.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Roaming/Component/SessionRoamingComponent.cs new file mode 100644 index 0000000..c5ce55a --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Roaming/Component/SessionRoamingComponent.cs @@ -0,0 +1,279 @@ +#if FANTASY_NET +using Fantasy.Async; +using Fantasy.Entitas; +using Fantasy.Entitas.Interface; +using Fantasy.InnerMessage; +using Fantasy.Network.Interface; +using Fantasy.PacketParser.Interface; +using Fantasy.Platform.Net; +using Fantasy.Scheduler; +using Fantasy.Timer; +#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. +#pragma warning disable CS8601 // Possible null reference assignment. +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. +#pragma warning disable CS8603 // Possible null reference return. +#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type. + +namespace Fantasy.Network.Roaming; + +internal sealed class SessionRoamingComponentDestroySystem : DestroySystem +{ + protected override void Destroy(SessionRoamingComponent self) + { + self.RoamingLock.Dispose(); + self.RoamingMessageLock.Dispose(); + + self.RoamingLock = null; + self.RoamingMessageLock = null; + self.TimerComponent = null; + self.NetworkMessagingComponent = null; + self.MessageDispatcherComponent = null; + } +} + +/// +/// Session的漫游组件。 +/// 用于关联对应的Session的功能。 +/// 但这个组件并不会挂载到这个Session下。 +/// +public sealed class SessionRoamingComponent : Entity +{ + internal CoroutineLock RoamingLock; + internal CoroutineLock RoamingMessageLock; + internal TimerComponent TimerComponent; + internal NetworkMessagingComponent NetworkMessagingComponent; + internal MessageDispatcherComponent MessageDispatcherComponent; + /// + /// 漫游的列表。 + /// + private readonly Dictionary _roaming = new Dictionary(); + + internal void Initialize(Session session) + { + session.SessionRoamingComponent = this; + + var scene = session.Scene; + TimerComponent = scene.TimerComponent; + NetworkMessagingComponent = scene.NetworkMessagingComponent; + MessageDispatcherComponent = scene.MessageDispatcherComponent; + RoamingLock = scene.CoroutineLockComponent.Create(this.GetType().TypeHandle.Value.ToInt64()); + RoamingMessageLock = scene.CoroutineLockComponent.Create(this.GetType().TypeHandle.Value.ToInt64()); + } + + #region Get + + /// + /// 尝试获取一个漫游。 + /// + /// + /// + /// + public bool TryGetRoaming(int roamingType, out Roaming roaming) + { + return _roaming.TryGetValue(roamingType, out roaming); + } + + #endregion + + #region Link + + /// + /// 建立漫游关系。 + /// + /// 要建立漫游协议的目标Scene的SceneConfig。 + /// 需要转发的Session + /// 要创建的漫游协议类型。 + /// 如果建立完成会返回为0,其余不为0的都是发生错误了。可以通过InnerErrorCode.cs来查看错误。 + public async FTask Link(Session session, SceneConfig targetSceneConfig, int roamingTyp) + { + return await Link(targetSceneConfig.RouteId, session.RuntimeId, roamingTyp); + } + + /// + /// 建立漫游关系。 + /// + /// 要建立漫游协议的目标Scene的RouteId。 + /// 需要转发的Session的RouteId。 + /// 要创建的漫游协议类型。 + /// 如果建立完成会返回为0,其余不为0的都是发生错误了。可以通过InnerErrorCode.cs来查看错误。 + public async FTask Link(long targetSceneRouteId, long forwardSessionRouteId, int roamingType) + { + if (_roaming.ContainsKey(roamingType)) + { + return InnerErrorCode.ErrLinkRoamingAlreadyExists; + } + + var response = (I_LinkRoamingResponse)await Scene.NetworkMessagingComponent.CallInnerRoute(targetSceneRouteId, + new I_LinkRoamingRequest() + { + RoamingId = Id, + RoamingType = roamingType, + ForwardSessionRouteId = forwardSessionRouteId, + SceneRouteId = Scene.RuntimeId + }); + + if (response.ErrorCode != 0) + { + return response.ErrorCode; + } + + var roaming = Entity.Create(Scene, true, true); + roaming.TerminusId = response.TerminusId; + roaming.TargetSceneRouteId = targetSceneRouteId; + roaming.ForwardSessionRouteId = forwardSessionRouteId; + roaming.SessionRoamingComponent = this; + roaming.RoamingType = roamingType; + roaming.RoamingLock = RoamingLock; + _roaming.Add(roamingType, roaming); + return 0; + } + + #endregion + + #region UnLink + + /// + /// 断开当前的所有漫游关系。 + /// 要移除的RoamingType,默认不设置是移除所有漫游。 + /// + public async FTask UnLink(int removeRoamingType = 0) + { + switch (removeRoamingType) + { + case 0: + { + foreach (var (roamingType,roaming) in _roaming) + { + try + { + var errorCode = await roaming.Disconnect(); + + if (errorCode != 0) + { + Log.Warning($"roaming roamingId:{Id} roamingType:{roamingType} disconnect errorCode:{errorCode}"); + } + } + finally + { + roaming.Dispose(); + } + } + + _roaming.Clear(); + return; + } + default: + { + if (!_roaming.Remove(removeRoamingType, out var roaming)) + { + return; + } + + var errorCode = await roaming.Disconnect(); + + if (errorCode != 0) + { + Log.Warning($"roaming roamingId:{Id} roamingType:{removeRoamingType} disconnect errorCode:{errorCode}"); + } + + roaming.Dispose(); + return; + } + } + } + + #endregion + + #region Message + + internal async FTask Send(int roamingType, Type requestType, APackInfo packInfo) + { + await Call(roamingType, requestType, packInfo); + } + + internal async FTask Call(int roamingType, Type requestType, APackInfo packInfo) + { + if (IsDisposed) + { + return MessageDispatcherComponent.CreateResponse(requestType, InnerErrorCode.ErrNotFoundRoaming); + } + + packInfo.IsDisposed = true; + + if (!_roaming.TryGetValue(roamingType, out var roaming)) + { + return MessageDispatcherComponent.CreateResponse(requestType, InnerErrorCode.ErrNotFoundRoaming); + } + + var failCount = 0; + var runtimeId = RuntimeId; + var routeId = roaming.TerminusId; + IResponse iRouteResponse = null; + + try + { + using (await RoamingMessageLock.Wait(roamingType, "RoamingComponent Call MemoryStream")) + { + while (!IsDisposed) + { + if (routeId == 0) + { + routeId = await roaming.GetTerminusId(); + } + + if (routeId == 0) + { + return MessageDispatcherComponent.CreateResponse(requestType, InnerErrorCode.ErrNotFoundRoaming); + } + + iRouteResponse = await NetworkMessagingComponent.CallInnerRoute(routeId, requestType, packInfo); + + if (runtimeId != RuntimeId) + { + iRouteResponse.ErrorCode = InnerErrorCode.ErrRoamingTimeout; + } + + switch (iRouteResponse.ErrorCode) + { + case InnerErrorCode.ErrRouteTimeout: + case InnerErrorCode.ErrRoamingTimeout: + { + return iRouteResponse; + } + case InnerErrorCode.ErrNotFoundRoute: + case InnerErrorCode.ErrNotFoundRoaming: + { + if (++failCount > 20) + { + Log.Error($"RoamingComponent.Call failCount > 20 route send message fail, LinkRoamingId: {routeId}"); + return iRouteResponse; + } + + await TimerComponent.Net.WaitAsync(100); + + if (runtimeId != RuntimeId) + { + iRouteResponse.ErrorCode = InnerErrorCode.ErrNotFoundRoaming; + } + routeId = 0; + continue; + } + default: + { + return iRouteResponse; // 对于其他情况,直接返回响应,无需额外处理 + } + } + } + } + } + finally + { + packInfo.Dispose(); + } + + return iRouteResponse; + } + + #endregion +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Roaming/Component/SessionRoamingFlgComponent.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Roaming/Component/SessionRoamingFlgComponent.cs new file mode 100644 index 0000000..23f4c33 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Roaming/Component/SessionRoamingFlgComponent.cs @@ -0,0 +1,23 @@ +#if FANTASY_NET +using Fantasy.Async; +using Fantasy.Entitas; +namespace Fantasy.Network.Roaming; + +internal sealed class SessionRoamingFlgComponent : Entity +{ + public int DelayRemove; + + public override void Dispose() + { + DisposeAsync().Coroutine(); + } + + private async FTask DisposeAsync() + { + var roamingId = Id; + await Scene.RoamingComponent.Remove(roamingId, 0, DelayRemove); + DelayRemove = 0; + base.Dispose(); + } +} +#endif diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Roaming/Component/TerminusComponent.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Roaming/Component/TerminusComponent.cs new file mode 100644 index 0000000..0185261 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Roaming/Component/TerminusComponent.cs @@ -0,0 +1,138 @@ +#if FANTASY_NET +using Fantasy.Async; +using Fantasy.DataStructure.Dictionary; +using Fantasy.Entitas; +using Fantasy.Network; +#pragma warning disable CS8601 // Possible null reference assignment. +#pragma warning disable CS8619 // Nullability of reference types in value doesn't match target type. +#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. + +namespace Fantasy.Network.Roaming; + +/// +/// 当Terminus创建完成后发送的事件参数 +/// +public struct OnCreateTerminus +{ + /// + /// 获取与事件关联的场景实体。 + /// + public readonly Scene Scene; + /// + /// 获取与事件关联的Terminus。 + /// + public readonly Terminus Terminus; + /// + /// 初始化一个新的 OnCreateTerminus 实例。 + /// + /// + /// + public OnCreateTerminus(Scene scene, Terminus terminus) + { + Scene = scene; + Terminus = terminus; + } +} + +/// +/// 漫游终端管理组件。 +/// 这个组件不需要手动挂载,会在Scene启动的时候自动挂载这个组件。 +/// +public sealed class TerminusComponent : Entity +{ + /// + /// 漫游终端的实体集合。 + /// + private readonly Dictionary _terminals = new(); + + /// + /// Dispose + /// + public override void Dispose() + { + foreach (var (_, terminus) in _terminals) + { + terminus.Dispose(); + } + + _terminals.Clear(); + base.Dispose(); + } + + /// + /// 创建一个新的漫游终端。 + /// + /// + /// + /// + /// + /// + public async FTask<(uint, Terminus)> Create(long roamingId, int roamingType, long forwardSessionRouteId, long forwardSceneRouteId) + { + if (_terminals.ContainsKey(roamingId)) + { + return (InnerErrorCode.ErrAddRoamingTerminalAlreadyExists, null); + } + + var terminus = roamingId == 0 + ? Entity.Create(Scene, false, true) + : Entity.Create(Scene, roamingId, false, true); + terminus.RoamingType = roamingType; + terminus.TerminusId = terminus.RuntimeId; + terminus.ForwardSceneRouteId = forwardSceneRouteId; + terminus.ForwardSessionRouteId = forwardSessionRouteId; + terminus.RoamingMessageLock = Scene.CoroutineLockComponent.Create(terminus.Type.TypeHandle.Value.ToInt64()); + await Scene.EventComponent.PublishAsync(new OnCreateTerminus(Scene, terminus)); + _terminals.Add(terminus.Id, terminus); + return (0U, terminus); + } + + /// + /// 添加一个漫游终端。 + /// + /// + public void AddTerminus(Terminus terminus) + { + _terminals.Add(terminus.Id, terminus); + } + + /// + /// 根据roamingId获取一个漫游终端。 + /// + /// + /// + /// + public bool TryGetTerminus(long roamingId, out Terminus terminus) + { + return _terminals.TryGetValue(roamingId, out terminus); + } + + /// + /// 根据roamingId获取一个漫游终端。 + /// + /// + /// + public Terminus GetTerminus(long roamingId) + { + return _terminals[roamingId]; + } + + /// + /// 根据roamingId移除一个漫游终端。 + /// + /// + /// + public void RemoveTerminus(long roamingId, bool isDispose = true) + { + if (!_terminals.Remove(roamingId, out var terminus)) + { + return; + } + + if (isDispose) + { + terminus.Dispose(); + } + } +} +#endif diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Roaming/Entity/Roaming.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Roaming/Entity/Roaming.cs new file mode 100644 index 0000000..33a5355 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Roaming/Entity/Roaming.cs @@ -0,0 +1,132 @@ +#if FANTASY_NET +using Fantasy.Async; +using Fantasy.Entitas; +using Fantasy.InnerMessage; + +#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. + +namespace Fantasy.Network.Roaming; + +/// +/// 漫游实体 +/// +public sealed class Roaming : Entity +{ + /// + /// 连接到漫游TerminusId。 + /// 也可以理解为目标实体的RouteId。 + /// + internal long TerminusId; + /// + /// 漫游目标Scene的RouteId。 + /// + public long TargetSceneRouteId { get; internal set; } + /// + /// 漫游转发Session的RouteId。 + /// + public long ForwardSessionRouteId { get; internal set; } + /// + /// 当前漫游类型。 + /// + public int RoamingType { get; internal set; } + /// + /// 协程锁。 + /// + internal CoroutineLock RoamingLock; + /// + /// 当前正在执行的协程锁。 + /// + private WaitCoroutineLock? _waitCoroutineLock; + /// + /// 关联Session的漫游组件。 + /// + internal SessionRoamingComponent SessionRoamingComponent; + /// + /// 获得当前漫游对应终端的TerminusId。 + /// + /// + internal async FTask GetTerminusId() + { + using (await RoamingLock.Wait(RoamingType,"Roaming.cs GetTerminusId")) + { + return TerminusId; + } + } + /// + /// 设置当前漫游对应的终端的TerminusId。 + /// + /// + internal async FTask SetTerminusId(long terminusId) + { + using (await RoamingLock.Wait(RoamingType,"Roaming.cs SetTerminusId")) + { + TerminusId = terminusId; + } + } + /// + /// 锁定TerminusId。 + /// + internal async FTask LockTerminusId() + { + _waitCoroutineLock = await RoamingLock.Wait(RoamingType,"Roaming.cs LockTerminusId"); + } + + /// + /// 解锁TerminusId。 + /// + /// + /// + internal void UnLockTerminusId(long terminusId, long targetSceneRouteId) + { + if (_waitCoroutineLock == null) + { + Log.Error("terminusId unlock waitCoroutineLock is null"); + return; + } + + TerminusId = terminusId; + TargetSceneRouteId = targetSceneRouteId; + _waitCoroutineLock.Dispose(); + _waitCoroutineLock = null; + } + + /// + /// 断开当前漫游的连接。 + /// + /// + public async FTask Disconnect() + { + var response = + await Scene.NetworkMessagingComponent.CallInnerRoute(TargetSceneRouteId, new I_UnLinkRoamingRequest() + { + RoamingId = SessionRoamingComponent.Id + }); + return response.ErrorCode; + } + /// + /// 销毁方法 + /// + public override void Dispose() + { + if (IsDisposed) + { + return; + } + + if (_waitCoroutineLock != null) + { + _waitCoroutineLock.Dispose(); + _waitCoroutineLock = null; + } + + TerminusId = 0; + TargetSceneRouteId = 0; + ForwardSessionRouteId = 0; + + RoamingLock = null; + SessionRoamingComponent = null; + base.Dispose(); + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Roaming/Entity/Terminus.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Roaming/Entity/Terminus.cs new file mode 100644 index 0000000..8d4448b --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Roaming/Entity/Terminus.cs @@ -0,0 +1,338 @@ +#if FANTASY_NET +using Fantasy.Async; +using Fantasy.Entitas; +using Fantasy.InnerMessage; +using Fantasy.Network; +using Fantasy.Network.Interface; +using Fantasy.Scheduler; +using MongoDB.Bson.Serialization.Attributes; +// ReSharper disable UnassignedField.Global +// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract +#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type. +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. +#pragma warning disable CS8603 // Possible null reference return. +namespace Fantasy.Network.Roaming; + +/// +/// 漫游终端实体 +/// +public sealed class Terminus : Entity +{ + /// + /// 当前漫游终端的TerminusId。 + /// 可以通过 TerminusId 发送消息给这个漫游终端。 + /// 也可以理解为实体的RuntimeId。 + /// + internal long TerminusId; + /// + /// 当前漫游终端的类型。 + /// + [BsonElement("r")] + internal int RoamingType; + /// + /// 漫游转发Session所在的Scene的RouteId。 + /// + [BsonElement("s")] + internal long ForwardSceneRouteId; + /// + /// 漫游转发Session的RouteId。 + /// 不知道原理千万不要手动赋值这个。 + /// + [BsonElement("f")] + internal long ForwardSessionRouteId; + /// + /// 关联的玩家实体 + /// + [BsonElement("e")] + public Entity TerminusEntity; + /// + /// 漫游消息锁。 + /// + [BsonIgnore] + internal CoroutineLock RoamingMessageLock; + /// + /// 获得转发的SessionRouteId,可以通过这个Id来发送消息来自动转发到客户端。 + /// + public long SessionRouteId => ForwardSessionRouteId; + /// + /// 存放其他漫游终端的Id。 + /// 通过这个Id可以发送消息给它。 + /// + [BsonIgnore] + private readonly Dictionary _roamingTerminusId = new Dictionary(); + /// + /// 创建关联的终端实体。 + /// 创建完成后,接收消息都是由关联的终端实体来处理。 + /// 注意,当你销毁这个实体的时候,并不能直接销毁Terminus,会导致无法接收到漫游消息。 + /// + /// + /// + public T LinkTerminusEntity() where T : Entity, new() + { + if (TerminusEntity != null) + { + Log.Error($"TerminusEntity:{TerminusEntity.Type.FullName} Already exists!"); + return null; + } + + var t = Entity.Create(Scene, true, true); + TerminusEntity = t; + TerminusId = TerminusEntity.RuntimeId; + return t; + } + + /// + /// 关联的终端实体。 + /// 注意,当你销毁这个实体的时候,并不能直接销毁Terminus,会导致无法接收到漫游消息 + /// + /// + public void LinkTerminusEntity(Entity entity) + { + if (entity == null) + { + Log.Error("Entity cannot be empty"); + return; + } + + if (TerminusEntity != null) + { + Log.Error($"TerminusEntity:{TerminusEntity.Type.FullName} Already exists!"); + return; + } + + TerminusEntity = entity; + TerminusId = TerminusEntity.RuntimeId; + } + + #region Transfer + + /// + /// 传送漫游终端 + /// 传送完成后,漫游终端和关联的玩家实体都会被销毁。 + /// 所以如果有其他组件关联这个实体,要提前记录好Id,方便传送后清理。 + /// + /// + public async FTask StartTransfer(long targetSceneRouteId) + { + var currentSceneRouteId = Scene.SceneConfig.RouteId; + if (targetSceneRouteId == currentSceneRouteId) + { + Log.Warning($"Unable to teleport to your own scene targetSceneRouteId:{targetSceneRouteId} == currentSceneRouteId:{currentSceneRouteId}"); + return 0; + } + + try + { + // 传送目标服务器之前要先锁定,防止再传送过程中还有其他消息发送过来。 + var lockErrorCode = await Lock(); + if (lockErrorCode != 0) + { + return lockErrorCode; + } + // 开始执行传送请求。 + var response = (I_TransferTerminusResponse)await Scene.NetworkMessagingComponent.CallInnerRoute( + targetSceneRouteId, + new I_TransferTerminusRequest() + { + Terminus = this + }); + if (response.ErrorCode != 0) + { + // 如果传送出现异常,需要先解锁,不然会出现一直卡死的问题。 + await UnLock(); + return response.ErrorCode; + } + // 在当前Scene下移除漫游终端。 + Scene.TerminusComponent.RemoveTerminus(Id); + } + catch (Exception e) + { + Log.Error(e); + // 如果代码执行出现任何异常,要先去解锁,避免会出现卡死的问题。 + await UnLock(); + return InnerErrorCode.ErrTerminusStartTransfer; + } + + return 0; + } + + /// + /// 传送完成。 + /// 当传送完成后,需要清理漫游终端。 + /// + /// + public async FTask TransferComplete(Scene scene) + { + // 首先恢复漫游终端的序列化数据。并且注册到框架中。 + Deserialize(scene); + TerminusId = RuntimeId; + if (TerminusEntity != null) + { + TerminusEntity.Deserialize(scene); + TerminusId = TerminusEntity.RuntimeId; + } + // 然后要解锁下漫游 + return await UnLock(); + } + + /// + /// 锁定漫游当执行锁定了后,所有消息都会被暂时放入队列中不会发送。 + /// 必须要解锁后才能继续发送消息。 + /// + /// + public async FTask Lock() + { + var response = await Scene.NetworkMessagingComponent.CallInnerRoute(ForwardSceneRouteId, + new I_LockTerminusIdRequest() + { + SessionRuntimeId = ForwardSessionRouteId, + RoamingType = RoamingType + }); + return response.ErrorCode; + } + + /// + /// 锁定漫游 + /// + /// + public async FTask UnLock() + { + var response = await Scene.NetworkMessagingComponent.CallInnerRoute(ForwardSceneRouteId, + new I_UnLockTerminusIdRequest() + { + SessionRuntimeId = ForwardSessionRouteId, + RoamingType = RoamingType, + TerminusId = TerminusId, + TargetSceneRouteId = Scene.RouteId + }); + return response.ErrorCode; + } + + #endregion + + #region Message + + private async FTask GetTerminusId(int roamingType) + { + if (IsDisposed) + { + return 0; + } + + var response = (I_GetTerminusIdResponse)await Scene.NetworkMessagingComponent.CallInnerRoute( + ForwardSceneRouteId, + new I_GetTerminusIdRequest() + { + SessionRuntimeId = ForwardSessionRouteId, + RoamingType = roamingType + }); + return response.TerminusId; + } + + /// + /// 发送一个消息给客户端 + /// + /// + public void Send(IRouteMessage message) + { + Scene.NetworkMessagingComponent.SendInnerRoute(ForwardSessionRouteId, message); + } + /// + /// 发送一个漫游消息 + /// + /// + /// + public void Send(int roamingType, IRoamingMessage message) + { + Call(roamingType, message).Coroutine(); + } + + /// + /// 发送一个漫游RPC消息。 + /// + /// + /// + /// + public async FTask Call(int roamingType, IRoamingMessage request) + { + if (IsDisposed) + { + return Scene.MessageDispatcherComponent.CreateResponse(request.GetType(), InnerErrorCode.ErrNotFoundRoaming); + } + + if (roamingType == RoamingType) + { + Log.Warning($"Does not support sending messages to the same scene as roamingType currentRoamingType:{RoamingType} roamingType:{roamingType}"); + return Scene.MessageDispatcherComponent.CreateResponse(request.GetType(), InnerErrorCode.ErrNotFoundRoaming); + } + + var failCount = 0; + var runtimeId = RuntimeId; + IResponse iRouteResponse = null; + _roamingTerminusId.TryGetValue(roamingType, out var routeId); + + using (await RoamingMessageLock.Wait(roamingType, "Terminus Call request")) + { + while (!IsDisposed) + { + if (routeId == 0) + { + routeId = await GetTerminusId(roamingType); + + if (routeId != 0) + { + _roamingTerminusId[roamingType] = routeId; + } + else + { + return Scene.MessageDispatcherComponent.CreateResponse(request.GetType(), InnerErrorCode.ErrNotFoundRoaming); + } + } + + iRouteResponse = await Scene.NetworkMessagingComponent.CallInnerRoute(routeId, request); + + if (runtimeId != RuntimeId) + { + iRouteResponse.ErrorCode = InnerErrorCode.ErrRoamingTimeout; + } + + switch (iRouteResponse.ErrorCode) + { + case InnerErrorCode.ErrRouteTimeout: + case InnerErrorCode.ErrRoamingTimeout: + { + return iRouteResponse; + } + case InnerErrorCode.ErrNotFoundRoute: + case InnerErrorCode.ErrNotFoundRoaming: + { + if (++failCount > 20) + { + Log.Error($"Terminus.Call failCount > 20 route send message fail, TerminusId: {routeId}"); + return iRouteResponse; + } + + await Scene.TimerComponent.Net.WaitAsync(100); + + if (runtimeId != RuntimeId) + { + iRouteResponse.ErrorCode = InnerErrorCode.ErrNotFoundRoaming; + } + + routeId = 0; + continue; + } + default: + { + return iRouteResponse; // 对于其他情况,直接返回响应,无需额外处理 + } + } + } + } + + return iRouteResponse; + } + + #endregion +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Roaming/Handler/I_GetTerminusIdRequestHandler.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Roaming/Handler/I_GetTerminusIdRequestHandler.cs new file mode 100644 index 0000000..90f7ab9 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Roaming/Handler/I_GetTerminusIdRequestHandler.cs @@ -0,0 +1,32 @@ +#if FANTASY_NET +using Fantasy.Async; +using Fantasy.InnerMessage; +using Fantasy.Network; +using Fantasy.Network.Interface; +using Fantasy.Network.Roaming; +// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract + +namespace Fantasy.Roaming.Handler; + +internal sealed class I_GetTerminusIdRequestHandler : RouteRPC +{ + protected override async FTask Run(Scene scene, I_GetTerminusIdRequest request, I_GetTerminusIdResponse response, Action reply) + { + if (!scene.TryGetEntity(request.SessionRuntimeId, out var sessionEntity)) + { + response.ErrorCode = InnerErrorCode.ErrLockTerminusIdNotFoundSession; + return; + } + + var session = (Session)sessionEntity; + + if (!scene.RoamingComponent.TryGet(session, out var sessionRoamingComponent) || !sessionRoamingComponent.TryGetRoaming(request.RoamingType, out var sessionRoaming)) + { + response.ErrorCode = InnerErrorCode.ErrLockTerminusIdNotFoundRoamingType; + return; + } + + response.TerminusId = await sessionRoaming.GetTerminusId(); + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Roaming/Handler/I_LinkRoamingRequestHandler.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Roaming/Handler/I_LinkRoamingRequestHandler.cs new file mode 100644 index 0000000..6efb58c --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Roaming/Handler/I_LinkRoamingRequestHandler.cs @@ -0,0 +1,26 @@ +#if FANTASY_NET +using Fantasy.Async; +using Fantasy.InnerMessage; +using Fantasy.Network.Interface; +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + +namespace Fantasy.Roaming.Handler; + +public sealed class I_LinkRoamingRequestHandler : RouteRPC +{ + protected override async FTask Run(Scene scene, I_LinkRoamingRequest request, I_LinkRoamingResponse response, Action reply) + { + var (errorCode, roamingTerminal) = await scene.TerminusComponent.Create( + request.RoamingId, request.RoamingType, + request.ForwardSessionRouteId, request.SceneRouteId); + + if (errorCode != 0) + { + response.ErrorCode = errorCode; + return; + } + + response.TerminusId = roamingTerminal.TerminusId; + } +} +#endif diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Roaming/Handler/I_LockTerminusIdRequestHandler.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Roaming/Handler/I_LockTerminusIdRequestHandler.cs new file mode 100644 index 0000000..c530a1e --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Roaming/Handler/I_LockTerminusIdRequestHandler.cs @@ -0,0 +1,35 @@ +#if FANTASY_NET +using Fantasy.Async; +using Fantasy.InnerMessage; +using Fantasy.Network; +using Fantasy.Network.Interface; +// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + +namespace Fantasy.Roaming.Handler; + +/// +/// 内部网络漫游锁定的请求处理。 +/// +internal sealed class I_LockTerminusIdRequestHandler : RouteRPC +{ + protected override async FTask Run(Scene scene, I_LockTerminusIdRequest request, I_LockTerminusIdResponse response, Action reply) + { + if (!scene.TryGetEntity(request.SessionRuntimeId, out var sessionEntity)) + { + response.ErrorCode = InnerErrorCode.ErrLockTerminusIdNotFoundSession; + return; + } + + var session = (Session)sessionEntity; + + if (!scene.RoamingComponent.TryGet(session, out var sessionRoamingComponent) || !sessionRoamingComponent.TryGetRoaming(request.RoamingType, out var sessionRoaming)) + { + response.ErrorCode = InnerErrorCode.ErrLockTerminusIdNotFoundRoamingType; + return; + } + + await sessionRoaming.LockTerminusId(); + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Roaming/Handler/I_TransferTerminusRequestHandler.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Roaming/Handler/I_TransferTerminusRequestHandler.cs new file mode 100644 index 0000000..162171f --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Roaming/Handler/I_TransferTerminusRequestHandler.cs @@ -0,0 +1,22 @@ +#if FANTASY_NET +using Fantasy.Async; +using Fantasy.InnerMessage; +using Fantasy.Network.Interface; +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + +namespace Fantasy.Roaming.Handler; + +/// +/// 传送漫游Terminus的请求处理 +/// +internal sealed class I_TransferTerminusRequestHandler : RouteRPC +{ + protected override async FTask Run(Scene scene, I_TransferTerminusRequest request, I_TransferTerminusResponse response, Action reply) + { + // 添加Terminus到当前Scene下。 + scene.TerminusComponent.AddTerminus(request.Terminus); + // 执行Terminus的传送完成逻辑。 + await request.Terminus.TransferComplete(scene); + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Roaming/Handler/I_UnLinkRoamingRequestHandler.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Roaming/Handler/I_UnLinkRoamingRequestHandler.cs new file mode 100644 index 0000000..5849b11 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Roaming/Handler/I_UnLinkRoamingRequestHandler.cs @@ -0,0 +1,16 @@ +#if FANTASY_NET +using Fantasy.Async; +using Fantasy.InnerMessage; +using Fantasy.Network.Interface; + +namespace Fantasy.Roaming.Handler; + +internal sealed class I_UnLinkRoamingRequestHandler : RouteRPC +{ + protected override async FTask Run(Scene scene, I_UnLinkRoamingRequest request, I_UnLinkRoamingResponse response, Action reply) + { + scene.TerminusComponent.RemoveTerminus(request.RoamingId, request.DisposeRoaming); + await FTask.CompletedTask; + } +} +#endif diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Roaming/Handler/I_UnLockTerminusIdRequestHandler.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Roaming/Handler/I_UnLockTerminusIdRequestHandler.cs new file mode 100644 index 0000000..8a04079 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Roaming/Handler/I_UnLockTerminusIdRequestHandler.cs @@ -0,0 +1,36 @@ +#if FANTASY_NET +using Fantasy.Async; +using Fantasy.InnerMessage; +using Fantasy.Network; +using Fantasy.Network.Interface; +// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + +namespace Fantasy.Roaming.Handler; + +/// +/// 内部网络漫游解锁的请求处理。 +/// +internal sealed class I_UnLockTerminusIdRequestHandler : RouteRPC +{ + protected override async FTask Run(Scene scene, I_UnLockTerminusIdRequest request, I_UnLockTerminusIdResponse response, Action reply) + { + if (!scene.TryGetEntity(request.SessionRuntimeId, out var sessionEntity)) + { + response.ErrorCode = InnerErrorCode.ErrUnLockTerminusIdNotFoundSession; + return; + } + + var session = (Session)sessionEntity; + + if (!scene.RoamingComponent.TryGet(session, out var sessionRoamingComponent) || !sessionRoamingComponent.TryGetRoaming(request.RoamingType, out var sessionRoaming)) + { + response.ErrorCode = InnerErrorCode.ErrLockTerminusIdNotFoundRoamingType; + return; + } + + sessionRoaming.UnLockTerminusId(request.TerminusId, request.TargetSceneRouteId); + await FTask.CompletedTask; + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Route/RouteComponent.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Route/RouteComponent.cs new file mode 100644 index 0000000..1ce1fe8 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Route/RouteComponent.cs @@ -0,0 +1,82 @@ +using Fantasy.Entitas; +using Fantasy.Entitas.Interface; + +#if FANTASY_NET +namespace Fantasy.Network; + +/// +/// RouteComponent的AwakeSystem +/// +public sealed class RouteComponentAwakeSystem : AwakeSystem +{ + /// + /// Awake + /// + /// + /// + protected override void Awake(RouteComponent self) + { + ((Session)self.Parent).RouteComponent = self; + } +} + +/// +/// 自定义Route组件、如果要自定义Route协议必须使用这个组件 +/// +public sealed class RouteComponent : Entity +{ + /// + /// 存储路由类型和路由ID的映射关系。 + /// + public readonly Dictionary RouteAddress = new Dictionary(); + + /// + /// 添加路由类型和路由ID的映射关系。 + /// + /// 路由类型。 + /// 路由ID。 + public void AddAddress(long routeType, long routeId) + { + RouteAddress.Add(routeType, routeId); + } + + /// + /// 移除指定路由类型的映射关系。 + /// + /// 路由类型。 + public void RemoveAddress(long routeType) + { + RouteAddress.Remove(routeType); + } + + /// + /// 获取指定路由类型的路由ID。 + /// + /// 路由类型。 + /// 路由ID。 + public long GetRouteId(long routeType) + { + return RouteAddress.GetValueOrDefault(routeType, 0); + } + + /// + /// 尝试获取指定路由类型的路由ID。 + /// + /// 路由类型。 + /// 输出的路由ID。 + /// 如果获取成功返回true,否则返回false。 + public bool TryGetRouteId(long routeType, out long routeId) + { + return RouteAddress.TryGetValue(routeType, out routeId); + } + + /// + /// 释放组件资源,清空映射关系。 + /// + public override void Dispose() + { + RouteAddress.Clear(); + base.Dispose(); + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Session/Component/ConsoleSessionHeartbeatComponent.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Session/Component/ConsoleSessionHeartbeatComponent.cs new file mode 100644 index 0000000..dc4677e --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Session/Component/ConsoleSessionHeartbeatComponent.cs @@ -0,0 +1,156 @@ +// ReSharper disable MemberCanBePrivate.Global + +#if FANTASY_CONSOLE + +using System; +using Fantasy.Async; +using Fantasy.Entitas; +using Fantasy.Entitas.Interface; +using Fantasy.Helper; +using Fantasy.InnerMessage; +using Fantasy.Timer; +#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. + +namespace Fantasy.Network +{ + public class SessionHeartbeatComponentAwakeSystem : AwakeSystem + { + protected override void Awake(SessionHeartbeatComponent self) + { + self.TimerComponent = self.Scene.TimerComponent; + } + } + + /// + /// 负责管理会话心跳的组件。 + /// + public class SessionHeartbeatComponent : Entity + { + public int TimeOut; + public long TimerId; + public long LastTime; + public long SelfRunTimeId; + public long TimeOutTimerId; + public long SessionRunTimeId; + public TimerComponent TimerComponent; + public EntityReference Session; + private readonly PingRequest _pingRequest = new PingRequest(); + + public int Ping { get; private set; } + + public override void Dispose() + { + if (IsDisposed) + { + return; + } + + Stop(); + Ping = 0; + Session = null; + TimeOut = 0; + SelfRunTimeId = 0; + base.Dispose(); + } + + /// + /// 使用指定的间隔启动心跳功能。 + /// + /// 以毫秒为单位的心跳请求发送间隔。 + /// 设置与服务器的通信超时时间,如果超过这个时间限制,将自动断开会话(Session)。 + /// 用于检测与服务器连接超时频率。 + public void Start(int interval, int timeOut = 2000, int timeOutInterval = 3000) + { + TimeOut = timeOut + interval; + Session = (Session)Parent; + SelfRunTimeId = RuntimeId; + LastTime = TimeHelper.Now; + + if (TimerComponent == null) + { + Log.Error("请在Unity的菜单执行Fantasy->Generate link.xml再重新打包"); + return; + } + + TimerId = TimerComponent.Net.RepeatedTimer(interval, () => RepeatedSend().Coroutine()); + TimeOutTimerId = TimerComponent.Net.RepeatedTimer(timeOutInterval, CheckTimeOut); + } + + private void CheckTimeOut() + { + if (TimeHelper.Now - LastTime < TimeOut) + { + return; + } + + Session entityReference = Session; + + if (entityReference == null) + { + return; + } + + entityReference.Dispose(); + } + + /// + /// 停止心跳功能。 + /// + public void Stop() + { + if (TimerId != 0) + { + TimerComponent?.Net.Remove(ref TimerId); + } + + if (TimeOutTimerId != 0) + { + TimerComponent?.Net.Remove(ref TimeOutTimerId); + } + } + + /// + /// 异步发送心跳请求并处理响应。 + /// + /// 表示进行中操作的异步任务。 + private async FTask RepeatedSend() + { + if (SelfRunTimeId != RuntimeId) + { + Stop(); + return; + } + + Session session = Session; + + if (session == null) + { + Dispose(); + return; + } + + try + { + var requestTime = TimeHelper.Now; + + var pingResponse = (PingResponse)await session.Call(_pingRequest); + + if (pingResponse.ErrorCode != 0) + { + return; + } + + var responseTime = TimeHelper.Now; + LastTime = responseTime; + Ping = (int)(responseTime - requestTime) / 2; + TimeHelper.TimeDiff = pingResponse.Now + Ping - responseTime; + } + catch (Exception) + { + Dispose(); + } + } + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Session/Component/SessionIdleCheckerComponent.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Session/Component/SessionIdleCheckerComponent.cs new file mode 100644 index 0000000..1edc1cf --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Session/Component/SessionIdleCheckerComponent.cs @@ -0,0 +1,104 @@ +using Fantasy.Entitas; +using Fantasy.Entitas.Interface; +using Fantasy.Helper; +using Fantasy.Timer; + +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. +#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +#if FANTASY_NET +namespace Fantasy.Network; + +public class SessionIdleCheckerComponentAwakeSystem : AwakeSystem +{ + protected override void Awake(SessionIdleCheckerComponent self) + { + self.TimerComponent = self.Scene.TimerComponent; + } +} + +/// +/// 负责检查会话空闲超时的组件。 +/// +public class SessionIdleCheckerComponent : Entity +{ + /// + /// 空闲超时时间(毫秒) + /// + private long _timeOut; + /// + /// 检查计时器的 ID + /// + private long _timerId; + /// + /// 用于确保组件完整性的自身运行时 ID + /// + private long _selfRuntimeId; + /// + /// 对会话对象的引用 + /// + private Session _session; + public TimerComponent TimerComponent; + + /// + /// 重写 Dispose 方法以释放资源。 + /// + public override void Dispose() + { + Stop(); // 停止检查计时器 + _timeOut = 0; // 重置空闲超时时间 + _selfRuntimeId = 0; // 重置自身运行时 ID + _session = null; // 清除会话引用 + base.Dispose(); + } + + /// + /// 使用指定的间隔和空闲超时时间启动空闲检查功能。 + /// + /// 以毫秒为单位的检查间隔。 + /// 以毫秒为单位的空闲超时时间。 + public void Start(int interval, int timeOut) + { + _timeOut = timeOut; + _session = (Session)Parent; + _selfRuntimeId = RuntimeId; + // 安排重复计时器,在指定的间隔内执行 Check 方法 + _timerId = TimerComponent.Net.RepeatedTimer(interval, Check); + } + + /// + /// 停止空闲检查功能。 + /// + public void Stop() + { + if (_timerId == 0) + { + return; + } + + TimerComponent.Net.Remove(ref _timerId); + } + + /// + /// 执行空闲检查操作。 + /// + private void Check() + { + if (_selfRuntimeId != RuntimeId || IsDisposed || _session == null) + { + Stop(); + return; + } + + var timeNow = TimeHelper.Now; + + if (timeNow - _session.LastReceiveTime < _timeOut) + { + return; + } + + Log.Warning($"session timeout id:{Id} timeNow:{timeNow} _session.LastReceiveTime:{_session.LastReceiveTime} _timeOut:{_timeOut}"); + _session.Dispose(); + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Session/Component/UnitySessionHeartbeatComponent.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Session/Component/UnitySessionHeartbeatComponent.cs new file mode 100644 index 0000000..83c7cbf --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Session/Component/UnitySessionHeartbeatComponent.cs @@ -0,0 +1,156 @@ +// ReSharper disable MemberCanBePrivate.Global + +using System; +using Fantasy.Async; +using Fantasy.Entitas; +using Fantasy.Entitas.Interface; +using Fantasy.Helper; +using Fantasy.InnerMessage; +using Fantasy.Timer; + +#if FANTASY_UNITY + +namespace Fantasy.Network +{ + public class SessionHeartbeatComponentAwakeSystem : AwakeSystem + { + protected override void Awake(SessionHeartbeatComponent self) + { + self.TimerComponent = self.Scene.TimerComponent; + } + } + + /// + /// 负责管理会话心跳的组件。 + /// + public class SessionHeartbeatComponent : Entity + { + public int TimeOut; + public long TimerId; + public long LastTime; + public long SelfRunTimeId; + public long TimeOutTimerId; + public TimerComponent TimerComponent; + public EntityReference Session; + private readonly PingRequest _pingRequest = new PingRequest(); + + public int Ping { get; private set; } + + public override void Dispose() + { + if (IsDisposed) + { + return; + } + + Stop(); + Ping = 0; + Session = null; + TimeOut = 0; + LastTime = 0; + SelfRunTimeId = 0; + base.Dispose(); + } + + /// + /// 使用指定的间隔启动心跳功能。 + /// + /// 以毫秒为单位的心跳请求发送间隔。 + /// 设置与服务器的通信超时时间,如果超过这个时间限制,将自动断开会话(Session)。 + /// 用于检测与服务器连接超时频率。 + public void Start(int interval, int timeOut = 5000, int timeOutInterval = 3000) + { + TimeOut = timeOut + interval; + Session = (Session)Parent; + SelfRunTimeId = RuntimeId; + LastTime = TimeHelper.Now; + + if (TimerComponent == null) + { + Log.Error("请在Unity的菜单执行Fantasy->Generate link.xml再重新打包"); + return; + } + + TimerId = TimerComponent.Unity.RepeatedTimer(interval, () => + { + RepeatedSend().Coroutine(); + }); + TimeOutTimerId = TimerComponent.Unity.RepeatedTimer(timeOutInterval, CheckTimeOut); + } + + private void CheckTimeOut() + { + if (TimeHelper.Now - LastTime < TimeOut) + { + return; + } + + Session entityReference = Session; + + if (entityReference == null) + { + return; + } + + entityReference.Dispose(); + } + + /// + /// 停止心跳功能。 + /// + public void Stop() + { + if (TimerId != 0) + { + TimerComponent?.Unity.Remove(ref TimerId); + } + + if (TimeOutTimerId != 0) + { + TimerComponent?.Unity.Remove(ref TimeOutTimerId); + } + } + + /// + /// 异步发送心跳请求并处理响应。 + /// + /// 表示进行中操作的异步任务。 + private async FTask RepeatedSend() + { + if (SelfRunTimeId != RuntimeId) + { + Stop(); + return; + } + + Session session = Session; + + if (session == null) + { + Dispose(); + return; + } + + try + { + var requestTime = TimeHelper.Now; + var pingResponse = (PingResponse)await session.Call(_pingRequest); + + if (pingResponse.ErrorCode != 0) + { + return; + } + + var responseTime = TimeHelper.Now; + LastTime = responseTime; + Ping = (int)(responseTime - requestTime) / 2; + TimeHelper.TimeDiff = pingResponse.Now + Ping - responseTime; + } + catch (Exception) + { + Dispose(); + } + } + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Session/ProcessSession/ProcessScheduler.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Session/ProcessSession/ProcessScheduler.cs new file mode 100644 index 0000000..ec7c30c --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Session/ProcessSession/ProcessScheduler.cs @@ -0,0 +1,269 @@ +#if FANTASY_NET +// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract +using Fantasy.IdFactory; +using Fantasy.Network; +using Fantasy.Network.Interface; +using Fantasy.PacketParser.Interface; +using Fantasy.Platform.Net; + +namespace Fantasy.Scheduler; + +internal static class ProcessScheduler +{ + public static void Scheduler(this ProcessSession session, Type messageType, uint rpcId, long routeId, APackInfo packInfo) + { + switch (packInfo.OpCodeIdStruct.Protocol) + { + case OpCodeType.InnerResponse: + case OpCodeType.InnerRouteResponse: + case OpCodeType.InnerAddressableResponse: + case OpCodeType.InnerRoamingResponse: + case OpCodeType.OuterAddressableResponse: + case OpCodeType.OuterCustomRouteResponse: + case OpCodeType.OuterRoamingResponse: + { + using (packInfo) + { + var sessionScene = session.Scene; + var message = packInfo.Deserialize(messageType); + sessionScene.ThreadSynchronizationContext.Post(() => + { + // 因为有可能是其他Scene线程下发送过来的、所以必须放到当前Scene进程下运行。 + sessionScene.NetworkMessagingComponent.ResponseHandler(rpcId, (IResponse)message); + }); + } + + return; + } + case OpCodeType.InnerRouteMessage: + { + using (packInfo) + { + var sceneId = IdFactoryHelper.RuntimeIdTool.GetSceneId(ref routeId); + + if (!Process.TryGetScene(sceneId, out var scene)) + { + throw new Exception($"not found scene routeId:{routeId}"); + } + + var message = packInfo.Deserialize(messageType); + + scene.ThreadSynchronizationContext.Post(() => + { + var entity = scene.GetEntity(routeId); + var sceneMessageDispatcherComponent = scene.MessageDispatcherComponent; + + if (entity == null || entity.IsDisposed) + { + return; + } + + sceneMessageDispatcherComponent.RouteMessageHandler(session, messageType, entity, message, rpcId).Coroutine(); + }); + } + + return; + } + case OpCodeType.InnerRouteRequest: + { + using (packInfo) + { + var sceneId = IdFactoryHelper.RuntimeIdTool.GetSceneId(ref routeId); + + if (!Process.TryGetScene(sceneId, out var scene)) + { + throw new Exception($"not found scene routeId:{routeId}"); + } + + var message = packInfo.Deserialize(messageType); + + scene.ThreadSynchronizationContext.Post(() => + { + var entity = scene.GetEntity(routeId); + var sceneMessageDispatcherComponent = scene.MessageDispatcherComponent; + + if (entity == null || entity.IsDisposed) + { + sceneMessageDispatcherComponent.FailRouteResponse(session, messageType, InnerErrorCode.ErrNotFoundRoute, rpcId); + return; + } + + sceneMessageDispatcherComponent.RouteMessageHandler(session, messageType, entity, message, rpcId).Coroutine(); + }); + } + + return; + } + case OpCodeType.OuterAddressableMessage: + case OpCodeType.OuterCustomRouteMessage: + case OpCodeType.OuterAddressableRequest: + case OpCodeType.OuterCustomRouteRequest: + case OpCodeType.OuterRoamingMessage: + case OpCodeType.OuterRoamingRequest: + { + using (packInfo) + { + var sceneId = IdFactoryHelper.RuntimeIdTool.GetSceneId(ref routeId); + + if (!Process.TryGetScene(sceneId, out var scene)) + { + throw new NotSupportedException($"not found scene routeId = {routeId}"); + } + + var message = packInfo.Deserialize(messageType); + + scene.ThreadSynchronizationContext.Post(() => + { + var entity = scene.GetEntity(routeId); + + if (entity == null || entity.IsDisposed) + { + scene.MessageDispatcherComponent.FailRouteResponse(session, messageType, InnerErrorCode.ErrNotFoundRoute, rpcId); + return; + } + + scene.MessageDispatcherComponent.RouteMessageHandler(session, messageType, entity, message, rpcId).Coroutine(); + }); + } + return; + } + default: + { + var packInfoProtocolCode = packInfo.ProtocolCode; + packInfo.Dispose(); + throw new NotSupportedException($"SessionInnerScheduler Received unsupported message protocolCode:{packInfoProtocolCode} messageType:{messageType}"); + } + } + } + + public static void Scheduler(this ProcessSession session, Type messageType, uint rpcId, long routeId, uint protocolCode, object message) + { + OpCodeIdStruct opCodeIdStruct = protocolCode; + + switch (opCodeIdStruct.Protocol) + { + case OpCodeType.InnerResponse: + case OpCodeType.InnerRouteResponse: + case OpCodeType.InnerAddressableResponse: + case OpCodeType.InnerRoamingResponse: + case OpCodeType.OuterAddressableResponse: + case OpCodeType.OuterCustomRouteResponse: + case OpCodeType.OuterRoamingResponse: + { + var sessionScene = session.Scene; + sessionScene.ThreadSynchronizationContext.Post(() => + { + var iResponse = (IResponse)session.Deserialize(messageType, message, ref opCodeIdStruct); + // 因为有可能是其他Scene线程下发送过来的、所以必须放到当前Scene进程下运行。 + sessionScene.NetworkMessagingComponent.ResponseHandler(rpcId, iResponse); + }); + + return; + } + case OpCodeType.InnerRoamingMessage: + case OpCodeType.InnerAddressableMessage: + case OpCodeType.InnerRouteMessage: + { + var sceneId = IdFactoryHelper.RuntimeIdTool.GetSceneId(ref routeId); + + if (!Process.TryGetScene(sceneId, out var scene)) + { + throw new Exception($"not found scene routeId:{routeId}"); + } + + var messageObject = session.Deserialize(messageType, message, ref opCodeIdStruct); + + scene.ThreadSynchronizationContext.Post(() => + { + var entity = scene.GetEntity(routeId); + var sceneMessageDispatcherComponent = scene.MessageDispatcherComponent; + + if (entity == null || entity.IsDisposed) + { + return; + } + + sceneMessageDispatcherComponent.RouteMessageHandler(session, messageType, entity, messageObject, rpcId).Coroutine(); + }); + + return; + } + case OpCodeType.InnerAddressableRequest: + case OpCodeType.InnerRoamingRequest: + case OpCodeType.InnerRouteRequest: + { + var sceneId = IdFactoryHelper.RuntimeIdTool.GetSceneId(ref routeId); + + if (!Process.TryGetScene(sceneId, out var scene)) + { + throw new Exception($"not found scene routeId:{routeId}"); + } + + var messageObject = session.Deserialize(messageType, message, ref opCodeIdStruct); + + scene.ThreadSynchronizationContext.Post(() => + { + var entity = scene.GetEntity(routeId); + var sceneMessageDispatcherComponent = scene.MessageDispatcherComponent; + + if (entity == null || entity.IsDisposed) + { + sceneMessageDispatcherComponent.FailRouteResponse(session, message.GetType(), InnerErrorCode.ErrNotFoundRoute, rpcId); + return; + } + + sceneMessageDispatcherComponent.RouteMessageHandler(session, messageType, entity, messageObject, rpcId).Coroutine(); + }); + + return; + } + case OpCodeType.OuterAddressableMessage: + case OpCodeType.OuterCustomRouteMessage: + case OpCodeType.OuterRoamingMessage: + { + var sceneId = IdFactoryHelper.RuntimeIdTool.GetSceneId(ref routeId); + + if (!Process.TryGetScene(sceneId, out var scene)) + { + Log.Error($"not found scene routeId:{routeId}"); + return; + } + + var messageObject = session.Deserialize(messageType, message, ref opCodeIdStruct); + + scene.ThreadSynchronizationContext.Post(() => + { + var entity = scene.GetEntity(routeId); + + switch (entity) + { + case null: + { + // 执行到这里是说明Session已经断开了 + // 因为这里是其他服务器Send到外网的数据、所以不需要给发送端返回就可以 + return; + } + case Session gateSession: + { + // 这里如果是Session只可能是Gate的Session、如果是的话、肯定是转发Address消息 + gateSession.Send((IMessage)messageObject, rpcId); + return; + } + default: + { + scene.MessageDispatcherComponent.RouteMessageHandler(session, messageType, entity, messageObject, rpcId).Coroutine(); + return; + } + } + }); + + return; + } + default: + { + throw new NotSupportedException($"SessionInnerScheduler Received unsupported message protocolCode:{protocolCode} messageType:{messageType}"); + } + } + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Session/ProcessSession/ProcessSession.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Session/ProcessSession/ProcessSession.cs new file mode 100644 index 0000000..ecd0bd4 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Session/ProcessSession/ProcessSession.cs @@ -0,0 +1,124 @@ +using Fantasy.Async; +using Fantasy.Network.Interface; +using Fantasy.PacketParser; +using Fantasy.PacketParser.Interface; +using Fantasy.Pool; +using Fantasy.Scheduler; +using Fantasy.Serialize; +#pragma warning disable CS8604 // Possible null reference argument. +#pragma warning disable CS8603 // Possible null reference return. +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. +#if FANTASY_NET +namespace Fantasy.Network; + +/// +/// 网络服务器内部会话。 +/// +public sealed class ProcessSession : Session +{ + private readonly MemoryStreamBufferPool _memoryStreamBufferPool = new MemoryStreamBufferPool(); + private readonly Dictionary> _createInstances = new Dictionary>(); + + /// + /// 发送消息到服务器内部。 + /// + /// 要发送的消息。 + /// RPC 标识符。 + /// 路由标识符。 + public override void Send(IMessage message, uint rpcId = 0, long routeId = 0) + { + if (IsDisposed) + { + return; + } + + this.Scheduler(message.GetType(), rpcId, routeId, message.OpCode(), message); + } + + /// + /// 发送路由消息到服务器内部。 + /// + /// 要发送的路由消息。 + /// RPC 标识符。 + /// 路由标识符。 + public override void Send(IRouteMessage routeMessage, uint rpcId = 0, long routeId = 0) + { + if (IsDisposed) + { + return; + } + + this.Scheduler(routeMessage.GetType(), rpcId, routeId, routeMessage.OpCode(), routeMessage); + } + + public override void Send(uint rpcId, long routeId, Type messageType, APackInfo packInfo) + { + if (IsDisposed) + { + return; + } + + this.Scheduler(messageType, rpcId, routeId, packInfo); + } + + public override void Send(ProcessPackInfo packInfo, uint rpcId = 0, long routeId = 0) + { + this.Scheduler(packInfo.MessageType, rpcId, routeId, packInfo); + } + + public override void Send(MemoryStreamBuffer memoryStream, uint rpcId = 0, long routeId = 0) + { + throw new Exception("The use of this method is not supported"); + } + + public override FTask Call(IRouteRequest request, long routeId = 0) + { + throw new Exception("The use of this method is not supported"); + } + + public override FTask Call(IRequest request, long routeId = 0) + { + throw new Exception("The use of this method is not supported"); + } + + public object Deserialize(Type messageType, object message, ref OpCodeIdStruct opCodeIdStruct) + { + var memoryStream = _memoryStreamBufferPool.RentMemoryStream(MemoryStreamBufferSource.None); + + try + { + if (SerializerManager.TryGetSerializer(opCodeIdStruct.OpCodeProtocolType, out var serializer)) + { + serializer.Serialize(messageType, message, memoryStream); + + if (memoryStream.Position == 0) + { + if (_createInstances.TryGetValue(messageType, out var createInstance)) + { + return createInstance(); + } + + createInstance = CreateInstance.CreateObject(messageType); + _createInstances.Add(messageType, createInstance); + return createInstance(); + } + + memoryStream.SetLength(memoryStream.Position); + memoryStream.Seek(0, SeekOrigin.Begin); + return serializer.Deserialize(messageType, memoryStream); + } + } + catch (Exception e) + { + Log.Error($"ProcessSession.Deserialize {e}"); + } + finally + { + _memoryStreamBufferPool.ReturnMemoryStream(memoryStream); + } + + throw new Exception($"type:{messageType} Does not support processing protocol"); + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Session/ProcessSession/ProcessSessionInfo.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Session/ProcessSession/ProcessSessionInfo.cs new file mode 100644 index 0000000..6e3a534 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Session/ProcessSession/ProcessSessionInfo.cs @@ -0,0 +1,17 @@ +using Fantasy.Network.Interface; + +#if FANTASY_NET +namespace Fantasy.Network; + +internal sealed class ProcessSessionInfo(Session session, AClientNetwork aClientNetwork) : IDisposable +{ + public readonly Session Session = session; + public readonly AClientNetwork AClientNetwork = aClientNetwork; + + public void Dispose() + { + Session.Dispose(); + AClientNetwork?.Dispose(); + } +} +#endif diff --git a/Fantasy/Fantays.Console/Runtime/Core/Network/Session/Session.cs b/Fantasy/Fantays.Console/Runtime/Core/Network/Session/Session.cs new file mode 100644 index 0000000..fc75dea --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Network/Session/Session.cs @@ -0,0 +1,272 @@ +// ReSharper disable RedundantUsingDirective +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using Fantasy.Async; +using Fantasy.Entitas; +using Fantasy.Entitas.Interface; +using Fantasy.Helper; +using Fantasy.Network.Interface; +using Fantasy.PacketParser; +using Fantasy.PacketParser.Interface; +using Fantasy.Scheduler; +using Fantasy.Serialize; +#if FANTASY_NET +using Fantasy.Network.Route; +using Fantasy.Platform.Net; +using Fantasy.Network.Roaming; +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +#endif +// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract +#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type. +#pragma warning disable CS8602 // Dereference of a possibly null reference. +#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. +#pragma warning disable CS8603 +#pragma warning disable CS8601 +#pragma warning disable CS8618 + +namespace Fantasy.Network +{ + /// + /// 网络会话的基类,用于管理网络通信。 + /// + public class Session : Entity, ISupportedMultiEntity + { + private uint _rpcId; + internal long LastReceiveTime; + /// + /// 关联的网络连接通道 + /// + internal INetworkChannel Channel { get; private set; } + /// + /// 当前Session的终结点信息 + /// + public IPEndPoint RemoteEndPoint { get; private set; } + private ANetworkMessageScheduler NetworkMessageScheduler { get; set;} + internal readonly Dictionary> RequestCallback = new(); + /// + /// Session的Dispose委托 + /// + internal event Action OnDispose; +#if FANTASY_NET + internal RouteComponent RouteComponent; + internal SessionRoamingComponent SessionRoamingComponent; + internal AddressableRouteComponent AddressableRouteComponent; + internal static Session Create(ANetworkMessageScheduler networkMessageScheduler, ANetworkServerChannel channel, NetworkTarget networkTarget) + { + var session = Entity.Create(channel.Scene, false, true); + session.Channel = channel; + session.NetworkMessageScheduler = networkMessageScheduler; + session.RemoteEndPoint = channel.RemoteEndPoint as IPEndPoint; + session.OnDispose = channel.Dispose; + session.LastReceiveTime = TimeHelper.Now; + // 在外部网络目标下,添加会话空闲检查组件 + if (networkTarget == NetworkTarget.Outer) + { + var interval = ProcessDefine.SessionIdleCheckerInterval; + var timeOut = ProcessDefine.SessionIdleCheckerTimeout; + session.AddComponent().Start(interval, timeOut); + } + return session; + } +#endif + internal static Session Create(AClientNetwork network, IPEndPoint remoteEndPoint) + { + // 创建会话实例 + var session = Entity.Create(network.Scene, false, true); + session.Channel = network; + session.RemoteEndPoint = remoteEndPoint; + session.OnDispose = network.Dispose; + session.NetworkMessageScheduler = network.NetworkMessageScheduler; + session.LastReceiveTime = TimeHelper.Now; + return session; + } +#if FANTASY_NET + internal static ProcessSession CreateInnerSession(Scene scene) + { + var session = Entity.Create(scene, false, false); + session.NetworkMessageScheduler = new InnerMessageScheduler(scene); + return session; + } + + /// + /// 发送一个消息,框架内部使用建议不要用这个方法。 + /// + /// 如果是RPC消息需要传递一个RPCId + /// routeId + /// 消息的类型 + /// packInfo消息包 + public virtual void Send(uint rpcId, long routeId, Type messageType, APackInfo packInfo) + { + if (IsDisposed) + { + return; + } + + Channel.Send(rpcId, routeId, packInfo.MemoryStream, null); + } + + /// + /// 发送一个消息,框架内部使用建议不要用这个方法。 + /// + /// 一个ProcessPackInfo消息包 + /// 如果是RPC消息需要传递一个RPCId + /// routeId + public virtual void Send(ProcessPackInfo packInfo, uint rpcId = 0, long routeId = 0) + { + if (IsDisposed) + { + return; + } + + using (packInfo) + { + Channel.Send(rpcId, routeId, packInfo.MemoryStream, null); + } + } + + /// + /// 发送一个消息 + /// + /// 需要发送的MemoryStreamBuffer + /// 如果是RPC消息需要传递一个RPCId + /// routeId + public virtual void Send(MemoryStreamBuffer memoryStream, uint rpcId = 0, long routeId = 0) + { + if (IsDisposed) + { + return; + } + + Channel.Send(rpcId, routeId, memoryStream, null); + } +#endif + /// + /// 销毁一个Session,当执行了这个方法会自动断开网络的连接。 + /// + public override void Dispose() + { + if (IsDisposed) + { + return; + } + + _rpcId = 0; + LastReceiveTime = 0; + Channel = null; + RemoteEndPoint = null; + NetworkMessageScheduler = null; +#if FANTASY_NET + SessionRoamingComponent = null; + RouteComponent = null; + AddressableRouteComponent = null; +#endif + base.Dispose(); + + // 终止所有等待中的请求回调 + foreach (var requestCallback in RequestCallback.Values.ToArray()) + { + requestCallback.SetException(new Exception($"session is dispose: {Id}")); + } + + RequestCallback.Clear(); + OnDispose?.Invoke(); + } + + /// + /// 发送一个消息 + /// + /// 消息的实例 + /// 如果是RPC消息需要传递一个RPCId + /// routeId + public virtual void Send(IMessage message, uint rpcId = 0, long routeId = 0) + { + if (IsDisposed) + { + return; + } + + Channel.Send(rpcId, routeId, null, message); + } + + /// + /// 发送一个消息 + /// + /// 消息的实例,不同的是这个是发送Route消息使用的 + /// 如果是RPC消息需要传递一个RPCId + /// routeId + public virtual void Send(IRouteMessage routeMessage, uint rpcId = 0, long routeId = 0) + { + if (IsDisposed) + { + return; + } + + Channel.Send(rpcId, routeId, null, routeMessage); + } + + /// + /// 发送一个RPC消息 + /// + /// 请求Route消息的实例 + /// routeId + /// + public virtual FTask Call(IRouteRequest request, long routeId = 0) + { + if (IsDisposed) + { + return null; + } + + var requestCallback = FTask.Create(); + var rpcId = ++_rpcId; + RequestCallback.Add(rpcId, requestCallback); + Send(request, rpcId, routeId); + return requestCallback; + } + + /// + /// 发送一个RPC消息 + /// + /// 请求消息的实例 + /// routeId + /// + public virtual FTask Call(IRequest request, long routeId = 0) + { + if (IsDisposed) + { + return null; + } + + var requestCallback = FTask.Create(); + var rpcId = ++_rpcId; + RequestCallback.Add(rpcId, requestCallback); + Send(request, rpcId, routeId); + return requestCallback; + } + + internal void Receive(APackInfo packInfo) + { + if (IsDisposed) + { + return; + } + + LastReceiveTime = TimeHelper.Now; + + try + { + NetworkMessageScheduler.Scheduler(this, packInfo).Coroutine(); + } + catch (Exception e) + { + // 如果解析失败,只有一种可能,那就是有人恶意发包。 + // 所以这里强制关闭了当前连接。不让对方一直发包。 + Dispose(); + Log.Error(e); + } + } + } +} diff --git a/Fantasy/Fantays.Console/Runtime/Core/Platform/Console/Entry.cs b/Fantasy/Fantays.Console/Runtime/Core/Platform/Console/Entry.cs new file mode 100644 index 0000000..2cddb8b --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Platform/Console/Entry.cs @@ -0,0 +1,101 @@ +#if FANTASY_CONSOLE +using Fantasy.Assembly; +using Fantasy.Async; +using Fantasy.Serialize; +#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. +#pragma warning disable CS8603 // Possible null reference return. +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. +namespace Fantasy.Platform.Console +{ + public struct OnFantasyInit + { + public Scene Scene; + } + + /// + /// 一般的控制台启动入口,可以适用大部分客户端环境 + /// + public sealed class Entry + { + private static bool _isInit; + private static Thread _updateThread; + public static Scene Scene { get; private set; } + + /// + /// 初始化框架 + /// + /// + public static async FTask Initialize(params System.Reflection.Assembly[] assemblies) + { + if (_isInit) + { + Log.Error("Fantasy has already been initialized and does not need to be initialized again!"); + return; + } + + // 初始化程序集管理系统 + await AssemblySystem.InnerInitialize(assemblies); + // 初始化序列化 + SerializerManager.Initialize(); + _isInit = true; + Log.Debug("Fantasy Initialize Complete!"); + } + + /// + /// 启动框架。 + /// 如果您的平台有每帧更新逻辑的方法,请不要调用这个方法。 + /// 如果没有实现每帧执行方法平台需要调用这个方法,目的是开启一个新的线程来每帧执行Update。 + /// 注意因为开启了一个新的线程来处理更新逻辑,所以要注意多线程的问题。 + /// + public static void StartUpdate() + { + _updateThread = new Thread(() => + { + while (_isInit) + { + ThreadScheduler.Update(); + Thread.Sleep(1); + } + }) + { + IsBackground = true + }; + _updateThread.Start(); + } + + /// + /// 在Entry中创建一个Scene,如果Scene已经被创建过,将先销毁Scene再创建。 + /// + /// + /// + public static async FTask CreateScene(string sceneRuntimeMode = SceneRuntimeMode.MainThread) + { + Scene?.Dispose(); + Scene = await Scene.Create(sceneRuntimeMode); + await Scene.EventComponent.PublishAsync(new OnFantasyInit() + { + Scene = Scene + }); + return Scene; + } + + /// + /// 如果有的话一定要在每帧执行这个方法 + /// + public void Update() + { + ThreadScheduler.Update(); + } + + public static void Dispose() + { + AssemblySystem.Dispose(); + SerializerManager.Dispose(); + Scene?.Dispose(); + Scene = null; + _isInit = false; + } + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Platform/Console/ThreadSynchronizationContext.cs b/Fantasy/Fantays.Console/Runtime/Core/Platform/Console/ThreadSynchronizationContext.cs new file mode 100644 index 0000000..29fd54a --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Platform/Console/ThreadSynchronizationContext.cs @@ -0,0 +1,38 @@ +#if FANTASY_CONSOLE +#pragma warning disable CS8601 // Possible null reference assignment. +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. +#pragma warning disable CS8765 // Nullability of type of parameter doesn't match overridden member (possibly because of nullability attributes). +namespace Fantasy +{ + public sealed class ThreadSynchronizationContext : SynchronizationContext + { + private Action _actionHandler; + private readonly Queue _queue = new(); + + public void Update() + { + while (_queue.TryDequeue(out _actionHandler)) + { + try + { + _actionHandler(); + } + catch (Exception e) + { + Log.Error(e); + } + } + } + + public override void Post(SendOrPostCallback callback, object state) + { + Post(() => callback(state)); + } + + public void Post(Action action) + { + _queue.Enqueue(action); + } + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Platform/Net/ConfigTable/MachineConfig.cs b/Fantasy/Fantays.Console/Runtime/Core/Platform/Net/ConfigTable/MachineConfig.cs new file mode 100644 index 0000000..e09b00e --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Platform/Net/ConfigTable/MachineConfig.cs @@ -0,0 +1,88 @@ +#if FANTASY_NET +// ReSharper disable InconsistentNaming +using System.Collections.Concurrent; +using System.Runtime.Serialization; +using Fantasy.Helper; +using Newtonsoft.Json; +#pragma warning disable CS8601 // Possible null reference assignment. +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. +namespace Fantasy.Platform.Net +{ + /// + /// 用于记录服务器物理信息 + /// + public sealed class MachineConfigData + { + /// + /// 存放所有MachineConfigInfo信息 + /// + public List List; + [JsonIgnore] + [IgnoreDataMember] + private readonly ConcurrentDictionary _configs = new ConcurrentDictionary(); + /// + /// 获得MachineConfig的实例 + /// + public static MachineConfigData Instance { get; private set; } + /// + /// 初始化MachineConfig + /// + /// + public static void Initialize(string machineConfigJson) + { + Instance = machineConfigJson.Deserialize(); + foreach (var config in Instance.List) + { + Instance._configs.TryAdd(config.Id, config); + } + } + /// + /// 根据Id获取MachineConfig + /// + /// + /// + /// + public MachineConfig Get(uint id) + { + if (_configs.TryGetValue(id, out var machineConfigInfo)) + { + return machineConfigInfo; + } + + throw new FileNotFoundException($"MachineConfig not find {id} Id"); + } + /// + /// 根据Id获取MachineConfig + /// + /// + /// + /// + public bool TryGet(uint id, out MachineConfig config) + { + return _configs.TryGetValue(id, out config); + } + } + /// + /// 表示一个物理服务器的信息 + /// + public sealed class MachineConfig + { + /// + /// Id + /// + public uint Id { get; set; } + /// + /// 外网IP + /// + public string OuterIP { get; set; } + /// + /// 外网绑定IP + /// + public string OuterBindIP { get; set; } + /// + /// 内网绑定IP + /// + public string InnerBindIP { get; set; } + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Platform/Net/ConfigTable/ProcessConfig.cs b/Fantasy/Fantays.Console/Runtime/Core/Platform/Net/ConfigTable/ProcessConfig.cs new file mode 100644 index 0000000..4a400b0 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Platform/Net/ConfigTable/ProcessConfig.cs @@ -0,0 +1,100 @@ +#if FANTASY_NET +using System.Collections.Concurrent; +using System.Runtime.Serialization; +using Fantasy.Helper; +using Newtonsoft.Json; +// ReSharper disable CollectionNeverUpdated.Global +#pragma warning disable CS8601 // Possible null reference assignment. +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. + +namespace Fantasy.Platform.Net +{ + /// + /// 用于管理进程信息 + /// + public sealed class ProcessConfigData + { + /// + /// 存放所有ProcessConfig信息 + /// + public List List; + [JsonIgnore] + [IgnoreDataMember] + private readonly ConcurrentDictionary _configs = new ConcurrentDictionary(); + /// + /// 获得ProcessConfigData的实例 + /// + public static ProcessConfigData Instance { get; private set; } + /// + /// 初始化MachineConfig + /// + /// + public static void Initialize(string processConfigJson) + { + Instance = processConfigJson.Deserialize(); + foreach (var config in Instance.List) + { + Instance._configs.TryAdd(config.Id, config); + } + } + /// + /// 根据Id获取ProcessConfig + /// + /// + /// + /// + public ProcessConfig Get(uint id) + { + if (_configs.TryGetValue(id, out var processConfigInfo)) + { + return processConfigInfo; + } + + throw new FileNotFoundException($"MachineConfig not find {id} Id"); + } + /// + /// 根据Id获取ProcessConfig + /// + /// + /// + /// + public bool TryGet(uint id, out ProcessConfig config) + { + return _configs.TryGetValue(id, out config); + } + /// + /// 按照startupGroup寻找属于startupGroup组的ProcessConfig + /// + /// startupGroup + /// + public IEnumerable ForEachByStartupGroup(uint startupGroup) + { + foreach (var processConfig in List) + { + if (processConfig.StartupGroup == startupGroup) + { + yield return processConfig; + } + } + } + } + /// + /// 表示一个进程配置信息 + /// + public sealed class ProcessConfig + { + /// + /// 进程Id + /// + public uint Id { get; set; } + /// + /// 机器ID + /// + public uint MachineId { get; set; } + /// + /// 启动组 + /// + public uint StartupGroup { get; set; } + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Platform/Net/ConfigTable/SceneConfig.cs b/Fantasy/Fantays.Console/Runtime/Core/Platform/Net/ConfigTable/SceneConfig.cs new file mode 100644 index 0000000..e939419 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Platform/Net/ConfigTable/SceneConfig.cs @@ -0,0 +1,196 @@ +#if FANTASY_NET +using System.Collections.Concurrent; +using System.Runtime.Serialization; +using Fantasy.DataStructure.Collection; +using Fantasy.DataStructure.Dictionary; +using Fantasy.Helper; +using Fantasy.IdFactory; +using Newtonsoft.Json; +#pragma warning disable CS8603 // Possible null reference return. +#pragma warning disable CS8601 // Possible null reference assignment. + +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. +namespace Fantasy.Platform.Net +{ + /// + /// 存放所有SceneConfigInfo信息 + /// + public sealed class SceneConfigData + { + /// + /// 存放所有SceneConfig信息 + /// + public List List; + [JsonIgnore] + [IgnoreDataMember] + private readonly ConcurrentDictionary _configs = new ConcurrentDictionary(); + [JsonIgnore] + [IgnoreDataMember] + private readonly OneToManyList _sceneConfigBySceneType = new OneToManyList(); + [JsonIgnore] + [IgnoreDataMember] + private readonly OneToManyList _sceneConfigByProcess = new OneToManyList(); + [JsonIgnore] [IgnoreDataMember] + private readonly Dictionary>> _worldSceneTypes = new Dictionary>>(); + /// + /// 获得SceneConfigData的实例 + /// + public static SceneConfigData Instance { get; private set; } + /// + /// 初始化SceneConfig + /// + /// + public static void Initialize(string sceneConfigJson) + { + Instance = sceneConfigJson.Deserialize(); + foreach (var config in Instance.List) + { + config.Initialize(); + Instance._configs.TryAdd(config.Id, config); + Instance._sceneConfigByProcess.Add(config.ProcessConfigId, config); + Instance._sceneConfigBySceneType.Add(config.SceneType, config); + + var configWorldConfigId = (int)config.WorldConfigId; + + if (!Instance._worldSceneTypes.TryGetValue(configWorldConfigId, out var sceneConfigDic)) + { + sceneConfigDic = new Dictionary>(); + Instance._worldSceneTypes.Add(configWorldConfigId, sceneConfigDic); + } + + if (!sceneConfigDic.TryGetValue(config.SceneType, out var sceneConfigList)) + { + sceneConfigList = new List(); + sceneConfigDic.Add(config.SceneType, sceneConfigList); + } + + sceneConfigList.Add(config); + } + } + + /// + /// 根据Id获取SceneConfig + /// + /// + /// + /// + public SceneConfig Get(uint id) + { + if (_configs.TryGetValue(id, out var sceneConfigInfo)) + { + return sceneConfigInfo; + } + + throw new FileNotFoundException($"WorldConfig not find {id} Id"); + } + + /// + /// 根据Id获取SceneConfig + /// + /// + /// + /// + public bool TryGet(uint id, out SceneConfig config) + { + return _configs.TryGetValue(id, out config); + } + + /// + /// 获得SceneConfig + /// + /// + /// + public List GetByProcess(uint serverConfigId) + { + return _sceneConfigByProcess.TryGetValue(serverConfigId, out var list) ? list : new List(); + } + + /// + /// 获得SceneConfig + /// + /// + /// + public List GetSceneBySceneType(int sceneType) + { + return !_sceneConfigBySceneType.TryGetValue(sceneType, out var list) ? new List() : list; + } + + /// + /// 获得SceneConfig + /// + /// + /// + /// + public List GetSceneBySceneType(int world, int sceneType) + { + if (!_worldSceneTypes.TryGetValue(world, out var sceneConfigDic)) + { + return new List(); + } + + if (!sceneConfigDic.TryGetValue(sceneType, out var list)) + { + return new List(); + } + + return list; + } + } + + /// + /// 表示一个Scene配置信息 + /// + public sealed class SceneConfig + { + /// + /// ID + /// + public uint Id { get; set; } + /// + /// 进程Id + /// + public uint ProcessConfigId { get; set; } + /// + /// 世界Id + /// + public uint WorldConfigId { get; set; } + /// + /// Scene运行类型 + /// + public string SceneRuntimeMode { get; set; } + /// + /// Scene类型 + /// + public string SceneTypeString { get; set; } + /// + /// 协议类型 + /// + public string NetworkProtocol { get; set; } + /// + /// 外网端口 + /// + public int OuterPort { get; set; } + /// + /// 内网端口 + /// + public int InnerPort { get; set; } + /// + /// Scene类型 + /// + public int SceneType { get; set; } + /// + /// RouteId + /// + [JsonIgnore] + [IgnoreDataMember] + public long RouteId { get; private set; } + /// + /// 初始化方法 + /// + public void Initialize() + { + RouteId = IdFactoryHelper.RuntimeId(0, Id, (byte)WorldConfigId, 0); + } + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Platform/Net/ConfigTable/WorldConfig.cs b/Fantasy/Fantays.Console/Runtime/Core/Platform/Net/ConfigTable/WorldConfig.cs new file mode 100644 index 0000000..9b0ede5 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Platform/Net/ConfigTable/WorldConfig.cs @@ -0,0 +1,93 @@ +#if FANTASY_NET +using System.Collections.Concurrent; +using System.Runtime.Serialization; +using Fantasy.Helper; +using Newtonsoft.Json; +#pragma warning disable CS8601 // Possible null reference assignment. + +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. +namespace Fantasy.Platform.Net +{ + /// + /// 存放所有WorldConfigInfo信息 + /// + public sealed class WorldConfigData + { + /// + /// 存放所有WorldConfigInfo信息 + /// + public List List; + [JsonIgnore] + [IgnoreDataMember] + private readonly ConcurrentDictionary _configs = new ConcurrentDictionary(); + /// + /// 获得WorldConfig的实例 + /// + public static WorldConfigData Instance { get; private set; } + /// + /// 初始化WorldConfig + /// + /// + public static void Initialize(string worldConfigJson) + { + Instance = worldConfigJson.Deserialize(); + foreach (var config in Instance.List) + { + Instance._configs.TryAdd(config.Id, config); + } + } + /// + /// 根据Id获取WorldConfig + /// + /// + /// + /// + public WorldConfig Get(uint id) + { + if (_configs.TryGetValue(id, out var worldConfigInfo)) + { + return worldConfigInfo; + } + + throw new FileNotFoundException($"WorldConfig not find {id} Id"); + } + /// + /// 根据Id获取WorldConfig + /// + /// + /// + /// + public bool TryGet(uint id, out WorldConfig config) + { + return _configs.TryGetValue(id, out config); + } + } + + /// + /// 表示一个世界配置信息 + /// + public sealed class WorldConfig + { + /// + /// Id + /// + public uint Id { get; set; } + /// + /// 名称 + /// + public string WorldName { get; set; } + /// + /// 数据库连接字符串 + /// + public string DbConnection { get; set; } + /// + /// 数据库名称 + /// + public string DbName { get; set; } + /// + /// 数据库类型 + /// + public string DbType { get; set; } + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Platform/Net/Entry.cs b/Fantasy/Fantays.Console/Runtime/Core/Platform/Net/Entry.cs new file mode 100644 index 0000000..959818d --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Platform/Net/Entry.cs @@ -0,0 +1,124 @@ +#if FANTASY_NET +using CommandLine; +using Fantasy.Assembly; +using Fantasy.Async; +using Fantasy.Helper; +using Fantasy.IdFactory; +using Fantasy.LowLevel; +using Fantasy.Network; +using Fantasy.Serialize; +// ReSharper disable FunctionNeverReturns + +namespace Fantasy.Platform.Net; + +/// +/// Fantasy.Net 应用程序入口 +/// +/// 当命令行格式异常时抛出。 +/// 不支持的 ProcessType 类型异常。 +public static class Entry +{ + /// + /// 框架初始化 + /// + /// 注册的Assembly + public static async FTask Initialize(params System.Reflection.Assembly[] assemblies) + { + // 解析命令行参数 + Parser.Default.ParseArguments(Environment.GetCommandLineArgs()) + .WithNotParsed(error => throw new Exception("Command line format error!")) + .WithParsed(option => + { + ProcessDefine.Options = option; + ProcessDefine.InnerNetwork = Enum.Parse(option.InnerNetwork); + }); + // 初始化Log系统 + Log.Initialize(); + // 检查启动参数,后期可能有机器人等不同的启动参数 + switch (ProcessDefine.Options.ProcessType) + { + case "Game": + { + break; + } + default: + { + throw new NotSupportedException($"ProcessType is {ProcessDefine.Options.ProcessType} Unrecognized!"); + } + } + // 初始化程序集管理系统 + await AssemblySystem.InnerInitialize(assemblies); + // 初始化序列化 + SerializerManager.Initialize(); + // 精度处理(只针对Windows下有作用、其他系统没有这个问题、一般也不会用Windows来做服务器的) + WinPeriod.Initialize(); + + FantasyMemory.Initialize(); + } + + /// + /// 启动Fantasy.Net + /// + public static async FTask Start() + { + // 启动Process + StartProcess().Coroutine(); + await FTask.CompletedTask; + while (true) + { + ThreadScheduler.Update(); + Thread.Sleep(1); + } + } + + /// + /// 初始化并且启动框架 + /// + /// + public static async FTask Start(params System.Reflection.Assembly[] assemblies) + { + await Initialize(assemblies); + await Start(); + } + + private static async FTask StartProcess() + { + if (ProcessDefine.Options.StartupGroup != 0) + { + foreach (var processConfig in ProcessConfigData.Instance.ForEachByStartupGroup((uint)ProcessDefine.Options.StartupGroup)) + { + await Process.Create(processConfig.Id); + } + + return; + } + + switch (ProcessDefine.Options.Mode) + { + case "Develop": + { + foreach (var processConfig in ProcessConfigData.Instance.List) + { + await Process.Create(processConfig.Id); + } + + return; + } + case "Release": + { + await Process.Create(ProcessDefine.Options.ProcessId); + return; + } + } + } + + /// + /// 关闭 Fantasy + /// + public static void Close() + { + AssemblySystem.Dispose(); + SerializerManager.Dispose(); + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Platform/Net/Process.cs b/Fantasy/Fantays.Console/Runtime/Core/Platform/Net/Process.cs new file mode 100644 index 0000000..e3a6cf8 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Platform/Net/Process.cs @@ -0,0 +1,156 @@ +#if FANTASY_NET +using System.Collections.Concurrent; +using Fantasy.Async; +using Fantasy.IdFactory; + +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. +#pragma warning disable CS8601 // Possible null reference assignment. +namespace Fantasy.Platform.Net; + +/// +/// 一个进程的实例 +/// +public sealed class Process : IDisposable +{ + /// + /// 当前进程的Id + /// + public readonly uint Id; + /// + /// 进程关联的MachineId + /// + public readonly uint MachineId; + private readonly ConcurrentDictionary _processScenes = new ConcurrentDictionary(); + private static readonly ConcurrentDictionary Scenes = new ConcurrentDictionary(); + private Process() {} + private Process(uint id, uint machineId) + { + Id = id; + MachineId = machineId; + } + + internal bool IsProcess(ref long routeId) + { + var sceneId = IdFactoryHelper.RuntimeIdTool.GetSceneId(ref routeId); + return _processScenes.ContainsKey(sceneId); + } + + internal bool IsProcess(ref uint sceneId) + { + return _processScenes.ContainsKey(sceneId); + } + + internal void AddSceneToProcess(Scene scene) + { + _processScenes.TryAdd(scene.SceneConfigId, scene); + } + + internal void RemoveSceneToProcess(Scene scene, bool isDispose) + { + if (!_processScenes.Remove(scene.SceneConfigId, out _)) + { + return; + } + + if (isDispose) + { + scene.Dispose(); + } + } + + internal bool TryGetSceneToProcess(long routeId, out Scene scene) + { + var sceneId = IdFactoryHelper.RuntimeIdTool.GetSceneId(ref routeId); + return _processScenes.TryGetValue(sceneId, out scene); + } + + internal bool TryGetSceneToProcess(uint sceneId, out Scene scene) + { + return _processScenes.TryGetValue(sceneId, out scene); + } + /// + /// 销毁方法 + /// + public void Dispose() + { + if (_processScenes.IsEmpty) + { + return; + } + + var sceneQueue = new Queue(); + + foreach (var (_, scene) in _processScenes) + { + sceneQueue.Enqueue(scene); + } + + while (sceneQueue.TryDequeue(out var removeScene)) + { + removeScene.Dispose(); + } + + _processScenes.Clear(); + } + + internal static async FTask Create(uint processConfigId) + { + if (!ProcessConfigData.Instance.TryGet(processConfigId, out var processConfig)) + { + Log.Error($"not found processConfig by Id:{processConfigId}"); + return null; + } + + if (!MachineConfigData.Instance.TryGet(processConfig.MachineId, out var machineConfig)) + { + Log.Error($"not found machineConfig by Id:{processConfig.MachineId}"); + return null; + } + + var process = new Process(processConfigId, processConfig.MachineId); + var sceneConfigs = SceneConfigData.Instance.GetByProcess(processConfigId); + + foreach (var sceneConfig in sceneConfigs) + { + await Scene.Create(process, machineConfig, sceneConfig); + } + + Log.Info($"Process:{processConfigId} Startup Complete SceneCount:{sceneConfigs.Count}"); + return process; + } + + internal bool IsInAppliaction(ref uint sceneId) + { + return _processScenes.ContainsKey(sceneId); + } + + internal static void AddScene(Scene scene) + { + Scenes.TryAdd(scene.SceneConfigId, scene); + } + + internal static void RemoveScene(Scene scene, bool isDispose) + { + if (!Scenes.Remove(scene.SceneConfigId, out _)) + { + return; + } + + if (isDispose) + { + scene.Dispose(); + } + } + + internal static bool TryGetScene(long routeId, out Scene scene) + { + var sceneId = IdFactoryHelper.RuntimeIdTool.GetSceneId(ref routeId); + return Scenes.TryGetValue(sceneId, out scene); + } + + internal static bool TryGetScene(uint sceneId, out Scene scene) + { + return Scenes.TryGetValue(sceneId, out scene); + } +} +#endif diff --git a/Fantasy/Fantays.Console/Runtime/Core/Platform/Net/ProcessDefine.cs b/Fantasy/Fantays.Console/Runtime/Core/Platform/Net/ProcessDefine.cs new file mode 100644 index 0000000..2bcc4e0 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Platform/Net/ProcessDefine.cs @@ -0,0 +1,99 @@ +#if FANTASY_NET +using CommandLine; +using Fantasy.Network; + +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. +namespace Fantasy.Platform.Net; + +/// +/// Process运行模式 +/// +public enum ProcessMode +{ + /// + /// 默认 + /// + None =0, + /// + /// 开发模式 + /// + Develop = 1, + /// + /// 发布模式 + /// + Release = 2 +} + +internal sealed class CommandLineOptions +{ + /// + /// 用于启动指定的进程,该进程的 ID 与 ProcessConfig 的 ID 相关联。此参数只能传递单个 ID,不支持传递多个 ID。 + /// + [Option("pid", Required = false, Default = (uint)0, HelpText = "Enter an ProcessIdId such as 1")] + public uint ProcessId { get; set; } + /// + /// Process类型,获取或设置应用程序的类型。 + /// Game - 游戏服务器Process + /// Robot - 机器人(暂未支持该功能) + /// + [Option('a', "ProcessType", Required = false, Default = "Game", HelpText = "Game")] + public string ProcessType { get; set; } + /// + /// 服务器运行模式,获取或设置服务器的运行模式。 + /// Develop - 开发模式(启动Process配置表中的所有Process) + /// Release - 发布模式(根据ProcessId启动Process) + /// + [Option('m', "Mode", Required = true, Default = "Release", HelpText = "Develop:启动Process配置表中的所有Process,\nRelease:根据ProcessId启动Process")] + public string Mode { get; set; } + /// + /// 服务器内部网络协议 + /// TCP - 服务器内部之间通讯使用TCP协议 + /// KCP - 服务器内部之间通讯使用KCP协议 + /// WebSocket - 服务器内部之间通讯使用WebSocket协议(不推荐、TCP或KCP) + /// + [Option('n', "InnerNetwork", Required = false, Default = "TCP", HelpText = "TCP、KCP、WebSocket")] + public string InnerNetwork { get; set; } + /// + /// 会话空闲检查超时时间。 + /// + [Option('t', "SessionIdleCheckerTimeout", Required = false, Default = 8000, HelpText = "Session idle check timeout")] + public int SessionIdleCheckerTimeout { get; set; } + /// + /// 会话空闲检查间隔。 + /// + [Option('i', "SessionIdleCheckerInterval", Required = false, Default = 5000, HelpText = "Session idle check interval")] + public int SessionIdleCheckerInterval { get; set; } + /// + /// 启动组。 + /// + [Option('g', "StartupGroup", Required = false, Default = 0, HelpText = "Used to start a group of Process")] + public int StartupGroup { get; set; } +} + +/// +/// AppDefine +/// +internal static class ProcessDefine +{ + /// + /// 命令行选项 + /// + public static CommandLineOptions Options; + /// + /// App程序Id + /// + public static uint ProcessId => Options.ProcessId; + /// + /// 会话空闲检查超时时间。 + /// + public static int SessionIdleCheckerTimeout => Options.SessionIdleCheckerTimeout; + /// + /// 会话空闲检查间隔。 + /// + public static int SessionIdleCheckerInterval => Options.SessionIdleCheckerInterval; + /// + /// 内部网络通讯协议类型 + /// + public static NetworkProtocolType InnerNetwork; +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Platform/Net/ThreadSynchronizationContext.cs b/Fantasy/Fantays.Console/Runtime/Core/Platform/Net/ThreadSynchronizationContext.cs new file mode 100644 index 0000000..bb2948a --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Platform/Net/ThreadSynchronizationContext.cs @@ -0,0 +1,52 @@ +#if FANTASY_NET +using System.Collections.Concurrent; +#pragma warning disable CS8765 +#pragma warning disable CS8601 +#pragma warning disable CS8618 + +namespace Fantasy; + +/// +/// 线程的同步上下文 +/// +public sealed class ThreadSynchronizationContext : SynchronizationContext +{ + private readonly ConcurrentQueue _queue = new(); + /// + /// 执行当前上下文投递过的逻辑 + /// + public void Update() + { + while (_queue.TryDequeue(out var actionHandler)) + { + try + { + actionHandler(); + } + catch (Exception e) + { + Log.Error(e); + } + } + } + + /// + /// 投递一个逻辑到当前上下文 + /// + /// + /// + public override void Post(SendOrPostCallback callback, object state) + { + Post(() => callback(state)); + } + + /// + /// 投递一个逻辑到当前上下文 + /// + /// + public void Post(Action action) + { + _queue.Enqueue(action); + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Platform/Unity/AppDefine.cs b/Fantasy/Fantays.Console/Runtime/Core/Platform/Unity/AppDefine.cs new file mode 100644 index 0000000..d9d85ae --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Platform/Unity/AppDefine.cs @@ -0,0 +1,19 @@ +#if FANTASY_UNITY +namespace Fantasy.Platform.Unity +{ + public static class AppDefine + { + public static string RemoteUpdatePath; + public static bool EditorModel = true; + public const string VersionName = "version.bytes"; + public const string VersionMD5Name = "version.md5"; + public const string AssetBundleManifestName = "Fantasy"; + public static bool IsEditor => UnityEngine.Application.isEditor && EditorModel; + public static string AssetBundleSaveDirectory => "Assets/AssetBundles"; + public static string LocalAssetBundlePath => UnityEngine.Application.streamingAssetsPath; + public static string RemoteAssetBundlePath => UnityEngine.Application.persistentDataPath; + public static string PersistentDataVersion => $"{UnityEngine.Application.persistentDataPath}/{VersionName}"; + public static string StreamingAssetsVersion => $"{UnityEngine.Application.streamingAssetsPath}/{VersionName}"; + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Platform/Unity/Attributes/BsonDefaultValueAttribute.cs b/Fantasy/Fantays.Console/Runtime/Core/Platform/Unity/Attributes/BsonDefaultValueAttribute.cs new file mode 100644 index 0000000..49914ec --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Platform/Unity/Attributes/BsonDefaultValueAttribute.cs @@ -0,0 +1,11 @@ +#if FANTASY_UNITY +using System; +namespace MongoDB.Bson.Serialization.Attributes +{ + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] + public class BsonDefaultValueAttribute : Attribute + { + public BsonDefaultValueAttribute(object defaultValue) { } + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Platform/Unity/Attributes/BsonElementAttribute.cs b/Fantasy/Fantays.Console/Runtime/Core/Platform/Unity/Attributes/BsonElementAttribute.cs new file mode 100644 index 0000000..3892727 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Platform/Unity/Attributes/BsonElementAttribute.cs @@ -0,0 +1,13 @@ +#if FANTASY_UNITY +using System; +namespace MongoDB.Bson.Serialization.Attributes +{ + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] + public class BsonElementAttribute : Attribute + { + public BsonElementAttribute() { } + + public BsonElementAttribute(string elementName) { } + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Platform/Unity/Attributes/BsonIdAttribute.cs b/Fantasy/Fantays.Console/Runtime/Core/Platform/Unity/Attributes/BsonIdAttribute.cs new file mode 100644 index 0000000..48274d0 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Platform/Unity/Attributes/BsonIdAttribute.cs @@ -0,0 +1,11 @@ +#if FANTASY_UNITY +using System; +namespace MongoDB.Bson.Serialization.Attributes +{ + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] + public class BsonIdAttribute : Attribute + { + + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Platform/Unity/Attributes/BsonIgnoreAttribute.cs b/Fantasy/Fantays.Console/Runtime/Core/Platform/Unity/Attributes/BsonIgnoreAttribute.cs new file mode 100644 index 0000000..23e55c8 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Platform/Unity/Attributes/BsonIgnoreAttribute.cs @@ -0,0 +1,11 @@ +#if FANTASY_UNITY +using System; +namespace MongoDB.Bson.Serialization.Attributes +{ + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] + public class BsonIgnoreAttribute : Attribute + { + + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Platform/Unity/Attributes/BsonIgnoreIfDefaultAttribute.cs b/Fantasy/Fantays.Console/Runtime/Core/Platform/Unity/Attributes/BsonIgnoreIfDefaultAttribute.cs new file mode 100644 index 0000000..82dddde --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Platform/Unity/Attributes/BsonIgnoreIfDefaultAttribute.cs @@ -0,0 +1,13 @@ +#if FANTASY_UNITY +using System; +namespace MongoDB.Bson.Serialization.Attributes +{ + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] + public class BsonIgnoreIfDefaultAttribute : Attribute + { + public BsonIgnoreIfDefaultAttribute() { } + + public BsonIgnoreIfDefaultAttribute(bool value) { } + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Platform/Unity/Attributes/BsonIgnoreIfNullAttribute.cs b/Fantasy/Fantays.Console/Runtime/Core/Platform/Unity/Attributes/BsonIgnoreIfNullAttribute.cs new file mode 100644 index 0000000..f330a8b --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Platform/Unity/Attributes/BsonIgnoreIfNullAttribute.cs @@ -0,0 +1,11 @@ +#if FANTASY_UNITY +using System; +namespace MongoDB.Bson.Serialization.Attributes +{ + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] + public class BsonIgnoreIfNullAttribute : Attribute + { + + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Platform/Unity/Entry.cs b/Fantasy/Fantays.Console/Runtime/Core/Platform/Unity/Entry.cs new file mode 100644 index 0000000..e5377f2 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Platform/Unity/Entry.cs @@ -0,0 +1,102 @@ +#if FANTASY_UNITY +using System.Linq; +using Fantasy.Assembly; +using Fantasy.Async; +using Fantasy.Serialize; +using UnityEngine; +using UnityEngine.Scripting; + +#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. +#pragma warning disable CS8603 // Possible null reference return. +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. + +namespace Fantasy.Platform.Unity +{ + public sealed class FantasyObject : MonoBehaviour + { + public static GameObject FantasyObjectGameObject { get; private set; } + + public static void OnRuntimeMethodLoad() + { + FantasyObjectGameObject = new GameObject("Fantasy.Net"); + DontDestroyOnLoad(FantasyObjectGameObject); + } + private void OnApplicationQuit() + { + Destroy(FantasyObjectGameObject); + } + } + + public struct OnSceneCreate + { + public Scene Scene; + public object Arg; + } + + public class Entry : MonoBehaviour + { + private static bool _isInit; + public static Scene Scene { get; private set; } + + /// + /// 初始化框架 + /// + /// + public static async FTask Initialize(params System.Reflection.Assembly[] assemblies) + { + if (_isInit) + { + Log.Error("Fantasy has already been initialized and does not need to be initialized again!"); + return; + } + FantasyObject.OnRuntimeMethodLoad(); + Log.Register(new UnityLog()); + await AssemblySystem.InnerInitialize(assemblies); + // 初始化序列化 + SerializerManager.Initialize(); +#if FANTASY_WEBGL + ThreadSynchronizationContext.Initialize(); +#endif + _isInit = true; + FantasyObject.FantasyObjectGameObject.AddComponent(); + Log.Debug("Fantasy Initialize Complete!"); + } + + /// + /// 在Entry中创建一个Scene,如果Scene已经被创建过,将先销毁Scene再创建。 + /// + /// + /// + /// + public static async FTask CreateScene(object arg = null, string sceneRuntimeMode = SceneRuntimeMode.MainThread) + { + Scene?.Dispose(); + Scene = await Scene.Create(sceneRuntimeMode); + await Scene.EventComponent.PublishAsync(new OnSceneCreate() + { + Arg = arg, + Scene = Scene + }); + return Scene; + } + + private void Update() + { + ThreadScheduler.Update(); + } + + private void OnDestroy() + { + AssemblySystem.Dispose(); + SerializerManager.Dispose(); + if (Scene != null) + { + Scene?.Dispose(); + Scene = null; + } + _isInit = false; + } + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Platform/Unity/Temp.cs b/Fantasy/Fantays.Console/Runtime/Core/Platform/Unity/Temp.cs new file mode 100644 index 0000000..399488f --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Platform/Unity/Temp.cs @@ -0,0 +1,108 @@ +// using System.Reflection; +// using Fantasy.Assembly; +// using Fantasy.Async; +// // using UnityEngine; +// #pragma warning disable CS0649 // Field is never assigned to, and will always have its default value +// #pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. +// #pragma warning disable CS8603 // Possible null reference return. +// #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +// #pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. +// +// namespace Fantasy.Platform.Unity +// { +// public class MonoBehaviour +// { +// +// } +// +// public class GameObject +// { +// public GameObject(string name) +// { +// +// } +// } +// +// internal enum RuntimeInitializeLoadType +// { +// BeforeSceneLoad = 1, +// } +// +// internal class RuntimeInitializeOnLoadMethodAttribute : Attribute +// { +// public RuntimeInitializeLoadType RuntimeInitializeLoadType; +// +// public RuntimeInitializeOnLoadMethodAttribute(RuntimeInitializeLoadType loadType) +// { +// +// } +// } +// +// public sealed class FantasyObject : MonoBehaviour +// { +// public static GameObject FantasyObjectGameObject { get; private set; } +// // 这个方法将在游戏启动时自动调用 +// [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] +// static void OnRuntimeMethodLoad() +// { +// FantasyObjectGameObject = new GameObject("Fantasy.Net"); +// // DontDestroyOnLoad(FantasyObjectGameObject); +// } +// private void OnApplicationQuit() +// { +// // Destroy(FantasyObjectGameObject); +// } +// } +// +// public struct OnFantasyInit +// { +// public Scene Scene; +// } +// +// public class Entry : MonoBehaviour +// { +// private static bool _isInit; +// public static Scene Scene { get; private set; } +// /// +// /// 初始化框架 +// /// +// public static async FTask Initialize(params System.Reflection.Assembly[] assemblies) +// { +// Scene?.Dispose(); +// // 初始化程序集管理系统 +// AssemblySystem.Initialize(assemblies); +// if (!_isInit) +// { +// #if FANTASY_WEBGL +// ThreadSynchronizationContext.Initialize(); +// #endif +// _isInit = true; +// // FantasyObject.FantasyObjectGameObject.AddComponent(); +// } +// // Scene = await Scene.Create(SceneRuntimeType.MainThread); +// // await Scene.EventComponent.PublishAsync(new OnFantasyInit() +// // { +// // Scene = Scene +// // }); +// // return Scene; +// await FTask.CompletedTask; +// return null; +// } +// +// private void Update() +// { +// ThreadScheduler.Update(); +// } +// +// private void OnDestroy() +// { +// AssemblySystem.Dispose(); +// if (Scene != null) +// { +// Scene?.Dispose(); +// Scene = null; +// } +// _isInit = false; +// } +// } +// } diff --git a/Fantasy/Fantays.Console/Runtime/Core/Platform/Unity/ThreadSynchronizationContext.cs b/Fantasy/Fantays.Console/Runtime/Core/Platform/Unity/ThreadSynchronizationContext.cs new file mode 100644 index 0000000..44b165a --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Platform/Unity/ThreadSynchronizationContext.cs @@ -0,0 +1,104 @@ +#if FANTASY_UNITY && !FANTASY_WEBGL +#pragma warning disable CS8765 +#pragma warning disable CS8601 +#pragma warning disable CS8618 +using System; +using System.Collections.Concurrent; +using System.Threading; + +namespace Fantasy +{ + public sealed class ThreadSynchronizationContext : SynchronizationContext + { + private Action _actionHandler; + private readonly ConcurrentQueue _queue = new(); + + public void Update() + { + while (_queue.TryDequeue(out _actionHandler)) + { + try + { + _actionHandler(); + } + catch (Exception e) + { + Log.Error(e); + } + } + } + + public override void Post(SendOrPostCallback callback, object state) + { + Post(() => callback(state)); + } + + public void Post(Action action) + { + _queue.Enqueue(action); + } + } +} +#endif +#if FANTASY_UNITY && FANTASY_WEBGL +using System; +using System.Collections.Generic; +using System.Threading; +using Fantasy; +using UnityEngine; +using Object = UnityEngine.Object; + +public class WebGLSynchronizationContextUpdater : MonoBehaviour +{ + private ThreadSynchronizationContext _context; + + public void Initialize(ThreadSynchronizationContext context) + { + _context = context; + } + + void Update() + { + _context.Update(); + } +} +public sealed class ThreadSynchronizationContext : SynchronizationContext +{ + private Action _actionHandler; + private readonly Queue _queue = new(); + + public static void Initialize() + { + var context = new ThreadSynchronizationContext(); + SetSynchronizationContext(context); + var go = new GameObject("WebGLSynchronizationContextUpdater"); + go.AddComponent().Initialize(context); + Object.DontDestroyOnLoad(go); + } + + public void Update() + { + while (_queue.TryDequeue(out _actionHandler)) + { + try + { + _actionHandler(); + } + catch (Exception e) + { + Log.Error(e); + } + } + } + + public override void Post(SendOrPostCallback callback, object state) + { + Post(() => callback(state)); + } + + public void Post(Action action) + { + _queue.Enqueue(action); + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Pool/Concurrent/MultiThreadPool.cs b/Fantasy/Fantays.Console/Runtime/Core/Pool/Concurrent/MultiThreadPool.cs new file mode 100644 index 0000000..4c946d1 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Pool/Concurrent/MultiThreadPool.cs @@ -0,0 +1,37 @@ +#if !FANTASY_WEBGL +using System; +using System.Collections.Concurrent; +using System.Runtime.CompilerServices; +#pragma warning disable CS8603 // Possible null reference return. + +namespace Fantasy.Pool +{ + /// + /// 线程安全的静态通用对象池。 + /// + internal static class MultiThreadPool + { + private static readonly ConcurrentDictionary ObjectPools = new ConcurrentDictionary(); + + public static T Rent() where T : IPool, new() + { + return ObjectPools.GetOrAdd(typeof(T), t => new MultiThreadPoolQueue(2000, () => new T())).Rent(); + } + + public static IPool Rent(Type type) + { + return ObjectPools.GetOrAdd(type, t => new MultiThreadPoolQueue(2000, CreateInstance.CreateIPool(type))).Rent(); + } + + public static void Return(T obj) where T : IPool, new() + { + if (!obj.IsPool()) + { + return; + } + + ObjectPools.GetOrAdd(typeof(T), t => new MultiThreadPoolQueue(2000, () => new T())).Return(obj); + } + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Pool/Concurrent/MultiThreadPoolQueue.cs b/Fantasy/Fantays.Console/Runtime/Core/Pool/Concurrent/MultiThreadPoolQueue.cs new file mode 100644 index 0000000..df15a1c --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Pool/Concurrent/MultiThreadPoolQueue.cs @@ -0,0 +1,76 @@ +#if !FANTASY_WEBGL +using System; +using System.Collections.Concurrent; +using System.Threading; +// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract +#pragma warning disable CS8601 // Possible null reference assignment. +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. +#pragma warning disable CS8603 // Possible null reference return. + +namespace Fantasy.Pool +{ + /// + /// 线程安全的对象池。 + /// + internal class MultiThreadPoolQueue + { + private int _poolCount; + private readonly int _maxCapacity; + private readonly Func _createInstance; + private readonly ConcurrentQueue _poolQueue = new ConcurrentQueue(); + private MultiThreadPoolQueue() { } + + public MultiThreadPoolQueue(int maxCapacity, Func createInstance) + { + _maxCapacity = maxCapacity; + _createInstance = createInstance; + } + + public T Rent() where T : IPool, new() + { + if (!_poolQueue.TryDequeue(out var t)) + { + var pool = new T(); + pool.SetIsPool(true); + return pool; + } + + t.SetIsPool(true); + Interlocked.Decrement(ref _poolCount); + return (T)t; + } + + public IPool Rent() + { + if (!_poolQueue.TryDequeue(out var t)) + { + var instance = _createInstance(); + instance.SetIsPool(true); + return instance; + } + + t.SetIsPool(true); + Interlocked.Decrement(ref _poolCount); + return t; + } + + public void Return(IPool obj) + { + if (!obj.IsPool()) + { + return; + } + + obj.SetIsPool(false); + + if (Interlocked.Increment(ref _poolCount) <= _maxCapacity) + { + _poolQueue.Enqueue(obj); + return; + } + + Interlocked.Decrement(ref _poolCount); + } + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Pool/Interface/IPool.cs b/Fantasy/Fantays.Console/Runtime/Core/Pool/Interface/IPool.cs new file mode 100644 index 0000000..1775f28 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Pool/Interface/IPool.cs @@ -0,0 +1,18 @@ +namespace Fantasy.Pool +{ + /// + /// 实现了这个接口代表支持对象池 + /// + public interface IPool + { + /// + /// 是否从池里创建的 + /// + bool IsPool(); + /// + /// 设置是否从池里创建的 + /// + /// + void SetIsPool(bool isPool); + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Pool/Normal/Pool.cs b/Fantasy/Fantays.Console/Runtime/Core/Pool/Normal/Pool.cs new file mode 100644 index 0000000..88006e1 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Pool/Normal/Pool.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; +// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract +// ReSharper disable CheckNamespace + +namespace Fantasy.Pool +{ + /// + /// 静态的对象池系统,不支持多线程。 + /// + /// + public static class Pool where T : IPool, new() + { + private static readonly Queue PoolQueue = new Queue(); + /// + /// 池子里可用的数量 + /// + public static int Count => PoolQueue.Count; + + /// + /// 租借 + /// + /// + public static T Rent() + { + return PoolQueue.Count == 0 ? new T() : PoolQueue.Dequeue(); + } + + /// + /// 租借 + /// + /// 如果池子里没有,会先执行这个委托。 + /// + public static T Rent(Func generator) + { + return PoolQueue.Count == 0 ? generator() : PoolQueue.Dequeue(); + } + + /// + /// 返还 + /// + /// + public static void Return(T t) + { + if (t == null) + { + return; + } + + PoolQueue.Enqueue(t); + } + + /// + /// 返还 + /// + /// 返还的东西 + /// 返还后执行的委托 + public static void Return(T t, Action reset) + { + if (t == null) + { + return; + } + + reset(t); + PoolQueue.Enqueue(t); + } + + /// + /// 清空池子 + /// + public static void Clear() + { + PoolQueue.Clear(); + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Pool/Normal/PoolCore.cs b/Fantasy/Fantays.Console/Runtime/Core/Pool/Normal/PoolCore.cs new file mode 100644 index 0000000..94144f1 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Pool/Normal/PoolCore.cs @@ -0,0 +1,203 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using Fantasy.DataStructure.Collection; + +// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract +#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type. +#pragma warning disable CS8603 // Possible null reference return. + +namespace Fantasy.Pool +{ + /// + /// 对象池抽象接口,用于创建和管理可重复使用的对象实例。 + /// + public abstract class PoolCore : IDisposable + { + private int _poolCount; + private readonly int _maxCapacity; + /// + /// 池子里可用的数量 + /// + public int Count => _poolQueue.Count; + private readonly OneToManyQueue _poolQueue = new OneToManyQueue(); + private readonly Dictionary> _typeCheckCache = new Dictionary>(); + + /// + /// 构造函数 + /// + /// 初始的容量 + protected PoolCore(int maxCapacity) + { + _maxCapacity = maxCapacity; + } + + /// + /// 租借 + /// + /// + /// + public T Rent() where T : IPool, new() + { + if (!_poolQueue.TryDequeue(typeof(T), out var queue)) + { + queue = new T(); + } + + queue.SetIsPool(true); + _poolCount--; + return (T)queue; + } + + /// + /// 租借 + /// + /// 租借的类型 + /// + /// + public IPool Rent(Type type) + { + if (!_poolQueue.TryDequeue(type, out var queue)) + { + if (!_typeCheckCache.TryGetValue(type, out var createInstance)) + { + if (!typeof(IPool).IsAssignableFrom(type)) + { + throw new NotSupportedException($"{this.GetType().FullName} Type:{type.FullName} must inherit from IPool"); + } + else + { + createInstance = CreateInstance.CreateIPool(type); + _typeCheckCache[type] = createInstance; + } + } + + var instance = createInstance(); + instance.SetIsPool(true); + return instance; + } + + queue.SetIsPool(true); + _poolCount--; + return queue; + } + + /// + /// 返还 + /// + /// + /// + public void Return(Type type, IPool obj) + { + if (obj == null) + { + return; + } + + if (!obj.IsPool()) + { + return; + } + + if (_poolCount >= _maxCapacity) + { + return; + } + + _poolCount++; + obj.SetIsPool(false); + _poolQueue.Enqueue(type, obj); + } + + /// + /// 销毁方法 + /// + public virtual void Dispose() + { + _poolCount = 0; + _poolQueue.Clear(); + _typeCheckCache.Clear(); + } + } + + /// + /// 泛型对象池核心类,用于创建和管理可重复使用的对象实例。 + /// + /// 要池化的对象类型 + public abstract class PoolCore where T : IPool, new() + { + private int _poolCount; + private readonly int _maxCapacity; + private readonly Queue _poolQueue = new Queue(); + /// + /// 池子里可用的数量 + /// + public int Count => _poolQueue.Count; + + /// + /// 构造函数 + /// + /// 初始的容量 + protected PoolCore(int maxCapacity) + { + _maxCapacity = maxCapacity; + } + + /// + /// 租借 + /// + /// + public virtual T Rent() + { + T dequeue; + + if (_poolQueue.Count == 0) + { + dequeue = new T(); + } + else + { + _poolCount--; + dequeue = _poolQueue.Dequeue(); + } + + dequeue.SetIsPool(true); + return dequeue; + } + + /// + /// 返还 + /// + /// + public virtual void Return(T item) + { + if (item == null) + { + return; + } + + if (!item.IsPool()) + { + return; + } + + if (_poolCount >= _maxCapacity) + { + return; + } + + _poolCount++; + item.SetIsPool(false); + _poolQueue.Enqueue(item); + } + + /// + /// 销毁方法 + /// + public virtual void Dispose() + { + _poolCount = 0; + _poolQueue.Clear(); + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Pool/Normal/PoolWithDisposable.cs b/Fantasy/Fantays.Console/Runtime/Core/Pool/Normal/PoolWithDisposable.cs new file mode 100644 index 0000000..3f97f1c --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Pool/Normal/PoolWithDisposable.cs @@ -0,0 +1,140 @@ +using System; +using System.Collections.Generic; +// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract + +namespace Fantasy.Pool +{ + /// + /// 静态通用对象池,用于存储实现了 IDisposable 接口的对象。 + /// + /// 要存储在对象池中的对象类型,必须实现 IDisposable 接口。 + public abstract class PoolWithDisposable : IDisposable where T : IPool, IDisposable, new() + { + private int _poolCount; + private readonly int _maxCapacity; + private readonly Queue _poolQueue = new Queue(); + /// + /// 池子里可用的数量 + /// + public int Count => _poolQueue.Count; + + /// + /// 构造函数 + /// + /// 初始的容量 + protected PoolWithDisposable(int maxCapacity) + { + _maxCapacity = maxCapacity; + } + + /// + /// 租借 + /// + /// + public T Rent() + { + T dequeue; + if (_poolQueue.Count == 0) + { + dequeue = new T(); + } + else + { + _poolCount--; + dequeue = _poolQueue.Dequeue(); + } + + dequeue.SetIsPool(true); + return dequeue; + } + + /// + /// 租借 + /// + /// + /// + public T Rent(Func generator) + { + T dequeue; + + if (_poolQueue.Count == 0) + { + dequeue = generator(); + } + else + { + _poolCount--; + dequeue = _poolQueue.Dequeue(); + } + + dequeue.SetIsPool(true); + return dequeue; + } + + /// + /// 返还 + /// + /// + public void Return(T t) + { + if (t == null) + { + return; + } + + if (!t.IsPool()) + { + return; + } + + if (_poolCount >= _maxCapacity) + { + return; + } + + _poolCount++; + t.SetIsPool(true); + _poolQueue.Enqueue(t); + t.Dispose(); + } + + /// + /// 返还 + /// + /// + /// + public void Return(T t, Action reset) + { + if (t == null) + { + return; + } + + if (!t.IsPool()) + { + reset(t); + return; + } + + if (_poolCount >= _maxCapacity) + { + return; + } + + reset(t); + _poolCount++; + t.SetIsPool(false); + _poolQueue.Enqueue(t); + t.Dispose(); + } + + /// + /// 销毁方法 + /// + public virtual void Dispose() + { + _poolCount = 0; + _poolQueue.Clear(); + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Pool/PoolHelper.cs b/Fantasy/Fantays.Console/Runtime/Core/Pool/PoolHelper.cs new file mode 100644 index 0000000..d9214fd --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Pool/PoolHelper.cs @@ -0,0 +1,79 @@ +using System; +using System.Reflection.Emit; +using Fantasy.Serialize; + +#pragma warning disable CS8604 // Possible null reference argument. + +namespace Fantasy.Pool +{ + internal static class CreateInstance where T : IPool + { + public static Func Create { get; } + + static CreateInstance() + { + var type = typeof(T); + var dynamicMethod = new DynamicMethod($"CreateInstance_{type.Name}", type, Type.EmptyTypes, true); + var il = dynamicMethod.GetILGenerator(); + il.Emit(OpCodes.Newobj, type.GetConstructor(Type.EmptyTypes)); + il.Emit(OpCodes.Ret); + Create = (Func) dynamicMethod.CreateDelegate(typeof(Func)); + } + } + + internal static class CreateInstance + { + public static Func CreateIPool(Type type) + { + var dynamicMethod = new DynamicMethod($"CreateInstance_{type.Name}", type, Type.EmptyTypes, true); + var il = dynamicMethod.GetILGenerator(); + il.Emit(OpCodes.Newobj, type.GetConstructor(Type.EmptyTypes)); + il.Emit(OpCodes.Ret); + return (Func)dynamicMethod.CreateDelegate(typeof(Func)); + } + + public static Func CreateObject(Type type) + { + var dynamicMethod = new DynamicMethod($"CreateInstance_{type.Name}", type, Type.EmptyTypes, true); + var il = dynamicMethod.GetILGenerator(); + il.Emit(OpCodes.Newobj, type.GetConstructor(Type.EmptyTypes)); + il.Emit(OpCodes.Ret); + return (Func)dynamicMethod.CreateDelegate(typeof(Func)); + } + + public static Func CreateMessage(Type type) + { + var dynamicMethod = new DynamicMethod($"CreateInstance_{type.Name}", type, Type.EmptyTypes, true); + var il = dynamicMethod.GetILGenerator(); + il.Emit(OpCodes.Newobj, type.GetConstructor(Type.EmptyTypes)); + il.Emit(OpCodes.Ret); + return (Func)dynamicMethod.CreateDelegate(typeof(Func)); + } + } + + // public static class CreateInstance + // { + // public static Func Create(Type type) + // { + // var dynamicMethod = new DynamicMethod($"CreateInstance_{type.Name}", type, Type.EmptyTypes, true); + // var il = dynamicMethod.GetILGenerator(); + // il.Emit(OpCodes.Newobj, type.GetConstructor(Type.EmptyTypes)); + // il.Emit(OpCodes.Ret); + // return (Func)dynamicMethod.CreateDelegate(typeof(Func)); + // } + // } + + // /// + // /// 利用泛型的特性来减少反射的使用。 + // /// + // /// + // public static class PoolChecker where T : new() + // { + // public static bool IsPool { get; } + // + // static PoolChecker() + // { + // IsPool = typeof(IPool).IsAssignableFrom(typeof(T)); + // } + // } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Scene/ISceneUpdate.cs b/Fantasy/Fantays.Console/Runtime/Core/Scene/ISceneUpdate.cs new file mode 100644 index 0000000..f736edf --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Scene/ISceneUpdate.cs @@ -0,0 +1,15 @@ +namespace Fantasy +{ + internal interface ISceneUpdate + { + void Update(); + } + + internal sealed class EmptySceneUpdate : ISceneUpdate + { + public void Update() + { + + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Scene/Scene.cs b/Fantasy/Fantays.Console/Runtime/Core/Scene/Scene.cs new file mode 100644 index 0000000..e961533 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Scene/Scene.cs @@ -0,0 +1,619 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Fantasy.Async; +using Fantasy.Entitas; +using Fantasy.Event; +using Fantasy.IdFactory; +using Fantasy.Network; +using Fantasy.Network.Interface; +using Fantasy.Pool; +using Fantasy.Scheduler; +using Fantasy.Timer; +#if FANTASY_NET +using Fantasy.DataBase; +using Fantasy.Platform.Net; +using Fantasy.SingleCollection; +using System.Runtime.CompilerServices; +using Fantasy.Network.Route; +using Fantasy.Network.Roaming; +#endif +// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract +#pragma warning disable CS8601 // Possible null reference assignment. +#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. +#pragma warning disable CS8603 // Possible null reference return. +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. +#pragma warning disable CS8602 // Dereference of a possibly null reference. +#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type. +namespace Fantasy +{ + /// + /// 当Scene创建完成后发送的事件参数 + /// + public struct OnCreateScene + { + /// + /// 获取与事件关联的场景实体。 + /// + public readonly Scene Scene; + /// + /// 初始化一个新的 OnCreateScene 实例。 + /// + /// + public OnCreateScene(Scene scene) + { + Scene = scene; + } + } + + /// + /// 表示一个场景实体,用于创建与管理特定的游戏场景信息。 + /// + public partial class Scene : Entity + { + #region Members + /// + /// Scene的运行类型 + /// + public SceneRuntimeType SceneRuntimeType { get; protected set; } +#if FANTASY_NET + /// + /// Scene类型,对应SceneConfig的SceneType + /// + public int SceneType { get; protected set; } + /// + /// 所属的世界 + /// + public World World { get; protected set; } + /// + /// 所在的Process + /// + public Process Process { get; protected set; } + /// + /// SceneConfig的Id + /// + public uint SceneConfigId { get; protected set; } + internal ANetwork InnerNetwork { get; private set; } + internal ANetwork OuterNetwork { get; private set; } + internal SceneConfig SceneConfig => SceneConfigData.Instance.Get(SceneConfigId); + private readonly Dictionary _processSessionInfos = new Dictionary(); +#endif + /// + /// 当前Scene的上下文 + /// + public ThreadSynchronizationContext ThreadSynchronizationContext { get; internal set; } + /// + /// 当前Scene的下创建的Entity + /// + private readonly Dictionary _entities = new Dictionary(); + internal readonly Dictionary> TypeInstance = new Dictionary>(); + #endregion + + #region IdFactory + + /// + /// Entity实体Id的生成器 + /// + public IEntityIdFactory EntityIdFactory { get; protected set; } + /// + /// Entity实体RuntimeId的生成器 + /// + public IRuntimeIdFactory RuntimeIdFactory { get; protected set; } + + #endregion + + #region Pool + + internal EntityPool EntityPool; + internal EntityListPool EntityListPool; + internal EntitySortedDictionaryPool EntitySortedDictionaryPool; + + #endregion + + #region Component + + /// + /// Scene下的任务调度器系统组件 + /// + public TimerComponent TimerComponent { get; internal set; } + /// + /// Scene下的事件系统组件 + /// + public EventComponent EventComponent { get; internal set; } + /// + /// Scene下的ESC系统组件 + /// + public EntityComponent EntityComponent { get; internal set; } + /// + /// Scene下的网络消息对象池组件 + /// + public MessagePoolComponent MessagePoolComponent { get; internal set; } + /// + /// Scene下的协程锁组件 + /// + public CoroutineLockComponent CoroutineLockComponent { get; internal set; } + /// + /// Scene下的网络消息派发组件 + /// + internal MessageDispatcherComponent MessageDispatcherComponent { get; set; } +#if FANTASY_NET + /// + /// Scene下的Entity分表组件 + /// + public SingleCollectionComponent SingleCollectionComponent { get; internal set; } + /// + /// Scene下的内网消息发送组件 + /// + public NetworkMessagingComponent NetworkMessagingComponent { get; internal set; } + /// + /// Scene下的漫游终端管理组件 + /// + public TerminusComponent TerminusComponent { get; internal set; } + /// + /// Scene下的Session漫游组件 + /// + public RoamingComponent RoamingComponent { get; internal set; } +#endif + #endregion + + #region Initialize + + private async FTask Initialize() + { + EntityPool = new EntityPool(); + EntityListPool = new EntityListPool(); + EntitySortedDictionaryPool = new EntitySortedDictionaryPool(); + SceneUpdate = EntityComponent = await Create(this, false, false).Initialize(); + MessagePoolComponent = Create(this,false,true); + EventComponent = await Create(this,false,true).Initialize(); + TimerComponent = Create(this, false, true).Initialize(); + CoroutineLockComponent = Create(this, false, true).Initialize(); + MessageDispatcherComponent = await Create(this, false, true).Initialize(); +#if FANTASY_NET + NetworkMessagingComponent = Create(this, false, true); + SingleCollectionComponent = await Create(this, false, true).Initialize(); + TerminusComponent = Create(this, false, true); + RoamingComponent = Create(this, false, true).Initialize(); +#endif + } + + /// + /// Scene销毁方法,执行了该方法会把当前Scene下的所有实体都销毁掉。 + /// + public override void Dispose() + { + if (IsDisposed) + { + return; + } + + base.Dispose(); + _entities.Remove(RuntimeId); + + switch (SceneRuntimeType) + { + case SceneRuntimeType.Root: + { +#if FANTASY_NET + foreach (var (_, processSessionInfo) in _processSessionInfos.ToList()) + { + processSessionInfo.Dispose(); + } + + _processSessionInfos.Clear(); +#endif + _entities.Remove(EntityComponent.RuntimeId); + + foreach (var (runtimeId, entity) in _entities.ToList()) + { + if (runtimeId != entity.RuntimeId) + { + continue; + } + entity.Dispose(); + } + + _entities.Clear(); +#if FANTASY_UNITY + _unityWorldId--; + _unitySceneId--; +#endif + TypeInstance.Clear(); +#if FANTASY_NET + Process.RemoveScene(this, false); + Process.RemoveSceneToProcess(this, false); +#endif + EntityComponent.Dispose(); + EntityPool.Dispose(); + EntityListPool.Dispose(); + EntitySortedDictionaryPool.Dispose(); + break; + } + case SceneRuntimeType.SubScene: + { + break; + } + default: + { + Log.Error($"SceneRuntimeType: {SceneRuntimeType} The unsupported SceneRuntimeType of the Scene executed Dispose."); + break; + } + } + + SceneUpdate = null; + EntityIdFactory = null; + RuntimeIdFactory = null; + + EntityPool = null; + EntityListPool = null; + EntitySortedDictionaryPool = null; + EntityComponent = null; + TimerComponent = null; + EventComponent = null; + MessagePoolComponent = null; + CoroutineLockComponent = null; + MessageDispatcherComponent = null; +#if FANTASY_NET + World = null; + Process = null; + SceneType = 0; + SceneConfigId = 0; + SingleCollectionComponent = null; + NetworkMessagingComponent = null; + TerminusComponent = null; + RoamingComponent = null; +#elif FANTASY_UNITY + Session = null; + UnityNetwork = null; +#endif + ThreadSynchronizationContext = null; + SceneRuntimeType = SceneRuntimeType.None; + } + + #endregion + + internal ISceneUpdate SceneUpdate { get; set; } + + internal void Update() + { + try + { + SceneUpdate.Update(); + } + catch (Exception e) + { + Log.Error(e); + } + } + + #region Create + +#if FANTASY_UNITY || FANTASY_CONSOLE + private static uint _unitySceneId = 0; + private static byte _unityWorldId = 0; + public Session Session { get; private set; } + private AClientNetwork UnityNetwork { get; set; } + /// + /// 创建一个Unity的Scene,注意:该方法只能在主线程下使用。 + /// + /// 选择Scene的运行方式 + /// + /// + public static async FTask Create(string sceneRuntimeMode = SceneRuntimeMode.MainThread) + { + var world = ++_unityWorldId; + + if (world > byte.MaxValue - 1) + { + throw new Exception($"World ID ({world}) exceeds the maximum allowed value of 255."); + } + + var sceneId = (uint)(++_unitySceneId + world * 1000); + + if (sceneId > 255255) + { + throw new Exception($"Scene ID ({sceneId}) exceeds the maximum allowed value of 255255."); + } + + var scene = new Scene(); + scene.Scene = scene; + scene.Parent = scene; + scene.Type = typeof(Scene); + scene.SceneRuntimeType = SceneRuntimeType.Root; + scene.EntityIdFactory = IdFactoryHelper.EntityIdFactory(sceneId, world); + scene.RuntimeIdFactory = IdFactoryHelper.RuntimeIdFactory(0, sceneId, world); + scene.Id = IdFactoryHelper.EntityId(0, sceneId, world, 0); + scene.RuntimeId = IdFactoryHelper.RuntimeId(0, sceneId, world, 0); + scene.AddEntity(scene); + await SetScheduler(scene, sceneRuntimeMode); + scene.ThreadSynchronizationContext.Post(() => + { + scene.EventComponent.PublishAsync(new OnCreateScene(scene)).Coroutine(); + }); + return scene; + } + public Session Connect(string remoteAddress, NetworkProtocolType networkProtocolType, Action onConnectComplete, Action onConnectFail, Action onConnectDisconnect, bool isHttps, int connectTimeout = 5000) + { + UnityNetwork?.Dispose(); + UnityNetwork = NetworkProtocolFactory.CreateClient(this, networkProtocolType, NetworkTarget.Outer); + Session = UnityNetwork.Connect(remoteAddress, onConnectComplete, onConnectFail, onConnectDisconnect, isHttps, connectTimeout); + return Session; + } +#endif +#if FANTASY_NET + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Scene Create(Process process, byte worldId, uint sceneConfigId) + { + var scene = new Scene(); + scene.Scene = scene; + scene.Parent = scene; + scene.Type = typeof(Scene); + scene.Process = process; + scene.SceneRuntimeType = SceneRuntimeType.Root; + scene.EntityIdFactory = IdFactoryHelper.EntityIdFactory(sceneConfigId, worldId); + scene.RuntimeIdFactory = IdFactoryHelper.RuntimeIdFactory(0,sceneConfigId, worldId); + scene.Id = IdFactoryHelper.EntityId(0, sceneConfigId, worldId, 0); + scene.RuntimeId = IdFactoryHelper.RuntimeId(0, sceneConfigId, worldId, 0); + scene.AddEntity(scene); + return scene; + } + /// + /// 创建一个新的Scene + /// + /// 所属的Process + /// 对应的MachineConfig配置文件 + /// 对应的SceneConfig配置文件 + /// 创建成功后会返回创建的Scene的实例 + public static async FTask Create(Process process, MachineConfig machineConfig, SceneConfig sceneConfig) + { + var scene = Create(process, (byte)sceneConfig.WorldConfigId, sceneConfig.Id); + scene.SceneType = sceneConfig.SceneType; + scene.SceneConfigId = sceneConfig.Id; + await SetScheduler(scene, sceneConfig.SceneRuntimeMode); + + if (sceneConfig.WorldConfigId != 0) + { + scene.World = World.Create(scene, (byte)sceneConfig.WorldConfigId); + } + + if (sceneConfig.InnerPort != 0) + { + // 创建内网网络服务器 + scene.InnerNetwork = NetworkProtocolFactory.CreateServer(scene, ProcessDefine.InnerNetwork, NetworkTarget.Inner, machineConfig.InnerBindIP, sceneConfig.InnerPort); + } + + if (sceneConfig.OuterPort != 0) + { + // 创建外网网络服务 + var networkProtocolType = Enum.Parse(sceneConfig.NetworkProtocol); + scene.OuterNetwork = NetworkProtocolFactory.CreateServer(scene, networkProtocolType, NetworkTarget.Outer, machineConfig.OuterBindIP, sceneConfig.OuterPort); + } + + Process.AddScene(scene); + process.AddSceneToProcess(scene); + + scene.ThreadSynchronizationContext.Post(() => + { + if (sceneConfig.SceneTypeString == "Addressable") + { + // 如果是AddressableScene,自动添加上AddressableManageComponent。 + scene.AddComponent(); + } + + scene.EventComponent.PublishAsync(new OnCreateScene(scene)).Coroutine(); + }); + + return scene; + } + /// + /// 在Scene下面创建一个子Scene,一般用于副本,或者一些特殊的场景。 + /// + /// 主Scene的实例 + /// SceneType,可以在SceneType里找到,例如:SceneType.Addressable + /// 子Scene创建成功后执行的委托,可以传递null + /// + public static SubScene CreateSubScene(Scene parentScene, int sceneType, Action onSubSceneComplete = null) + { + var scene = new SubScene(); + scene.Scene = scene; + scene.Parent = scene; + scene.RootScene = parentScene; + scene.Type = typeof(SubScene); + scene.SceneType = sceneType; + scene.World = parentScene.World; + scene.Process = parentScene.Process; + scene.SceneRuntimeType = SceneRuntimeType.SubScene; + scene.EntityIdFactory = parentScene.EntityIdFactory; + scene.RuntimeIdFactory = parentScene.RuntimeIdFactory; + scene.Id = scene.EntityIdFactory.Create; + scene.RuntimeId = scene.RuntimeIdFactory.Create; + scene.AddEntity(scene); + scene.Initialize(parentScene); + scene.ThreadSynchronizationContext.Post(() => OnEvent().Coroutine()); + return scene; + async FTask OnEvent() + { + await scene.EventComponent.PublishAsync(new OnCreateScene(scene)); + onSubSceneComplete?.Invoke(scene, parentScene); + } + } +#endif + private static async FTask SetScheduler(Scene scene, string sceneRuntimeMode) + { + switch (sceneRuntimeMode) + { + case "MainThread": + { + scene.ThreadSynchronizationContext = ThreadScheduler.MainScheduler.ThreadSynchronizationContext; + scene.SceneUpdate = new EmptySceneUpdate(); + ThreadScheduler.AddMainScheduler(scene); + await scene.Initialize(); + break; + } + case "MultiThread": + { +#if !FANTASY_WEBGL + scene.ThreadSynchronizationContext = new ThreadSynchronizationContext(); +#endif + scene.SceneUpdate = new EmptySceneUpdate(); + ThreadScheduler.AddToMultiThreadScheduler(scene); + await scene.Initialize(); + break; + } + case "ThreadPool": + { +#if !FANTASY_WEBGL + scene.ThreadSynchronizationContext = new ThreadSynchronizationContext(); +#endif + scene.SceneUpdate = new EmptySceneUpdate(); + ThreadScheduler.AddToThreadPoolScheduler(scene); + await scene.Initialize(); + break; + } + } + } + #endregion + + #region Entities + + /// + /// 添加一个实体到当前Scene下 + /// + /// 实体实例 + public virtual void AddEntity(Entity entity) + { + _entities.Add(entity.RuntimeId, entity); + } + + /// + /// 根据RunTimeId查询一个实体 + /// + /// 实体的RunTimeId + /// 返回的实体 + public virtual Entity GetEntity(long runTimeId) + { + return _entities.TryGetValue(runTimeId, out var entity) ? entity : null; + } + + /// + /// 根据RunTimeId查询一个实体 + /// + /// 实体的RunTimeId + /// 实体实例 + /// 返回一个bool值来提示是否查找到这个实体 + public virtual bool TryGetEntity(long runTimeId, out Entity entity) + { + return _entities.TryGetValue(runTimeId, out entity); + } + + /// + /// 根据RunTimeId查询一个实体 + /// + /// 实体的RunTimeId + /// 要查询实体的泛型类型 + /// 返回的实体 + public virtual T GetEntity(long runTimeId) where T : Entity + { + return _entities.TryGetValue(runTimeId, out var entity) ? (T)entity : null; + } + + /// + /// 根据RunTimeId查询一个实体 + /// + /// 实体的RunTimeId + /// 实体实例 + /// 要查询实体的泛型类型 + /// 返回一个bool值来提示是否查找到这个实体 + public virtual bool TryGetEntity(long runTimeId, out T entity) where T : Entity + { + if (_entities.TryGetValue(runTimeId, out var getEntity)) + { + entity = (T)getEntity; + return true; + } + + entity = null; + return false; + } + + /// + /// 删除一个实体,仅是删除不会指定实体的销毁方法 + /// + /// 实体的RunTimeId + /// 返回一个bool值来提示是否删除了这个实体 + public virtual bool RemoveEntity(long runTimeId) + { + return _entities.Remove(runTimeId); + } + + /// + /// 删除一个实体,仅是删除不会指定实体的销毁方法 + /// + /// 实体实例 + /// 返回一个bool值来提示是否删除了这个实体 + public virtual bool RemoveEntity(Entity entity) + { + return _entities.Remove(entity.RuntimeId); + } + + #endregion + + #region InnerSession + +#if FANTASY_NET + /// + /// 根据runTimeId获得Session + /// + /// + /// + /// + public virtual Session GetSession(long runTimeId) + { + var sceneId = IdFactoryHelper.RuntimeIdTool.GetSceneId(ref runTimeId); + + if (_processSessionInfos.TryGetValue(sceneId, out var processSessionInfo)) + { + if (!processSessionInfo.Session.IsDisposed) + { + return processSessionInfo.Session; + } + + _processSessionInfos.Remove(sceneId); + } + + if (Process.IsInAppliaction(ref sceneId)) + { + // 如果在同一个Process下,不需要通过Socket发送了,直接通过Process下转发。 + var processSession = Session.CreateInnerSession(Scene); + _processSessionInfos.Add(sceneId, new ProcessSessionInfo(processSession, null)); + return processSession; + } + + if (!SceneConfigData.Instance.TryGet(sceneId, out var sceneConfig)) + { + throw new Exception($"The scene with sceneId {sceneId} was not found in the configuration file"); + } + + if (!ProcessConfigData.Instance.TryGet(sceneConfig.ProcessConfigId, out var processConfig)) + { + throw new Exception($"The process with processId {sceneConfig.ProcessConfigId} was not found in the configuration file"); + } + + if (!MachineConfigData.Instance.TryGet(processConfig.MachineId, out var machineConfig)) + { + throw new Exception($"The machine with machineId {processConfig.MachineId} was not found in the configuration file"); + } + + var remoteAddress = $"{machineConfig.InnerBindIP}:{sceneConfig.InnerPort}"; + var client = NetworkProtocolFactory.CreateClient(Scene, ProcessDefine.InnerNetwork, NetworkTarget.Inner); + var session = client.Connect(remoteAddress, null, () => + { + Log.Error($"Unable to connect to the target server sourceServerId:{Scene.Process.Id} targetServerId:{sceneConfig.ProcessConfigId}"); + }, null, false); + _processSessionInfos.Add(sceneId, new ProcessSessionInfo(session, client)); + return session; + } +#endif + #endregion + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Scene/SceneRuntimeMode.cs b/Fantasy/Fantays.Console/Runtime/Core/Scene/SceneRuntimeMode.cs new file mode 100644 index 0000000..60dbc85 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Scene/SceneRuntimeMode.cs @@ -0,0 +1,21 @@ +namespace Fantasy +{ + /// + /// Scene的运行类型 + /// + public class SceneRuntimeMode + { + /// + /// Scene在主线程中运行. + /// + public const string MainThread = "MainThread"; + /// + /// Scene在一个独立的线程中运行. + /// + public const string MultiThread = "MultiThread"; + /// + /// Scene在一个根据当前CPU核心数创建的线程池中运行. + /// + public const string ThreadPool = "ThreadPool"; + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Scene/SceneRuntimeType.cs b/Fantasy/Fantays.Console/Runtime/Core/Scene/SceneRuntimeType.cs new file mode 100644 index 0000000..664107f --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Scene/SceneRuntimeType.cs @@ -0,0 +1,21 @@ +namespace Fantasy +{ + /// + /// 代表一个Scene的类型 + /// + public enum SceneRuntimeType + { + /// + /// 默认 + /// + None = 0, + /// + /// 代表一个普通的Scene,一个普通的Scene肯定是是Root的 + /// + Root = 1, + /// + /// 代表一个子场景,子场景肯定是有父场景的 + /// + SubScene = 2, + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Scene/Scheduler/ISceneScheduler.cs b/Fantasy/Fantays.Console/Runtime/Core/Scene/Scheduler/ISceneScheduler.cs new file mode 100644 index 0000000..c04cc30 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Scene/Scheduler/ISceneScheduler.cs @@ -0,0 +1,11 @@ +using System; + +namespace Fantasy +{ + internal interface ISceneScheduler : IDisposable + { + void Add(Scene scene); + void Remove(Scene scene); + void Update(); + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Scene/Scheduler/MainScheduler.cs b/Fantasy/Fantays.Console/Runtime/Core/Scene/Scheduler/MainScheduler.cs new file mode 100644 index 0000000..692def0 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Scene/Scheduler/MainScheduler.cs @@ -0,0 +1,83 @@ +using System.Collections.Generic; +#if FANTASY_UNITY || FANTASY_NET || !FANTASY_WEBGL +using System.Threading; +#endif +#if FANTASY_NET +using Fantasy.Platform.Net; +#endif +namespace Fantasy +{ + internal sealed class MainScheduler : ISceneScheduler + { + private readonly Queue _queue = new Queue(); + public readonly ThreadSynchronizationContext ThreadSynchronizationContext; + + public MainScheduler() + { + ThreadSynchronizationContext = new ThreadSynchronizationContext(); +#if !FANTASY_WEBGL + SynchronizationContext.SetSynchronizationContext(ThreadSynchronizationContext); +#endif + } + public void Dispose() + { + _queue.Clear(); + } + + public void Add(Scene scene) + { + ThreadSynchronizationContext.Post(() => + { + if (scene.IsDisposed) + { + return; + } + + _queue.Enqueue(scene); + }); + } + + public void Remove(Scene scene) + { + ThreadSynchronizationContext.Post(() => + { + if (scene.IsDisposed) + { + return; + } + + var initialCount = _queue.Count; + for (var i = 0; i < initialCount; i++) + { + var currentScene = _queue.Dequeue(); + if (currentScene != scene) + { + _queue.Enqueue(currentScene); + } + } + }); + } + + public void Update() + { + ThreadSynchronizationContext.Update(); + var initialCount = _queue.Count; + + while (initialCount-- > 0) + { + if(!_queue.TryDequeue(out var scene)) + { + continue; + } + + if (scene.IsDisposed) + { + continue; + } + + scene.Update(); + _queue.Enqueue(scene); + } + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Scene/Scheduler/MultiThreadScheduler.cs b/Fantasy/Fantays.Console/Runtime/Core/Scene/Scheduler/MultiThreadScheduler.cs new file mode 100644 index 0000000..28f2d42 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Scene/Scheduler/MultiThreadScheduler.cs @@ -0,0 +1,103 @@ +#if !FANTASY_WEBGL || !FANTASY_SINGLETHREAD +using System; +using System.Collections.Concurrent; +using System.Threading; +namespace Fantasy +{ + internal struct MultiThreadStruct : IDisposable + { + public readonly Thread Thread; + public readonly CancellationTokenSource Cts; + + public MultiThreadStruct(Thread thread, CancellationTokenSource cts) + { + Thread = thread; + Cts = cts; + } + + public void Dispose() + { + Cts.Cancel(); + if (Thread.IsAlive) + { + Thread.Join(); + } + Cts.Dispose(); + } + } + + internal sealed class MultiThreadScheduler : ISceneScheduler + { + private bool _isDisposed; + private readonly ConcurrentDictionary _threads = new ConcurrentDictionary(); + public int ThreadCount => _threads.Count; + + public void Dispose() + { + if (_isDisposed) + { + return; + } + + _isDisposed = true; + + foreach (var (_, multiThreadStruct) in _threads.ToArray()) + { + multiThreadStruct.Dispose(); + } + + _threads.Clear(); + } + + public void Add(Scene scene) + { + var cts = new CancellationTokenSource(); + var thread = new Thread(() => Loop(scene, cts.Token)); + _threads.TryAdd(scene.RuntimeId, new MultiThreadStruct(thread, cts)); + thread.Start(); + } + + public void Remove(Scene scene) + { + if (_threads.TryRemove(scene.RuntimeId, out var multiThreadStruct)) + { + multiThreadStruct.Dispose(); + } + } + + public void Update() + { + throw new NotImplementedException(); + } + + private void Loop(Scene scene, CancellationToken cancellationToken) + { + var sceneThreadSynchronizationContext = scene.ThreadSynchronizationContext; + SynchronizationContext.SetSynchronizationContext(sceneThreadSynchronizationContext); + + while (!cancellationToken.IsCancellationRequested) + { + try + { + if (scene.IsDisposed) + { + Remove(scene); + return; + } + + sceneThreadSynchronizationContext.Update(); + scene.Update(); + } + catch (Exception e) + { + Log.Error($"Error in MultiThreadScheduler loop: {e.Message}"); + } + finally + { + Thread.Sleep(1); + } + } + } + } +} +#endif diff --git a/Fantasy/Fantays.Console/Runtime/Core/Scene/Scheduler/ThreadPoolScheduler.cs b/Fantasy/Fantays.Console/Runtime/Core/Scene/Scheduler/ThreadPoolScheduler.cs new file mode 100644 index 0000000..9f5a44c --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Scene/Scheduler/ThreadPoolScheduler.cs @@ -0,0 +1,140 @@ +#if !FANTASY_WEBGL || !FANTASY_SINGLETHREAD +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Threading; +// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract +#pragma warning disable CS8604 // Possible null reference argument. +namespace Fantasy +{ + internal sealed class ThreadPoolScheduler : ISceneScheduler + { + private bool _isDisposed; + private readonly List _threads; + private readonly ConcurrentBag _queue = new ConcurrentBag(); + private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); + + public ThreadPoolScheduler() + { + // 最大线程数、避免线程过多发生的资源抢占问题。 + // 但如果使用了MultiThreadScheduler,那么这里的线程数就算是设置了也有可能导致线程过多的问题。 + // 线程过多看每个线程的抢占情况,如果抢占资源占用不是很大也没什么大问题。如果过大的情况,就会有性能问题。 + // 所以根据情况来使用不同的调度器。 + var maxThreadCount = Environment.ProcessorCount; + _threads = new List(maxThreadCount); + + for (var i = 0; i < maxThreadCount; ++i) + { + Thread thread = new(() => Loop(_cancellationTokenSource.Token)) + { + IsBackground = true + }; + _threads.Add(thread); + thread.Start(); + } + } + + public void Dispose() + { + if (_isDisposed) + { + return; + } + + _isDisposed = true; + _cancellationTokenSource.Cancel(); + + foreach (var thread in _threads) + { + if (thread.IsAlive) + { + thread.Join(); + } + } + + _cancellationTokenSource.Dispose(); + _threads.Clear(); + } + + public void Add(Scene scene) + { + if (_isDisposed) + { + return; + } + + _queue.Add(scene); + } + + public void Remove(Scene scene) + { + if (_isDisposed) + { + return; + } + + var newQueue = new Queue(); + + while (!_queue.IsEmpty) + { + if (_queue.TryTake(out var currentScene)) + { + if (currentScene != scene) + { + newQueue.Enqueue(currentScene); + } + } + } + + while (newQueue.TryDequeue(out var newScene)) + { + _queue.Add(newScene); + } + } + + public void Update() + { + throw new NotImplementedException(); + } + + private void Loop(CancellationToken cancellationToken) + { + while (!cancellationToken.IsCancellationRequested) + { + if (_queue.TryTake(out var scene)) + { + if (scene == null || scene.IsDisposed) + { + continue; + } + + var sceneThreadSynchronizationContext = scene.ThreadSynchronizationContext; + SynchronizationContext.SetSynchronizationContext(sceneThreadSynchronizationContext); + + try + { + sceneThreadSynchronizationContext.Update(); + scene.Update(); + } + catch (Exception e) + { + Log.Error($"Error in ThreadPoolScheduler scene: {e.Message}"); + } + finally + { + SynchronizationContext.SetSynchronizationContext(null); + } + + _queue.Add(scene); + Thread.Sleep(1); + } + else + { + // 当队列为空的时候、避免无效循环消耗CPU。 + Thread.Sleep(10); + } + } + } + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Scene/Scheduler/ThreadScheduler.cs b/Fantasy/Fantays.Console/Runtime/Core/Scene/Scheduler/ThreadScheduler.cs new file mode 100644 index 0000000..5bd0dea --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Scene/Scheduler/ThreadScheduler.cs @@ -0,0 +1,66 @@ +// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. +namespace Fantasy +{ + /// + /// 线程调度器 + /// + internal static class ThreadScheduler + { + /// + /// 主线程调度器 + /// + public static MainScheduler MainScheduler { get; private set; } + /// + /// 多线程调度器,根据当前CPU核心数量创建的固定线程。 + /// + public static ISceneScheduler MultiThreadScheduler { get; private set; } + /// + /// 线程池调度器 + /// + public static ISceneScheduler ThreadPoolScheduler { get; private set; } + + static ThreadScheduler() + { + MainScheduler = new MainScheduler(); + } + + internal static void Update() + { + MainScheduler.Update(); + } + + internal static void AddMainScheduler(Scene scene) + { + MainScheduler.Add(scene); + } + + internal static void AddToMultiThreadScheduler(Scene scene) + { + if (MultiThreadScheduler == null) + { +#if FANTASY_SINGLETHREAD || FANTASY_WEBGL + MultiThreadScheduler = MainScheduler; +#else + MultiThreadScheduler = new MultiThreadScheduler(); +#endif + } + + MultiThreadScheduler.Add(scene); + } + + internal static void AddToThreadPoolScheduler(Scene scene) + { + if (ThreadPoolScheduler == null) + { +#if FANTASY_SINGLETHREAD || FANTASY_WEBGL + ThreadPoolScheduler = MainScheduler; +#else + ThreadPoolScheduler = new ThreadPoolScheduler(); +#endif + } + + ThreadPoolScheduler.Add(scene); + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Scene/SubScene.cs b/Fantasy/Fantays.Console/Runtime/Core/Scene/SubScene.cs new file mode 100644 index 0000000..438b8ba --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Scene/SubScene.cs @@ -0,0 +1,171 @@ +using System.Collections.Generic; +using System.Linq; +using Fantasy.Entitas; +using Fantasy.Network; +#pragma warning disable CS8601 // Possible null reference assignment. +#pragma warning disable CS8603 // Possible null reference return. +#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. + +namespace Fantasy +{ + /// + /// 代表一个Scene下的子Scene + /// + public sealed partial class SubScene : Scene + { + /// + /// 子Scene的根Scene + /// + public Scene RootScene { get; internal set; } + /// + /// 存储当前Scene下管理的实体。 + /// + private readonly Dictionary _entities = new Dictionary(); + + internal void Initialize(Scene rootScene) + { + EntityPool = rootScene.EntityPool; + EntityListPool = rootScene.EntityListPool; + EntitySortedDictionaryPool = rootScene.EntitySortedDictionaryPool; + SceneUpdate = rootScene.SceneUpdate; + TimerComponent = rootScene.TimerComponent; + EventComponent = rootScene.EventComponent; + EntityComponent = rootScene.EntityComponent; + MessagePoolComponent = rootScene.MessagePoolComponent; + CoroutineLockComponent = rootScene.CoroutineLockComponent; + MessageDispatcherComponent = rootScene.MessageDispatcherComponent; + #if FANTASY_NET + NetworkMessagingComponent = rootScene.NetworkMessagingComponent; + SingleCollectionComponent = rootScene.SingleCollectionComponent; + TerminusComponent = rootScene.TerminusComponent; + #endif + ThreadSynchronizationContext = rootScene.ThreadSynchronizationContext; + } + + /// + /// 当子Scene销毁时执行 + /// + public override void Dispose() + { + if (IsDisposed) + { + return; + } + + ThreadSynchronizationContext.Post(() => + { + if (IsDisposed) + { + return; + } + + foreach (var (runtimeId, entity) in _entities.ToList()) + { + if (runtimeId != entity.RuntimeId) + { + continue; + } + entity.Dispose(); + } + + _entities.Clear(); + base.Dispose(); + }); + } + + /// + /// 添加一个实体到当前Scene下 + /// + /// 实体实例 + public override void AddEntity(Entity entity) + { + _entities.Add(entity.RuntimeId, entity); + RootScene.AddEntity(entity); + } + + /// + /// 根据RunTimeId查询一个实体 + /// + /// 实体的RunTimeId + /// 返回的实体 + public override Entity GetEntity(long runTimeId) + { + return _entities.GetValueOrDefault(runTimeId); + } + + /// + /// 根据RunTimeId查询一个实体 + /// + /// 实体的RunTimeId + /// 实体实例 + /// 返回一个bool值来提示是否查找到这个实体 + public override bool TryGetEntity(long runTimeId, out Entity entity) + { + return _entities.TryGetValue(runTimeId, out entity); + } + + /// + /// 根据RunTimeId查询一个实体 + /// + /// 实体的RunTimeId + /// 要查询实体的泛型类型 + /// 返回的实体 + public override T GetEntity(long runTimeId) + { + return _entities.TryGetValue(runTimeId, out var entity) ? (T)entity : null; + } + + /// + /// 根据RunTimeId查询一个实体 + /// + /// 实体的RunTimeId + /// 实体实例 + /// 要查询实体的泛型类型 + /// 返回一个bool值来提示是否查找到这个实体 + public override bool TryGetEntity(long runTimeId, out T entity) + { + if (_entities.TryGetValue(runTimeId, out var getEntity)) + { + entity = (T)getEntity; + return true; + } + + entity = null; + return false; + } + + /// + /// 删除一个实体,仅是删除不会指定实体的销毁方法 + /// + /// 实体的RunTimeId + /// 返回一个bool值来提示是否删除了这个实体 + public override bool RemoveEntity(long runTimeId) + { + return _entities.Remove(runTimeId) && RootScene.RemoveEntity(runTimeId); + } + + /// + /// 删除一个实体,仅是删除不会指定实体的销毁方法 + /// + /// 实体实例 + /// 返回一个bool值来提示是否删除了这个实体 + public override bool RemoveEntity(Entity entity) + { + return RemoveEntity(entity.RuntimeId); + } + +#if FANTASY_NET + /// + /// 根据runTimeId获得Session + /// + /// + /// + /// + public override Session GetSession(long runTimeId) + { + return RootScene.GetSession(runTimeId); + } + #endif + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Serialize/BsonPack/BsonPackHelperNet.cs b/Fantasy/Fantays.Console/Runtime/Core/Serialize/BsonPack/BsonPackHelperNet.cs new file mode 100644 index 0000000..bb5293b --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Serialize/BsonPack/BsonPackHelperNet.cs @@ -0,0 +1,311 @@ +#if FANTASY_NET +using System.Buffers; +using System.Collections; +using System.ComponentModel; +using System.Reflection; +using Fantasy.Assembly; +using Fantasy.Entitas; +using MongoDB.Bson; +using MongoDB.Bson.IO; +using MongoDB.Bson.Serialization; +using MongoDB.Bson.Serialization.Conventions; +using MongoDB.Bson.Serialization.Serializers; +#pragma warning disable CS8603 // Possible null reference return. +#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type. +#pragma warning disable CS8602 // Dereference of a possibly null reference. + +namespace Fantasy.Serialize +{ + /// + /// BSON帮助方法 + /// + public class BsonPackHelper : ISerialize + { + /// + /// 序列化器的名字 + /// + public string SerializeName { get; } = "Bson"; + + /// + /// 构造函数 + /// + public BsonPackHelper() + { + // 清除掉注册过的LookupClassMap。 + + var classMapRegistryField = typeof(BsonClassMap).GetField("__classMaps", BindingFlags.Static | BindingFlags.NonPublic); + + if (classMapRegistryField != null) + { + ((Dictionary)classMapRegistryField.GetValue(null)).Clear(); + } + + // 清除掉注册过的ConventionRegistry。 + + var registryField = typeof(ConventionRegistry).GetField("_lookup", BindingFlags.Static | BindingFlags.NonPublic); + + if (registryField != null) + { + var registry = registryField.GetValue(null); + var dictionaryField = registry.GetType().GetField("_conventions", BindingFlags.Instance | BindingFlags.NonPublic); + if (dictionaryField != null) + { + ((IDictionary)dictionaryField.GetValue(registry)).Clear(); + } + } + + // 初始化ConventionRegistry、注册IgnoreExtraElements。 + + ConventionRegistry.Register("IgnoreExtraElements", new ConventionPack { new IgnoreExtraElementsConvention(true) }, type => true); + + // 注册一个自定义的序列化器。 + + // BsonSerializer.TryRegisterSerializer(typeof(float2), new StructBsonSerialize()); + // BsonSerializer.TryRegisterSerializer(typeof(float3), new StructBsonSerialize()); + // BsonSerializer.TryRegisterSerializer(typeof(float4), new StructBsonSerialize()); + // BsonSerializer.TryRegisterSerializer(typeof(quaternion), new StructBsonSerialize()); + BsonSerializer.RegisterSerializer(new ObjectSerializer(x => true)); + + // 注册LookupClassMap。 + + foreach (var type in AssemblySystem.ForEach()) + { + if (type.IsInterface || type.IsAbstract || type.IsGenericType || !typeof(Entity).IsAssignableFrom(type)) + { + continue; + } + + BsonClassMap.LookupClassMap(type); + } + } + + /// + /// 反序列化 + /// + /// + /// + /// + public T Deserialize(byte[] bytes) + { + var @object = BsonSerializer.Deserialize(bytes); + if (@object is ASerialize aSerialize) + { + aSerialize.AfterDeserialization(); + } + return @object; + } + + /// + /// 反序列化 + /// + /// + /// + /// + public T Deserialize(MemoryStreamBuffer buffer) + { + var @object = BsonSerializer.Deserialize(buffer); + if (@object is ASerialize aSerialize) + { + aSerialize.AfterDeserialization(); + } + return @object; + } + + /// + /// 反序列化 + /// + /// + /// + /// + public object Deserialize(Type type, byte[] bytes) + { + var @object = BsonSerializer.Deserialize(bytes, type); + if (@object is ASerialize aSerialize) + { + aSerialize.AfterDeserialization(); + } + return @object; + } + + /// + /// 反序列化 + /// + /// + /// + /// + public object Deserialize(Type type, MemoryStreamBuffer buffer) + { + var @object = BsonSerializer.Deserialize(buffer, type); + if (@object is ASerialize aSerialize) + { + aSerialize.AfterDeserialization(); + } + return @object; + } + + /// + /// 反序列化 + /// + /// + /// + /// + /// + /// + public unsafe T Deserialize(byte[] bytes, int index, int count) + { + T @object; + + fixed (byte* ptr = &bytes[index]) + { + using var stream = new UnmanagedMemoryStream(ptr, count); + @object = BsonSerializer.Deserialize(stream); + } + + if (@object is ASerialize aSerialize) + { + aSerialize.AfterDeserialization(); + } + + return @object; + } + + /// + /// 反序列化 + /// + /// + /// + /// + /// + /// + public unsafe object Deserialize(Type type, byte[] bytes, int index, int count) + { + object @object; + + fixed (byte* ptr = &bytes[index]) + { + using var stream = new UnmanagedMemoryStream(ptr, count); + @object = BsonSerializer.Deserialize(stream, type); + } + + if (@object is ASerialize aSerialize) + { + aSerialize.AfterDeserialization(); + } + + return @object; + } + + /// + /// 序列化 + /// + /// + /// + /// + public void Serialize(T @object, IBufferWriter buffer) + { + if (@object is ASerialize aSerialize) + { + aSerialize.BeginInit(); + } + + using IBsonWriter bsonWriter = + new BsonBinaryWriter((MemoryStream)buffer, BsonBinaryWriterSettings.Defaults); + BsonSerializer.Serialize(bsonWriter, @object); + } + + /// + /// 序列化 + /// + /// + /// + public void Serialize(object @object, IBufferWriter buffer) + { + if (@object is ASerialize aSerialize) + { + aSerialize.BeginInit(); + } + + using IBsonWriter bsonWriter = + new BsonBinaryWriter((MemoryStream)buffer, BsonBinaryWriterSettings.Defaults); + BsonSerializer.Serialize(bsonWriter, @object.GetType(), @object); + } + + /// + /// 序列化 + /// + /// + /// + /// + public void Serialize(Type type, object @object, IBufferWriter buffer) + { + if (@object is ASerialize aSerialize) + { + aSerialize.BeginInit(); + } + + using IBsonWriter bsonWriter = + new BsonBinaryWriter((MemoryStream)buffer, BsonBinaryWriterSettings.Defaults); + BsonSerializer.Serialize(bsonWriter, type, @object); + } + + /// + /// 序列化并返回的长度 + /// + /// + /// + /// + /// + public int SerializeAndReturnLength(Type type, object @object, MemoryStreamBuffer buffer) + { + if (@object is ASerialize aSerialize) + { + aSerialize.BeginInit(); + } + + using IBsonWriter bsonWriter = new BsonBinaryWriter(buffer, BsonBinaryWriterSettings.Defaults); + BsonSerializer.Serialize(bsonWriter, type, @object); + return (int)buffer.Length; + } + + /// + /// 序列化 + /// + /// + /// + public static byte[] Serialize(object @object) + { + if (@object is ASerialize aSerialize) + { + aSerialize.BeginInit(); + } + return @object.ToBson(@object.GetType()); + } + + /// + /// 序列化 + /// + /// + /// + /// + public static byte[] Serialize(T @object) + { + if (@object is ASerialize aSerialize) + { + aSerialize.BeginInit(); + } + return @object.ToBson(); + } + + /// + /// 克隆 + /// + /// + /// + /// + public T Clone(T t) + { + return Deserialize(Serialize(t)); + } + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Serialize/BsonPack/BsonPackHelperUnity.cs b/Fantasy/Fantays.Console/Runtime/Core/Serialize/BsonPack/BsonPackHelperUnity.cs new file mode 100644 index 0000000..df44fcb --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Serialize/BsonPack/BsonPackHelperUnity.cs @@ -0,0 +1,60 @@ +#if FANTASY_UNITY +using System; +using System.Buffers; +namespace Fantasy.Serialize +{ + public class BsonPackHelper : ISerialize + { + public string SerializeName { get; } = "Bson"; + public T Deserialize(byte[] bytes) + { + throw new NotImplementedException(); + } + + public T Deserialize(MemoryStreamBuffer buffer) + { + throw new NotImplementedException(); + } + + public object Deserialize(Type type, byte[] bytes) + { + throw new NotImplementedException(); + } + + public object Deserialize(Type type, MemoryStreamBuffer buffer) + { + throw new NotImplementedException(); + } + + public T Deserialize(byte[] bytes, int index, int count) + { + throw new NotImplementedException(); + } + + public object Deserialize(Type type, byte[] bytes, int index, int count) + { + throw new NotImplementedException(); + } + + public void Serialize(T @object, IBufferWriter buffer) + { + throw new NotImplementedException(); + } + + public void Serialize(object @object, IBufferWriter buffer) + { + throw new NotImplementedException(); + } + + public void Serialize(Type type, object @object, IBufferWriter buffer) + { + throw new NotImplementedException(); + } + + public T Clone(T t) + { + throw new NotImplementedException(); + } + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Serialize/BsonPack/StructBsonSerialize.cs b/Fantasy/Fantays.Console/Runtime/Core/Serialize/BsonPack/StructBsonSerialize.cs new file mode 100644 index 0000000..15d8663 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Serialize/BsonPack/StructBsonSerialize.cs @@ -0,0 +1,65 @@ +#if FANTASY_NET +using System.Reflection; +using MongoDB.Bson; +using MongoDB.Bson.IO; +using MongoDB.Bson.Serialization; +using MongoDB.Bson.Serialization.Serializers; + +namespace Fantasy.Serialize; + +/// +/// 提供对结构体类型进行 BSON 序列化和反序列化的辅助类。 +/// +/// 要序列化和反序列化的结构体类型。 +public class StructBsonSerialize : StructSerializerBase where TValue : struct +{ + /// + /// 将结构体对象序列化为 BSON 数据。 + /// + /// 序列化上下文。 + /// 序列化参数。 + /// 要序列化的结构体对象。 + public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, TValue value) + { + var nominalType = args.NominalType; + var bsonWriter = context.Writer; + bsonWriter.WriteStartDocument(); + var fields = nominalType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + foreach (var field in fields) + { + bsonWriter.WriteName(field.Name); + BsonSerializer.Serialize(bsonWriter, field.FieldType, field.GetValue(value)); + } + bsonWriter.WriteEndDocument(); + } + + /// + /// 将 BSON 数据反序列化为结构体对象。 + /// + /// 反序列化上下文。 + /// 反序列化参数。 + /// 反序列化得到的结构体对象。 + public override TValue Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args) + { + //boxing is required for SetValue to work + object obj = new TValue(); + var actualType = args.NominalType; + var bsonReader = context.Reader; + bsonReader.ReadStartDocument(); + while (bsonReader.ReadBsonType() != BsonType.EndOfDocument) + { + var name = bsonReader.ReadName(Utf8NameDecoder.Instance); + + var field = actualType.GetField(name, + BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + if (field != null) + { + var value = BsonSerializer.Deserialize(bsonReader, field.FieldType); + field.SetValue(obj, value); + } + } + bsonReader.ReadEndDocument(); + return (TValue) obj; + } +} +#endif diff --git a/Fantasy/Fantays.Console/Runtime/Core/Serialize/BsonPack/SupportInitializeChecker.cs b/Fantasy/Fantays.Console/Runtime/Core/Serialize/BsonPack/SupportInitializeChecker.cs new file mode 100644 index 0000000..82c6829 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Serialize/BsonPack/SupportInitializeChecker.cs @@ -0,0 +1,17 @@ +#if FANTASY_NET +using System.ComponentModel; +using Fantasy.Entitas; + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +namespace Fantasy.Serialize; + +public static class SupportInitializeChecker where T : Entity +{ + public static bool IsSupported { get; } + + static SupportInitializeChecker() + { + IsSupported = typeof(ISupportInitialize).IsAssignableFrom(typeof(T)); + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Serialize/Interface/ASerialize.cs b/Fantasy/Fantays.Console/Runtime/Core/Serialize/Interface/ASerialize.cs new file mode 100644 index 0000000..7487b86 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Serialize/Interface/ASerialize.cs @@ -0,0 +1,60 @@ +using System; +using System.ComponentModel; +using System.Runtime.Serialization; +using Fantasy.Pool; +#if FANTASY_NET || FANTASY_UNITY || FANTASY_CONSOLE +using MongoDB.Bson.Serialization.Attributes; +#endif +using Newtonsoft.Json; +using ProtoBuf; +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. + +namespace Fantasy.Serialize +{ + public abstract class ASerialize : ISupportInitialize, IDisposable + { + public virtual void Dispose() { } + public virtual void BeginInit() { } + public virtual void EndInit() { } + public virtual void AfterDeserialization() => EndInit(); + } + + public abstract class AMessage : ASerialize, IPool + { +#if FANTASY_NET || FANTASY_UNITY || FANTASY_CONSOLE + [BsonIgnore] + [JsonIgnore] + [IgnoreDataMember] + [ProtoIgnore] + private Scene _scene; + protected Scene GetScene() + { + return _scene; + } + + public void SetScene(Scene scene) + { + _scene = scene; + } +#endif +#if FANTASY_NET + [BsonIgnore] +#endif + [JsonIgnore] + [IgnoreDataMember] + [ProtoIgnore] + private bool _isPool; + + public bool IsPool() + { + return _isPool; + } + + public void SetIsPool(bool isPool) + { + _isPool = isPool; + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Serialize/Interface/ISerialize.cs b/Fantasy/Fantays.Console/Runtime/Core/Serialize/Interface/ISerialize.cs new file mode 100644 index 0000000..a3a9159 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Serialize/Interface/ISerialize.cs @@ -0,0 +1,87 @@ +using System; +using System.Buffers; + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +namespace Fantasy.Serialize +{ + public interface ISerialize + { + /// + /// 序列化器的名字,用于在协议里指定用什么协议序列化使用 + /// + string SerializeName { get; } + /// + /// 反序列化 + /// + /// + /// + /// + T Deserialize(byte[] bytes); + /// + /// 反序列化 + /// + /// + /// + /// + T Deserialize(MemoryStreamBuffer buffer); + /// + /// 反序列化 + /// + /// + /// + /// + object Deserialize(Type type, byte[] bytes); + /// + /// 反序列化 + /// + /// + /// + /// + object Deserialize(Type type, MemoryStreamBuffer buffer); + /// + /// 反序列化 + /// + /// + /// + /// + /// + /// + T Deserialize(byte[] bytes, int index, int count); + /// + /// 反序列化 + /// + /// + /// + /// + /// + /// + object Deserialize(Type type, byte[] bytes, int index, int count); + /// + /// 序列化 + /// + /// + /// + /// + void Serialize(T @object, IBufferWriter buffer); + /// + /// 序列化 + /// + /// + /// + void Serialize(object @object, IBufferWriter buffer); + /// + /// 序列化 + /// + /// + /// + /// + void Serialize(Type type, object @object, IBufferWriter buffer); + /// + /// 克隆 + /// + /// + /// + /// + T Clone(T t); + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Serialize/MemoryStreamBuffer.cs b/Fantasy/Fantays.Console/Runtime/Core/Serialize/MemoryStreamBuffer.cs new file mode 100644 index 0000000..23919b4 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Serialize/MemoryStreamBuffer.cs @@ -0,0 +1,73 @@ +using System; +using System.Buffers; +using System.IO; +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + +namespace Fantasy.Serialize +{ + public enum MemoryStreamBufferSource + { + None = 0, + Pack = 1, + UnPack = 2, + } + + public sealed class MemoryStreamBuffer : MemoryStream, IBufferWriter + { + public MemoryStreamBufferSource MemoryStreamBufferSource; + public MemoryStreamBuffer() { } + + public MemoryStreamBuffer(MemoryStreamBufferSource memoryStreamBufferSource, int capacity) : base(capacity) + { + MemoryStreamBufferSource = memoryStreamBufferSource; + } + public MemoryStreamBuffer(byte[] buffer): base(buffer) { } + + public void Advance(int count) + { + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count), count, "The value of 'count' cannot be negative."); + } + + var newLength = Position + count; + + if (newLength != Length) + { + SetLength(newLength); + } + + Position = newLength; + } + + public Memory GetMemory(int sizeHint = 0) + { + if (sizeHint < 0) + { + throw new ArgumentOutOfRangeException(nameof(sizeHint), sizeHint, "The value of 'count' cannot be negative."); + } + + if (Length - Position <= sizeHint) + { + SetLength(Position + sizeHint); + } + + return new Memory(GetBuffer(), (int)Position, (int)(Length - Position)); + } + + public Span GetSpan(int sizeHint = 0) + { + if (sizeHint < 0) + { + throw new ArgumentOutOfRangeException(nameof(sizeHint), sizeHint, "The value of 'count' cannot be negative."); + } + + if (Length - Position <= sizeHint) + { + SetLength(Position + sizeHint); + } + + return new Span(GetBuffer(), (int)Position, (int)(Length - Position)); + } + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Serialize/ProtoBufPackHelper/IProto.cs b/Fantasy/Fantays.Console/Runtime/Core/Serialize/ProtoBufPackHelper/IProto.cs new file mode 100644 index 0000000..2267dd2 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Serialize/ProtoBufPackHelper/IProto.cs @@ -0,0 +1,9 @@ +namespace Fantasy.Serialize +{ + /// + /// 代表是一个ProtoBuf协议 + /// + public interface IProto + { + } +} \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Serialize/ProtoBufPackHelper/ProtoBufPackHelperNet.cs b/Fantasy/Fantays.Console/Runtime/Core/Serialize/ProtoBufPackHelper/ProtoBufPackHelperNet.cs new file mode 100644 index 0000000..dc3886b --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Serialize/ProtoBufPackHelper/ProtoBufPackHelperNet.cs @@ -0,0 +1,223 @@ +#if FANTASY_NET || FANTASY_EXPORTER +using System.Buffers; +using Fantasy.Assembly; +using ProtoBuf.Meta; +namespace Fantasy.Serialize +{ + /// + /// ProtoBufP帮助类,Net平台使用 + /// + public sealed class ProtoBufPackHelper : ISerialize + { + /// + /// 序列化器的名字 + /// + public string SerializeName { get; } = "ProtoBuf"; + + /// + /// 构造函数 + /// + public ProtoBufPackHelper () + { +#if FANTASY_NET + RuntimeTypeModel.Default.AutoAddMissingTypes = true; + RuntimeTypeModel.Default.AllowParseableTypes = true; + RuntimeTypeModel.Default.AutoAddMissingTypes = true; + RuntimeTypeModel.Default.AutoCompile = true; + RuntimeTypeModel.Default.UseImplicitZeroDefaults = true; + RuntimeTypeModel.Default.InferTagFromNameDefault = true; + + foreach (var type in AssemblySystem.ForEach(typeof(IProto))) + { + RuntimeTypeModel.Default.Add(type, true); + } + + RuntimeTypeModel.Default.CompileInPlace(); +#endif + } + + /// + /// 使用ProtoBuf反序列化数据到实例 + /// + /// + /// + /// + public T Deserialize(byte[] bytes) + { + var memory = new ReadOnlyMemory(bytes); + var @object = RuntimeTypeModel.Default.Deserialize(memory); + if (@object is ASerialize aSerialize) + { + aSerialize.AfterDeserialization(); + } + return @object; + } + /// + /// 使用ProtoBuf反序列化数据到实例 + /// + /// + /// + /// + public T Deserialize(MemoryStreamBuffer buffer) + { + var @object = RuntimeTypeModel.Default.Deserialize(buffer); + if (@object is ASerialize aSerialize) + { + aSerialize.AfterDeserialization(); + } + + return @object; + } + /// + /// 使用ProtoBuf反序列化数据到实例 + /// + /// + /// + /// + public object Deserialize(Type type, byte[] bytes) + { + var memory = new ReadOnlyMemory(bytes); + var @object = RuntimeTypeModel.Default.Deserialize(type, memory); + if (@object is ASerialize aSerialize) + { + aSerialize.AfterDeserialization(); + } + return @object; + } + /// + /// 使用ProtoBuf反序列化数据到实例 + /// + /// + /// + /// + public object Deserialize(Type type, MemoryStreamBuffer buffer) + { + var @object = RuntimeTypeModel.Default.Deserialize(type, buffer); + if (@object is ASerialize aSerialize) + { + aSerialize.AfterDeserialization(); + } + + return @object; + } + /// + /// 使用ProtoBuf反序列化数据到实例 + /// + /// + /// + /// + /// + /// + public T Deserialize(byte[] bytes, int index, int count) + { + var memory = new ReadOnlyMemory(bytes, index, count); + var @object = RuntimeTypeModel.Default.Deserialize(memory); + if (@object is ASerialize aSerialize) + { + aSerialize.AfterDeserialization(); + } + + return @object; + } + /// + /// 使用ProtoBuf反序列化数据到实例 + /// + /// + /// + /// + /// + /// + public object Deserialize(Type type, byte[] bytes, int index, int count) + { + var memory = new ReadOnlyMemory(bytes, index, count); + var @object = RuntimeTypeModel.Default.Deserialize(type, memory); + if (@object is ASerialize aSerialize) + { + aSerialize.AfterDeserialization(); + } + + return @object; + } + /// + /// 使用ProtoBuf序列化某一个实例到IBufferWriter中 + /// + /// + /// + /// + public void Serialize(T @object, IBufferWriter buffer) + { + if (@object is ASerialize aSerialize) + { + aSerialize.BeginInit(); + } + + RuntimeTypeModel.Default.Serialize(buffer, @object); + } + /// + /// 使用ProtoBuf序列化某一个实例到IBufferWriter中 + /// + /// + /// + public void Serialize(object @object, IBufferWriter buffer) + { + if (@object is ASerialize aSerialize) + { + aSerialize.BeginInit(); + } + + RuntimeTypeModel.Default.Serialize(buffer, @object); + } + /// + /// 使用ProtoBuf序列化某一个实例到IBufferWriter中 + /// + /// + /// + /// + public void Serialize(Type type, object @object, IBufferWriter buffer) + { + if (@object is ASerialize aSerialize) + { + aSerialize.BeginInit(); + } + + RuntimeTypeModel.Default.Serialize(buffer, @object); + } + internal byte[] Serialize(object @object) + { + if (@object is ASerialize aSerialize) + { + aSerialize.BeginInit(); + } + + using (var buffer = new MemoryStream()) + { + RuntimeTypeModel.Default.Serialize(buffer, @object); + return buffer.ToArray(); + } + } + private byte[] Serialize(T @object) + { + if (@object is ASerialize aSerialize) + { + aSerialize.BeginInit(); + } + + using (var buffer = new MemoryStream()) + { + RuntimeTypeModel.Default.Serialize(buffer, @object); + return buffer.ToArray(); + } + } + /// + /// 克隆 + /// + /// + /// + /// + public T Clone(T t) + { + return Deserialize(Serialize(t)); + } + } +} +#endif diff --git a/Fantasy/Fantays.Console/Runtime/Core/Serialize/ProtoBufPackHelper/ProtoBufPackHelperUnity.cs b/Fantasy/Fantays.Console/Runtime/Core/Serialize/ProtoBufPackHelper/ProtoBufPackHelperUnity.cs new file mode 100644 index 0000000..6df5903 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Serialize/ProtoBufPackHelper/ProtoBufPackHelperUnity.cs @@ -0,0 +1,202 @@ +#if FANTASY_UNITY || FANTASY_CONSOLE +using System; +using System.Buffers; +using System.IO; +using Fantasy.Assembly; +using ProtoBuf; +using ProtoBuf.Meta; + +namespace Fantasy.Serialize +{ + /// + /// ProtoBufP帮助类,Unity平台使用 + /// + public sealed class ProtoBufPackHelper : ISerialize + { + /// + /// 序列化器的名字 + /// + public string SerializeName { get; } = "ProtoBuf"; + + /// + /// 使用ProtoBuf反序列化数据到实例 + /// + /// + /// + /// + public unsafe T Deserialize(byte[] bytes) + { + fixed (byte* ptr = bytes) + { + using var stream = new UnmanagedMemoryStream(ptr, bytes.Length); + var @object = ProtoBuf.Serializer.Deserialize(stream); + if (@object is ASerialize aSerialize) + { + aSerialize.AfterDeserialization(); + } + return @object; + } + } + /// + /// 使用ProtoBuf反序列化数据到实例 + /// + /// + /// + /// + public T Deserialize(MemoryStreamBuffer buffer) + { + var @object = ProtoBuf.Serializer.Deserialize(buffer); + if (@object is ASerialize aSerialize) + { + aSerialize.AfterDeserialization(); + } + return @object; + } + /// + /// 使用ProtoBuf反序列化数据到实例 + /// + /// + /// + /// + public unsafe object Deserialize(Type type, byte[] bytes) + { + fixed (byte* ptr = bytes) + { + using var stream = new UnmanagedMemoryStream(ptr, bytes.Length); + var @object = ProtoBuf.Serializer.Deserialize(type, stream); + if (@object is ASerialize aSerialize) + { + aSerialize.AfterDeserialization(); + } + + return @object; + } + } + /// + /// 使用ProtoBuf反序列化数据到实例 + /// + /// + /// + /// + public object Deserialize(Type type, MemoryStreamBuffer buffer) + { + var @object = ProtoBuf.Serializer.Deserialize(type, buffer); + if (@object is ASerialize aSerialize) + { + aSerialize.AfterDeserialization(); + } + + return @object; + } + /// + /// 使用ProtoBuf反序列化数据到实例 + /// + /// + /// + /// + /// + /// + public unsafe T Deserialize(byte[] bytes, int index, int count) + { + fixed (byte* ptr = &bytes[index]) + { + using var stream = new UnmanagedMemoryStream(ptr, count); + var @object = ProtoBuf.Serializer.Deserialize(stream); + if (@object is ASerialize aSerialize) + { + aSerialize.AfterDeserialization(); + } + return @object; + } + } + /// + /// 使用ProtoBuf反序列化数据到实例 + /// + /// + /// + /// + /// + /// + public unsafe object Deserialize(Type type, byte[] bytes, int index, int count) + { + fixed (byte* ptr = &bytes[index]) + { + using var stream = new UnmanagedMemoryStream(ptr, count); + var @object = ProtoBuf.Serializer.Deserialize(type, stream); + if (@object is ASerialize aSerialize) + { + aSerialize.AfterDeserialization(); + } + return @object; + } + } + /// + /// 使用ProtoBuf序列化某一个实例到IBufferWriter中 + /// + /// + /// + /// + public void Serialize(T @object, IBufferWriter buffer) + { + if (@object is ASerialize aSerialize) + { + aSerialize.BeginInit(); + } + + RuntimeTypeModel.Default.Serialize((MemoryStream)buffer, @object); + } + /// + /// 使用ProtoBuf序列化某一个实例到IBufferWriter中 + /// + /// + /// + public void Serialize(object @object, IBufferWriter buffer) + { + if (@object is ASerialize aSerialize) + { + aSerialize.BeginInit(); + } + + RuntimeTypeModel.Default.Serialize((MemoryStream)buffer, @object); + } + /// + /// 使用ProtoBuf序列化某一个实例到IBufferWriter中 + /// + /// + /// + /// + public void Serialize(Type type, object @object, IBufferWriter buffer) + { + if (@object is ASerialize aSerialize) + { + aSerialize.BeginInit(); + } + + RuntimeTypeModel.Default.Serialize((MemoryStream)buffer, @object); + } + private byte[] Serialize(T @object) + { + if (@object is ASerialize aSerialize) + { + aSerialize.BeginInit(); + } + + using (var buffer = new MemoryStream()) + { + RuntimeTypeModel.Default.Serialize(buffer, @object); + return buffer.ToArray(); + } + } + /// + /// 克隆 + /// + /// + /// + /// + public T Clone(T t) + { + return Deserialize(Serialize(t)); + } + } +} +#endif \ No newline at end of file diff --git a/Fantasy/Fantays.Console/Runtime/Core/Serialize/SerializerManager.cs b/Fantasy/Fantays.Console/Runtime/Core/Serialize/SerializerManager.cs new file mode 100644 index 0000000..2272074 --- /dev/null +++ b/Fantasy/Fantays.Console/Runtime/Core/Serialize/SerializerManager.cs @@ -0,0 +1,152 @@ +using System; +using System.Collections.Generic; +using Fantasy.Assembly; +using Fantasy.Helper; +#if !FANTASY_EXPORTER +using Fantasy.Network; +#endif +#pragma warning disable CS8604 // Possible null reference argument. +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. +#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. +#pragma warning disable CS8602 // Dereference of a possibly null reference. +#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type. + +namespace Fantasy.Serialize +{ + /// + /// 框架内置的序列化器类型 + /// + public static class FantasySerializerType + { + /// + /// ProtoBuf在SerializerManager的数组下标 + /// + public const int ProtoBuf = 0; + /// + /// Bson在SerializerManager的数组下标 + /// + public const int Bson = 1; + } + + /// + /// 管理序列化静态方法,主要是优化网络协议时使用。 + /// + public static class SerializerManager + { + private static ISerialize[] _serializers; + private static bool _isInitialized = false; + +#if FANTASY_NET || FANTASY_UNITY + /// + /// 初始化方法 + /// + public static void Initialize() + { + if (_isInitialized) + { + return; + } + + try + { + var sort = new SortedList(); + + foreach (var serializerType in AssemblySystem.ForEach(typeof(ISerialize))) + { + var serializer = (ISerialize)Activator.CreateInstance(serializerType); + var computeHash64 = HashCodeHelper.ComputeHash64(serializer.SerializeName); + sort.Add(computeHash64, serializer); + } + + var index = 1; + _serializers = new ISerialize[sort.Count]; + + foreach (var (_, serialize) in sort) + { + var serializerIndex = 0; + + switch (serialize) + { + case ProtoBufPackHelper: + { + serializerIndex = FantasySerializerType.ProtoBuf; + break; + } + case BsonPackHelper: + { + serializerIndex = FantasySerializerType.Bson; + break; + } + default: + { + serializerIndex = ++index; + break; + } + } + + _serializers[serializerIndex] = serialize; + } + + _isInitialized = true; + Log.Info($"初始化序列化器成功,数量为:{_serializers.Length}"); + } + catch (Exception e) + { + Log.Error(e); + Dispose(); + } + } +#else + /// + /// 初始化方法 + /// + public static void Initialize() + { + if (_isInitialized) + { + return; + } + + _serializers = new ISerialize[1]; + _serializers[0] = new ProtoBufPackHelper(); + } +#endif + + /// + /// 销毁方法 + /// + public static void Dispose() + { + _isInitialized = false; + Array.Clear(_serializers, 0, _serializers.Length); + } + + /// + /// 根据协议类型获取序列化器 + /// + /// + /// + public static ISerialize GetSerializer(uint opCodeProtocolType) + { + return _serializers[opCodeProtocolType]; + } + + /// + /// 获得一个序列化器 + /// + /// + /// + /// + public static bool TryGetSerializer(uint opCodeProtocolType, out ISerialize serializer) + { + if (opCodeProtocolType < _serializers.Length) + { + serializer = _serializers[opCodeProtocolType]; + return true; + } + + serializer = default; + return false; + } + } +} \ No newline at end of file diff --git a/Hotfix/CustomSystem/IRunSystem.cs b/Hotfix/CustomSystem/IRunSystem.cs index b2fac57..8c9fb69 100644 --- a/Hotfix/CustomSystem/IRunSystem.cs +++ b/Hotfix/CustomSystem/IRunSystem.cs @@ -28,7 +28,7 @@ public abstract class RunSystem : CustomSystem where T : Entity /// 不知道为什么这样定义的,就照搬就可以了。 /// /// - public override Type EntityType() => typeof(T); + public override Type EntitiesType() => typeof(T); } // 下面是一个测试自定义系统。 diff --git a/Hotfix/OnCreateSceneEvent.cs b/Hotfix/OnCreateSceneEvent.cs index 09da220..ff89e97 100644 --- a/Hotfix/OnCreateSceneEvent.cs +++ b/Hotfix/OnCreateSceneEvent.cs @@ -56,7 +56,7 @@ public sealed class OnCreateSceneEvent : AsyncEventSystem { var subSceneTestComponent = scene.AddComponent(); Log.Debug("增加了SubSceneTestComponent"); - // scene.EntityComponent.CustomSystem(subSceneTestComponent, CustomSystemType.RunSystem); + scene.EntityComponent.CustomSystem(subSceneTestComponent, CustomSystemType.RunSystem); break; } case SceneType.Addressable: @@ -173,7 +173,7 @@ public sealed class OnCreateSceneEvent : AsyncEventSystem // 执行自定义系统 var testCustomSystemComponent = scene.AddComponent(); - // scene.EntityComponent.CustomSystem(testCustomSystemComponent, CustomSystemType.RunSystem); + scene.EntityComponent.CustomSystem(testCustomSystemComponent, CustomSystemType.RunSystem); // // 测试配置表 // var instanceList = UnitConfigData.Instance.List; // var unitConfig = instanceList[0]; diff --git a/Main/Program.cs b/Main/Program.cs index 7ff04d1..e56ef4f 100644 --- a/Main/Program.cs +++ b/Main/Program.cs @@ -1,67 +1,33 @@ -// ================================================================================ -// Fantasy.Net 服务器应用程序入口 -// ================================================================================ -// 本文件是 Fantasy.Net 分布式游戏服务器的主入口点 -// -// 初始化流程: -// 1. 强制加载引用程序集,触发 ModuleInitializer 执行 -// 2. 配置日志基础设施(NLog) -// 3. 启动 Fantasy.Net 框架 -// ================================================================================ - -using Fantasy; - -try -{ - // 初始化引用的程序集,确保 ModuleInitializer 执行 - // .NET 采用延迟加载机制 - 仅当类型被引用时才加载程序集 - // 通过访问 AssemblyMarker 强制加载程序集并调用 ModuleInitializer - // 注意:Native AOT 不存在延迟加载问题,所有程序集在编译时打包 - AssemblyHelper.Initialize(); - // 配置 NLog 日志基础设施 - // 可选:传入 null 或省略参数以使用控制台日志 - var logger = new Fantasy.NLog("Server"); - // 使用配置的日志系统启动 Fantasy.Net 框架 - await Fantasy.Platform.Net.Entry.Start(logger); -} -catch (Exception ex) -{ - Console.Error.WriteLine($"服务器初始化过程中发生致命错误:{ex}"); - Environment.Exit(1); -} +using Fantasy; +using Fantasy.ConfigTable; +using Fantasy.Helper; +using Fantasy.IdFactory; +using Fantasy.Platform.Net; -// using Fantasy; -// using Fantasy.ConfigTable; -// using Fantasy.Helper; -// using Fantasy.IdFactory; -// using Fantasy.Platform.Net; -// -// -// -// // 设置ID生成规则 -// IdFactoryHelper.Initialize(IdFactoryType.World); -// // // 获取配置文件 -// // // 比如通过远程获取这个配置文件,这样可以多组服务器共享一套配置了 -// // var machineConfigText = await FileHelper.GetTextByRelativePath("../../../Config/Json/Server/MachineConfigData.Json"); -// // var processConfigText = await FileHelper.GetTextByRelativePath("../../../Config/Json/Server/ProcessConfigData.Json"); -// // var worldConfigText = await FileHelper.GetTextByRelativePath("../../../Config/Json/Server/WorldConfigData.Json"); -// // var sceneConfigText = await FileHelper.GetTextByRelativePath("../../../Config/Json/Server/SceneConfigData.Json"); -// // // 初始化配置文件 -// // // 如果重复初始化方法会覆盖掉上一次的数据,非常适合热重载时使用 -// // MachineConfigData.Initialize(machineConfigText); -// // ProcessConfigData.Initialize(processConfigText); -// // WorldConfigData.Initialize(worldConfigText); -// // SceneConfigData.Initialize(sceneConfigText); -// -// //解析配置文件 -// var gameConfigText = await FileHelper.GetTextByRelativePath("../../../Config/Json/configs.Json"); -// ConfigTableHelper.Initialize(gameConfigText,NB.AssemblyHelper.Assemblies); -// -// // 注册日志模块到框架 -// // 开发者可以自己注册日志系统到框架,只要实现Fantasy.ILog接口就可以。 -// // 这里用的是NLog日志系统注册到框架中。 -// Log.Register(new Fantasy.NLog("Server")); -// -// await Entry.Start(NB.AssemblyHelper.Assemblies); +// 设置ID生成规则 +IdFactoryHelper.Initialize(IdFactoryType.World); +// // 获取配置文件 +// // 比如通过远程获取这个配置文件,这样可以多组服务器共享一套配置了 +// var machineConfigText = await FileHelper.GetTextByRelativePath("../../../Config/Json/Server/MachineConfigData.Json"); +// var processConfigText = await FileHelper.GetTextByRelativePath("../../../Config/Json/Server/ProcessConfigData.Json"); +// var worldConfigText = await FileHelper.GetTextByRelativePath("../../../Config/Json/Server/WorldConfigData.Json"); +// var sceneConfigText = await FileHelper.GetTextByRelativePath("../../../Config/Json/Server/SceneConfigData.Json"); +// // 初始化配置文件 +// // 如果重复初始化方法会覆盖掉上一次的数据,非常适合热重载时使用 +// MachineConfigData.Initialize(machineConfigText); +// ProcessConfigData.Initialize(processConfigText); +// WorldConfigData.Initialize(worldConfigText); +// SceneConfigData.Initialize(sceneConfigText); + +//解析配置文件 +var gameConfigText = await FileHelper.GetTextByRelativePath("../../../Config/Json/configs.Json"); +ConfigTableHelper.Initialize(gameConfigText,NB.AssemblyHelper.Assemblies); + +// 注册日志模块到框架 +// 开发者可以自己注册日志系统到框架,只要实现Fantasy.ILog接口就可以。 +// 这里用的是NLog日志系统注册到框架中。 +Log.Register(new Fantasy.NLog("Server")); + +await Entry.Start(NB.AssemblyHelper.Assemblies); diff --git a/Main/Properties/launchSettings.json b/Main/Properties/launchSettings.json index 90a0233..c31a6c9 100644 --- a/Main/Properties/launchSettings.json +++ b/Main/Properties/launchSettings.json @@ -3,7 +3,7 @@ "profiles": { "Main": { "commandName": "Project", - "workingDirectory": "$(OutputPath)", +// "workingDirectory": "$(OutputPath)", "environmentVariables": {}, "commandLineArgs": "--m Develop" } diff --git a/Server.sln b/Server.sln index a5cc31b..29972cd 100644 --- a/Server.sln +++ b/Server.sln @@ -14,8 +14,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Fantasy", "Fantasy", "{B1F4 EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ThirdParty", "ThirdParty\ThirdParty.csproj", "{9C25A89F-0C87-4F91-AEEA-2ECCD2763DC3}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Fantasy.SourceGenerator", "Fantasy\Fantasy.Net\Fantasy.SourceGenerator\Fantasy.SourceGenerator.csproj", "{059396AC-A322-4EB7-9C32-C38CD997DD20}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -46,14 +44,9 @@ Global {9C25A89F-0C87-4F91-AEEA-2ECCD2763DC3}.Debug|Any CPU.Build.0 = Debug|Any CPU {9C25A89F-0C87-4F91-AEEA-2ECCD2763DC3}.Release|Any CPU.ActiveCfg = Release|Any CPU {9C25A89F-0C87-4F91-AEEA-2ECCD2763DC3}.Release|Any CPU.Build.0 = Release|Any CPU - {059396AC-A322-4EB7-9C32-C38CD997DD20}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {059396AC-A322-4EB7-9C32-C38CD997DD20}.Debug|Any CPU.Build.0 = Debug|Any CPU - {059396AC-A322-4EB7-9C32-C38CD997DD20}.Release|Any CPU.ActiveCfg = Release|Any CPU - {059396AC-A322-4EB7-9C32-C38CD997DD20}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {0A539FA2-C595-4AA3-A2C3-86BF86EA7FFB} = {B1F4CF72-A767-4509-B050-8DB30D0DC40A} {7BDEBABB-8630-4B61-8AF4-75DD969F29E6} = {B1F4CF72-A767-4509-B050-8DB30D0DC40A} - {059396AC-A322-4EB7-9C32-C38CD997DD20} = {B1F4CF72-A767-4509-B050-8DB30D0DC40A} EndGlobalSection EndGlobal diff --git a/Server.sln.DotSettings.user b/Server.sln.DotSettings.user index 845c849..c0de287 100644 --- a/Server.sln.DotSettings.user +++ b/Server.sln.DotSettings.user @@ -73,7 +73,6 @@ ForceIncluded ForceIncluded ForceIncluded - ForceIncluded ForceIncluded ForceIncluded ForceIncluded diff --git a/Tools/NBConfigBuilder/TemplateClient.txt b/Tools/NBConfigBuilder/TemplateClient.txt index 01d76c8..47a04cb 100644 --- a/Tools/NBConfigBuilder/TemplateClient.txt +++ b/Tools/NBConfigBuilder/TemplateClient.txt @@ -11,7 +11,7 @@ using NBC.Serialize; namespace NBF { [ProtoContract] - public sealed partial class (ConfigName) : ASerialize, IConfigTable + public sealed partial class (ConfigName) : ASerialize, IProto, IConfigTable { (Fields) [ProtoIgnore] diff --git a/Tools/NBConfigBuilder/TemplateServer.txt b/Tools/NBConfigBuilder/TemplateServer.txt index 0aec988..3a2823b 100644 --- a/Tools/NBConfigBuilder/TemplateServer.txt +++ b/Tools/NBConfigBuilder/TemplateServer.txt @@ -11,7 +11,7 @@ using Fantasy.ConfigTable; namespace NBF { [ProtoContract] - public sealed partial class (ConfigName) : ASerialize, IConfigTable + public sealed partial class (ConfigName) : ASerialize, IProto, IConfigTable { (Fields) [ProtoIgnore] diff --git a/Tools/NBConfigBuilder/config - 副本.json b/Tools/NBConfigBuilder/config - 副本.json new file mode 100644 index 0000000..4891ac6 --- /dev/null +++ b/Tools/NBConfigBuilder/config - 副本.json @@ -0,0 +1,10 @@ +{ + "ExcelPath": "D:\\work\\Fishing2\\Config", + "ClientPath": "D:\\work\\Fishing2\\Assets\\Scripts\\Generate\\Config", + "ClientJsonPath": "D:\\work\\Fishing2\\Assets\\Resources\\config", + "ServerPath": "D:\\work\\Fishing2Server\\Entity\\Generate\\ConfigTable\\Entity", + "ServerJsonPath": "D:\\work\\Fishing2Server\\Config\\Json", + "GenClient": true, + "GenServer": true, + "ExcelVersionPath": "D:\\work\\Fishing2\\Config\\Version.txt" +} \ No newline at end of file diff --git a/Tools/NBConfigBuilder/config.json b/Tools/NBConfigBuilder/config.json index 4891ac6..c1f0aeb 100644 --- a/Tools/NBConfigBuilder/config.json +++ b/Tools/NBConfigBuilder/config.json @@ -1,10 +1,10 @@ { - "ExcelPath": "D:\\work\\Fishing2\\Config", - "ClientPath": "D:\\work\\Fishing2\\Assets\\Scripts\\Generate\\Config", - "ClientJsonPath": "D:\\work\\Fishing2\\Assets\\Resources\\config", - "ServerPath": "D:\\work\\Fishing2Server\\Entity\\Generate\\ConfigTable\\Entity", - "ServerJsonPath": "D:\\work\\Fishing2Server\\Config\\Json", + "ExcelPath": "D:\\myself\\Games\\Fishing2\\Config", + "ClientPath": "D:\\myself\\Games\\Fishing2\\Assets\\Scripts\\Generate\\Config", + "ClientJsonPath": "D:\\myself\\Games\\Fishing2\\Assets\\Resources\\config", + "ServerPath": "D:\\myself\\Games\\Fishing2Server\\Entity\\Generate\\ConfigTable\\Entity", + "ServerJsonPath": "D:\\myself\\Games\\Fishing2Server\\Config\\Json", "GenClient": true, "GenServer": true, - "ExcelVersionPath": "D:\\work\\Fishing2\\Config\\Version.txt" + "ExcelVersionPath": "D:\\myself\\Games\\Fishing2\\Config\\Version.txt" } \ No newline at end of file diff --git a/Tools/NBConfigBuilder/config2.json b/Tools/NBConfigBuilder/config2.json deleted file mode 100644 index c1f0aeb..0000000 --- a/Tools/NBConfigBuilder/config2.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "ExcelPath": "D:\\myself\\Games\\Fishing2\\Config", - "ClientPath": "D:\\myself\\Games\\Fishing2\\Assets\\Scripts\\Generate\\Config", - "ClientJsonPath": "D:\\myself\\Games\\Fishing2\\Assets\\Resources\\config", - "ServerPath": "D:\\myself\\Games\\Fishing2Server\\Entity\\Generate\\ConfigTable\\Entity", - "ServerJsonPath": "D:\\myself\\Games\\Fishing2Server\\Config\\Json", - "GenClient": true, - "GenServer": true, - "ExcelVersionPath": "D:\\myself\\Games\\Fishing2\\Config\\Version.txt" -} \ No newline at end of file diff --git a/Tools/NetworkProtocol/Fantasy.Tools.NetworkProtocol b/Tools/NetworkProtocol/Fantasy.Tools.NetworkProtocol deleted file mode 100644 index a7bf479..0000000 Binary files a/Tools/NetworkProtocol/Fantasy.Tools.NetworkProtocol and /dev/null differ diff --git a/Tools/NetworkProtocol/Fantasy.Tools.NetworkProtocol.dll b/Tools/NetworkProtocol/Fantasy.Tools.NetworkProtocol.dll index 8a519e9..5502bd8 100644 Binary files a/Tools/NetworkProtocol/Fantasy.Tools.NetworkProtocol.dll and b/Tools/NetworkProtocol/Fantasy.Tools.NetworkProtocol.dll differ diff --git a/Tools/NetworkProtocol/Fantasy.Tools.NetworkProtocol.exe b/Tools/NetworkProtocol/Fantasy.Tools.NetworkProtocol.exe new file mode 100644 index 0000000..7bdf927 Binary files /dev/null and b/Tools/NetworkProtocol/Fantasy.Tools.NetworkProtocol.exe differ diff --git a/Tools/NetworkProtocol/Fantasy.Tools.NetworkProtocol.pdb b/Tools/NetworkProtocol/Fantasy.Tools.NetworkProtocol.pdb index c5481d9..5c60a04 100644 Binary files a/Tools/NetworkProtocol/Fantasy.Tools.NetworkProtocol.pdb and b/Tools/NetworkProtocol/Fantasy.Tools.NetworkProtocol.pdb differ diff --git a/Tools/SourceCode/Fantasy.Tools.NetworkProtocol/NetworkProtocolTemplate.txt b/Tools/NetworkProtocol/Template.txt similarity index 87% rename from Tools/SourceCode/Fantasy.Tools.NetworkProtocol/NetworkProtocolTemplate.txt rename to Tools/NetworkProtocol/Template.txt index 37c9df6..217dfaf 100644 --- a/Tools/SourceCode/Fantasy.Tools.NetworkProtocol/NetworkProtocolTemplate.txt +++ b/Tools/NetworkProtocol/Template.txt @@ -1,7 +1,6 @@ #if SERVER using ProtoBuf; (UsingNamespace) -using System; using System.Collections.Generic; using MongoDB.Bson.Serialization.Attributes; using Fantasy; @@ -18,18 +17,18 @@ using Fantasy.Serialize; #pragma warning disable CS8618 namespace Fantasy -{ +{ #else using ProtoBuf; -using System; (UsingNamespace) using System.Collections.Generic; using Fantasy; -using Fantasy.Network.Interface; -using Fantasy.Serialize; +using NBC; +using NBC.Network.Interface; +using NBC.Serialize; #pragma warning disable CS8618 -namespace Fantasy +namespace NBC { #endif -(Content)} +(Content)} \ No newline at end of file diff --git a/Tools/SourceCode/Fantasy.Tools.ConfigTable/Exporter/ExcelExporter.cs b/Tools/SourceCode/Fantasy.Tools.ConfigTable/Exporter/ExcelExporter.cs index 00aa813..f11f6b0 100644 --- a/Tools/SourceCode/Fantasy.Tools.ConfigTable/Exporter/ExcelExporter.cs +++ b/Tools/SourceCode/Fantasy.Tools.ConfigTable/Exporter/ExcelExporter.cs @@ -664,6 +664,7 @@ public sealed class ExcelExporter Task.WaitAll(exportToBinaryTasks.ToArray()); } + private void GenerateBinary(string fileInfoFullName, ExcelWorksheet excelWorksheet, DynamicConfigDataType dynamicInfo, List cols, string id, int row, bool isLast, bool isServer) { if (cols == null || IsNullOrEmpty(id) || cols.Count <= 0 || dynamicInfo?.ConfigType == null) @@ -718,6 +719,7 @@ public sealed class ExcelExporter dynamicInfo.Json.AppendLine($"{json},"); } } + /// /// 从 Excel 文件加载工作表并返回 ExcelWorksheet 对象。 /// diff --git a/Tools/SourceCode/Fantasy.Tools.ConfigTable/Fantasy.Tools.ConfigTable.csproj b/Tools/SourceCode/Fantasy.Tools.ConfigTable/Fantasy.Tools.ConfigTable.csproj index 460135b..b58a43e 100644 --- a/Tools/SourceCode/Fantasy.Tools.ConfigTable/Fantasy.Tools.ConfigTable.csproj +++ b/Tools/SourceCode/Fantasy.Tools.ConfigTable/Fantasy.Tools.ConfigTable.csproj @@ -35,28 +35,31 @@ - + Core\Base\Pool.cs - + Core\Dictionary\IntDictionaryConfig.cs - + Core\Dictionary\StringDictionaryConfig.cs - + Core\Interface\IConfigTable.cs - + + Core\Serialize\ProtoBufPackHelper\IProto.cs + + Excel\Base\TimeHelper.cs - + Excel\Base\IPool.cs - + Excel\Base\AssemblyInfo.cs - + Excel\Base\OneToManyList.cs @@ -65,37 +68,37 @@ Core\ExportPlatform.cs - + Core\FileHelper.cs - + Core\HashCodeHelper.cs - + Core\JsonHelper.cs - + Core\Serialize\BsonPack\BsonPackHelperNet.cs - + Core\Serialize\BsonPack\StructBsonSerialize.cs - + Core\Serialize\BsonPack\SupportInitializeChecker.cs - + Core\Serialize\Interface\ASerialize.cs - + Core\Serialize\Interface\ISerialize.cs - + Core\Serialize\SerializerManager.cs - + Core\Serialize\ProtoBufPackHelper\ProtoBufPackHelperNet.cs - + Excel\Base\MemoryStreamBuffer.cs diff --git a/Tools/SourceCode/Fantasy.Tools.NetworkProtocol/Exporter/NetworkProtocolTemplate.cs b/Tools/SourceCode/Fantasy.Tools.NetworkProtocol/Exporter/NetworkProtocolTemplate.cs deleted file mode 100644 index 6ccab54..0000000 --- a/Tools/SourceCode/Fantasy.Tools.NetworkProtocol/Exporter/NetworkProtocolTemplate.cs +++ /dev/null @@ -1,38 +0,0 @@ -namespace Fantasy.Tools.ProtocalExporter; - -public static class NetworkProtocolTemplate -{ - private static string? _cachedTemplate; - - public static string Template - { - get - { - if (_cachedTemplate != null) - { - return _cachedTemplate; - } - - // 尝试从多个位置读取模板文件 - var possiblePaths = new[] - { - Path.Combine(ExporterAges.Instance.Folder ?? Directory.GetCurrentDirectory(), "NetworkProtocolTemplate.txt"), - Path.Combine(Directory.GetCurrentDirectory(), "NetworkProtocolTemplate.txt"), - Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "NetworkProtocolTemplate.txt") - }; - - foreach (var path in possiblePaths) - { - if (File.Exists(path)) - { - _cachedTemplate = File.ReadAllText(path); - return _cachedTemplate; - } - } - - throw new FileNotFoundException("找不到 NetworkProtocolTemplate.txt 模板文件!\n" + - "请确保模板文件位于以下位置之一:\n" + - string.Join("\n", possiblePaths)); - } - } -} \ No newline at end of file diff --git a/Tools/SourceCode/Fantasy.Tools.NetworkProtocol/Exporter/ProtocolExporter.cs b/Tools/SourceCode/Fantasy.Tools.NetworkProtocol/Exporter/ProtocolExporter.cs index eced15c..f59b1db 100644 --- a/Tools/SourceCode/Fantasy.Tools.NetworkProtocol/Exporter/ProtocolExporter.cs +++ b/Tools/SourceCode/Fantasy.Tools.NetworkProtocol/Exporter/ProtocolExporter.cs @@ -4,6 +4,7 @@ using Fantasy.Helper; using Fantasy.Network; using OpCode = Fantasy.Network.OpCode; using OpCodeType = Fantasy.Network.OpCodeType; + #pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type. #pragma warning disable CS8602 // Dereference of a possibly null reference. // ReSharper disable PossibleNullReferenceException @@ -13,38 +14,19 @@ using OpCodeType = Fantasy.Network.OpCodeType; namespace Fantasy.Tools.ProtocalExporter; -// 自定义异常,用于协议格式错误 -public class ProtocolFormatException : Exception -{ - public ProtocolFormatException(string message) : base(message) { } -} - public enum NetworkProtocolOpCodeType { None = 0, Outer = 1, Inner = 2, } + public sealed class OpcodeInfo { public uint Code; public string Name; } -public sealed class MessageFieldInfo -{ - public string FieldName; - public string FieldType; -} - -public sealed class MessageHelperInfo -{ - public string MessageName; - public string MessageType; // "IMessage" or "IRequest" - public string ResponseType; // Only for IRequest - public List Fields = new(); // 消息的属性字段 -} - public sealed class ProtocolExporter { private string _serverTemplate; @@ -56,12 +38,13 @@ public sealed class ProtocolExporter private readonly string _networkProtocolServerDirectory; private readonly string _networkProtocolDirectoryOuter; private readonly string _networkProtocolDirectoryInner; - + public ProtocolExporter() { Console.OutputEncoding = Encoding.UTF8; - - if (ExporterSettingsHelper.NetworkProtocolDirectory == null || ExporterSettingsHelper.NetworkProtocolDirectory.Trim() == "") + + if (ExporterSettingsHelper.NetworkProtocolDirectory == null || + ExporterSettingsHelper.NetworkProtocolDirectory.Trim() == "") { Log.Info($"NetworkProtocolDirectory Can not be empty!"); return; @@ -140,21 +123,12 @@ public sealed class ProtocolExporter var errorCodeStr = new StringBuilder(); var usingNamespace = new HashSet(); var saveDirectory = new Dictionary(); - var helperInfos = new List(); OpcodeInfo opcodeInfo = null; ProtocolOpCode protocolOpCode = null; string[] protocolFiles = null; - MessageHelperInfo currentHelperInfo = null; // 当前正在处理的消息Helper信息 - - // 格式检测相关 - var allMessageNames = new HashSet(); // 所有消息名称 - var currentFieldNames = new HashSet(); // 当前消息的字段名 - var currentFieldNumbers = new HashSet(); // 当前消息的字段编号 - var currentFilePath = ""; // 当前处理的文件路径 - _opcodes.Clear(); - + switch (opCodeType) { case NetworkProtocolOpCodeType.Outer: @@ -163,12 +137,12 @@ public sealed class ProtocolExporter { saveDirectory.Add(_networkProtocolServerDirectory, _serverTemplate); } - + if (ExporterAges.Instance.ExportPlatform.HasFlag(ExportPlatform.Client)) { saveDirectory.Add(_networkProtocolClientDirectory, _clientTemplate); } - + protocolOpCode = new ProtocolOpCode() { Message = OpCodeType.OuterMessage, @@ -188,7 +162,8 @@ public sealed class ProtocolExporter RoamingResponse = OpCodeType.OuterRoamingResponse, }; opCodeName = "OuterOpcode"; - protocolFiles = FileHelper.GetDirectoryFile(_networkProtocolDirectoryOuter, "*.proto", SearchOption.AllDirectories); + protocolFiles = FileHelper.GetDirectoryFile(_networkProtocolDirectoryOuter, "*.proto", + SearchOption.AllDirectories); break; } case NetworkProtocolOpCodeType.Inner: @@ -213,7 +188,8 @@ public sealed class ProtocolExporter }; opCodeName = "InnerOpcode"; saveDirectory.Add(_networkProtocolServerDirectory, _serverTemplate); - protocolFiles = FileHelper.GetDirectoryFile(_networkProtocolDirectoryInner, "*.proto", SearchOption.AllDirectories); + protocolFiles = FileHelper.GetDirectoryFile(_networkProtocolDirectoryInner, "*.proto", + SearchOption.AllDirectories); break; } } @@ -222,12 +198,11 @@ public sealed class ProtocolExporter { return; } - + #region GenerateFiles foreach (var filePath in protocolFiles) { - currentFilePath = filePath; // 记录当前文件路径 var keyIndex = 1; var parameter = ""; var hasOpCode = false; @@ -252,7 +227,8 @@ public sealed class ProtocolExporter if (currentLine.StartsWith("///")) { - messageStr.AppendFormat(" /// \r\n" + " /// {0}\r\n" + " /// \r\n", currentLine.Substring("///".Length)); + messageStr.AppendFormat(" /// \r\n" + " /// {0}\r\n" + " /// \r\n", + currentLine.Substring("///".Length)); continue; } @@ -271,6 +247,15 @@ public sealed class ProtocolExporter protocolOpCodeType = OpCodeProtocolType.ProtoBuf; break; } + // case "MemoryPack": + // { + // keyIndex = 0; + // protocolType = "\t[MemoryPackable]"; + // protocolIgnore = "\t\t[MemoryPackIgnore]"; + // protocolMember = "MemoryPackOrder"; + // // protocolOpCodeType = OpCodeProtocolType.MemoryPack; + // break; + // } case "Bson": { if (opCodeType == NetworkProtocolOpCodeType.Outer) @@ -278,6 +263,7 @@ public sealed class ProtocolExporter Log.Error("Under Outer, /// does not support the Bson protocol!"); return; } + protocolType = null; protocolIgnore = "\t\t[BsonIgnore]"; protocolMember = null; @@ -307,21 +293,6 @@ public sealed class ProtocolExporter { isMsgHead = true; className = currentLine.Split(SplitChars, StringSplitOptions.RemoveEmptyEntries)[1]; - - // 检测消息名称是否重复 - if (!allMessageNames.Add(className)) - { - var errorMsg = $"协议格式错误!\n文件: {currentFilePath}\n消息名称重复: {className}"; - Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine(errorMsg); - Console.ResetColor(); - throw new ProtocolFormatException(errorMsg); - } - - // 清空当前消息的字段集合 - currentFieldNames.Clear(); - currentFieldNumbers.Clear(); - var splits = currentLine.Split(new[] { "//" }, StringSplitOptions.RemoveEmptyEntries); if (isSetProtocol) @@ -335,11 +306,12 @@ public sealed class ProtocolExporter { messageStr.AppendLine("\t[ProtoContract]"); } - + if (splits.Length > 1) { hasOpCode = true; - var parameterArray = currentLine.Split(new[] { "//" }, StringSplitOptions.RemoveEmptyEntries)[1].Trim().Split(','); + var parameterArray = currentLine.Split(new[] { "//" }, StringSplitOptions.RemoveEmptyEntries)[1] + .Trim().Split(','); parameter = parameterArray[0].Trim(); opcodeInfo = new OpcodeInfo() { @@ -367,12 +339,15 @@ public sealed class ProtocolExporter break; } } + break; } case 3: { responseTypeStr = parameterArray[1].Trim(); - customRouteType = parameter.Contains("IRoaming") ? $"Fantasy.RoamingType.{parameterArray[2].Trim()}" : $"Fantasy.RouteType.{parameterArray[2].Trim()}"; + customRouteType = parameter.Contains("IRoaming") + ? $"Fantasy.RoamingType.{parameterArray[2].Trim()}" + : $"Fantasy.RouteType.{parameterArray[2].Trim()}"; break; } } @@ -386,6 +361,11 @@ public sealed class ProtocolExporter messageStr.Append(string.IsNullOrWhiteSpace(parameter) ? $"\tpublic partial class {className} : AMessage" : $"\tpublic partial class {className} : AMessage, {parameter}"); + if (protocolMember == "ProtoMember") + { + messageStr.Append(", IProto"); + } + continue; } @@ -400,33 +380,22 @@ public sealed class ProtocolExporter { messageStr.AppendLine("\n\t{"); messageStr.AppendLine($"\t\tpublic static {className} Create(Scene scene)"); - messageStr.AppendLine($"\t\t{{\n\t\t\treturn scene.MessagePoolComponent.Rent<{className}>();\n\t\t}}"); + messageStr.AppendLine( + $"\t\t{{\n\t\t\treturn scene.MessagePoolComponent.Rent<{className}>();\n\t\t}}"); messageStr.AppendLine($"\t\tpublic override void Dispose()"); messageStr.AppendLine($"\t\t{{"); - messageStr.AppendLine($"<<<>>#if FANTASY_NET || FANTASY_UNITY\n\t\t\tGetScene().MessagePoolComponent.Return<{className}>(this);\n#endif"); + messageStr.AppendLine( + $"<<<>>#if FANTASY_NET || FANTASY_UNITY\n\t\t\tGetScene().MessagePoolComponent.Return<{className}>(this);\n#endif"); messageStr.AppendLine($"\t\t}}"); if (parameter == "IMessage") { - opcodeInfo.Code = OpCode.Create(protocolOpCodeType, protocolOpCode.Message, protocolOpCode.AMessage++); + opcodeInfo.Code = OpCode.Create(protocolOpCodeType, protocolOpCode.Message, + protocolOpCode.AMessage++); messageStr.AppendLine($"\t\tpublic uint OpCode() {{ return {opCodeName}.{className}; }}"); - - // 收集客户端Helper信息 - if (opCodeType == NetworkProtocolOpCodeType.Outer) - { - currentHelperInfo = new MessageHelperInfo - { - MessageName = className, - MessageType = "IMessage" - }; - helperInfos.Add(currentHelperInfo); - } } else { - // 保存 responseTypeStr 用于后续收集Helper信息 - var savedResponseType = responseTypeStr; - if (responseTypeStr != null) { messageStr.AppendLine(protocolIgnore); @@ -444,7 +413,8 @@ public sealed class ProtocolExporter if (hasOpCode) { - messageStr.AppendLine($"\t\tpublic uint OpCode() {{ return {opCodeName}.{className}; }}"); + messageStr.AppendLine( + $"\t\tpublic uint OpCode() {{ return {opCodeName}.{className}; }}"); } if (customRouteType != null) @@ -458,28 +428,19 @@ public sealed class ProtocolExporter { case "IRequest": { - opcodeInfo.Code = OpCode.Create(protocolOpCodeType, protocolOpCode.Request, protocolOpCode.ARequest++); - - // 收集客户端Helper信息 - if (opCodeType == NetworkProtocolOpCodeType.Outer) - { - currentHelperInfo = new MessageHelperInfo - { - MessageName = className, - MessageType = "IRequest", - ResponseType = savedResponseType - }; - helperInfos.Add(currentHelperInfo); - } + opcodeInfo.Code = OpCode.Create(protocolOpCodeType, protocolOpCode.Request, + protocolOpCode.ARequest++); break; } case "IResponse": { - opcodeInfo.Code = OpCode.Create(protocolOpCodeType, protocolOpCode.Response, protocolOpCode.AResponse++); + opcodeInfo.Code = OpCode.Create(protocolOpCodeType, protocolOpCode.Response, + protocolOpCode.AResponse++); if (!string.IsNullOrEmpty(protocolMember)) { errorCodeStr.AppendLine($"\t\t[{protocolMember}(ErrorCodeKeyIndex)]"); } + errorCodeStr.AppendLine("\t\tpublic uint ErrorCode { get; set; }"); disposeStr.AppendLine($"\t\t\tErrorCode = default;"); break; @@ -490,44 +451,28 @@ public sealed class ProtocolExporter { case "IAddressableRouteMessage": { - opcodeInfo.Code = OpCode.Create(protocolOpCodeType, protocolOpCode.AddressableMessage, protocolOpCode.AAddressableMessage++); - - // 收集客户端Helper信息 - if (opCodeType == NetworkProtocolOpCodeType.Outer) - { - currentHelperInfo = new MessageHelperInfo - { - MessageName = className, - MessageType = "IMessage" // IAddressableRouteMessage也是Send方式 - }; - helperInfos.Add(currentHelperInfo); - } + opcodeInfo.Code = OpCode.Create(protocolOpCodeType, + protocolOpCode.AddressableMessage, + protocolOpCode.AAddressableMessage++); break; } case "IAddressableRouteRequest": { - opcodeInfo.Code = OpCode.Create(protocolOpCodeType, protocolOpCode.AddressableRequest, protocolOpCode.AAddressableRequest++); - - // 收集客户端Helper信息 - if (opCodeType == NetworkProtocolOpCodeType.Outer) - { - currentHelperInfo = new MessageHelperInfo - { - MessageName = className, - MessageType = "IRequest", - ResponseType = savedResponseType - }; - helperInfos.Add(currentHelperInfo); - } + opcodeInfo.Code = OpCode.Create(protocolOpCodeType, + protocolOpCode.AddressableRequest, + protocolOpCode.AAddressableRequest++); break; } case "IAddressableRouteResponse": { - opcodeInfo.Code = OpCode.Create(protocolOpCodeType, protocolOpCode.AddressableResponse, protocolOpCode.AAddressableResponse++); + opcodeInfo.Code = OpCode.Create(protocolOpCodeType, + protocolOpCode.AddressableResponse, + protocolOpCode.AAddressableResponse++); if (!string.IsNullOrEmpty(protocolMember)) { errorCodeStr.AppendLine($"\t\t[{protocolMember}(ErrorCodeKeyIndex)]"); } + errorCodeStr.AppendLine("\t\tpublic uint ErrorCode { get; set; }"); disposeStr.AppendLine($"\t\t\tErrorCode = default;"); break; @@ -536,54 +481,44 @@ public sealed class ProtocolExporter { if (opCodeType == NetworkProtocolOpCodeType.Inner) { - throw new NotSupportedException("Under Inner, /// does not support the ICustomRouteMessage!"); + throw new NotSupportedException( + "Under Inner, /// does not support the ICustomRouteMessage!"); } - opcodeInfo.Code = OpCode.Create(protocolOpCodeType, protocolOpCode.CustomRouteMessage, protocolOpCode.ACustomRouteMessage++); - // 收集客户端Helper信息 - if (opCodeType == NetworkProtocolOpCodeType.Outer) - { - currentHelperInfo = new MessageHelperInfo - { - MessageName = className, - MessageType = "IMessage" // ICustomRouteMessage也是Send方式 - }; - helperInfos.Add(currentHelperInfo); - } + opcodeInfo.Code = OpCode.Create(protocolOpCodeType, + protocolOpCode.CustomRouteMessage, + protocolOpCode.ACustomRouteMessage++); break; } case "ICustomRouteRequest": { if (opCodeType == NetworkProtocolOpCodeType.Inner) { - throw new NotSupportedException("Under Inner, /// does not support the ICustomRouteMessage!"); + throw new NotSupportedException( + "Under Inner, /// does not support the ICustomRouteMessage!"); } - opcodeInfo.Code = OpCode.Create(protocolOpCodeType, protocolOpCode.CustomRouteRequest, protocolOpCode.ACustomRouteRequest++); - // 收集客户端Helper信息 - if (opCodeType == NetworkProtocolOpCodeType.Outer) - { - currentHelperInfo = new MessageHelperInfo - { - MessageName = className, - MessageType = "IRequest", - ResponseType = savedResponseType - }; - helperInfos.Add(currentHelperInfo); - } + opcodeInfo.Code = OpCode.Create(protocolOpCodeType, + protocolOpCode.CustomRouteRequest, + protocolOpCode.ACustomRouteRequest++); break; } case "ICustomRouteResponse": { if (opCodeType == NetworkProtocolOpCodeType.Inner) { - throw new NotSupportedException("Under Inner, /// does not support the ICustomRouteMessage!"); + throw new NotSupportedException( + "Under Inner, /// does not support the ICustomRouteMessage!"); } - opcodeInfo.Code = OpCode.Create(protocolOpCodeType, protocolOpCode.CustomRouteResponse, protocolOpCode.ACustomRouteResponse++); + + opcodeInfo.Code = OpCode.Create(protocolOpCodeType, + protocolOpCode.CustomRouteResponse, + protocolOpCode.ACustomRouteResponse++); if (!string.IsNullOrEmpty(protocolMember)) { errorCodeStr.AppendLine($"\t\t[{protocolMember}(ErrorCodeKeyIndex)]"); } + errorCodeStr.AppendLine("\t\tpublic uint ErrorCode { get; set; }"); disposeStr.AppendLine($"\t\t\tErrorCode = default;"); break; @@ -594,18 +529,8 @@ public sealed class ProtocolExporter // { // throw new NotSupportedException("Under Inner, /// does not support the IRoamingMessage!"); // } - opcodeInfo.Code = OpCode.Create(protocolOpCodeType, protocolOpCode.RoamingMessage, protocolOpCode.ARoamingMessage++); - - // 收集客户端Helper信息 - if (opCodeType == NetworkProtocolOpCodeType.Outer) - { - currentHelperInfo = new MessageHelperInfo - { - MessageName = className, - MessageType = "IMessage" // IRoamingMessage也是Send方式 - }; - helperInfos.Add(currentHelperInfo); - } + opcodeInfo.Code = OpCode.Create(protocolOpCodeType, + protocolOpCode.RoamingMessage, protocolOpCode.ARoamingMessage++); break; } case "IRoamingRequest": @@ -614,19 +539,8 @@ public sealed class ProtocolExporter // { // throw new NotSupportedException("Under Inner, /// does not support the IRoamingRequest!"); // } - opcodeInfo.Code = OpCode.Create(protocolOpCodeType, protocolOpCode.RoamingRequest, protocolOpCode.ARoamingRequest++); - - // 收集客户端Helper信息 - if (opCodeType == NetworkProtocolOpCodeType.Outer) - { - currentHelperInfo = new MessageHelperInfo - { - MessageName = className, - MessageType = "IRequest", - ResponseType = savedResponseType - }; - helperInfos.Add(currentHelperInfo); - } + opcodeInfo.Code = OpCode.Create(protocolOpCodeType, + protocolOpCode.RoamingRequest, protocolOpCode.ARoamingRequest++); break; } case "IRoamingResponse": @@ -635,11 +549,13 @@ public sealed class ProtocolExporter // { // throw new NotSupportedException("Under Inner, /// does not support the IRoamingResponse!"); // } - opcodeInfo.Code = OpCode.Create(protocolOpCodeType, protocolOpCode.RoamingResponse, protocolOpCode.ARoamingResponse++); + opcodeInfo.Code = OpCode.Create(protocolOpCodeType, + protocolOpCode.RoamingResponse, protocolOpCode.ARoamingResponse++); if (!string.IsNullOrEmpty(protocolMember)) { errorCodeStr.AppendLine($"\t\t[{protocolMember}(ErrorCodeKeyIndex)]"); } + errorCodeStr.AppendLine("\t\tpublic uint ErrorCode { get; set; }"); disposeStr.AppendLine($"\t\t\tErrorCode = default;"); break; @@ -648,31 +564,41 @@ public sealed class ProtocolExporter { if (opCodeType == NetworkProtocolOpCodeType.Outer) { - throw new NotSupportedException("Under Inner, /// does not support the ICustomRouteMessage!"); + throw new NotSupportedException( + "Under Inner, /// does not support the ICustomRouteMessage!"); } - opcodeInfo.Code = OpCode.Create(protocolOpCodeType, protocolOpCode.RouteMessage, protocolOpCode.ARouteMessage++); + + opcodeInfo.Code = OpCode.Create(protocolOpCodeType, + protocolOpCode.RouteMessage, protocolOpCode.ARouteMessage++); break; } case "IRouteRequest": { if (opCodeType == NetworkProtocolOpCodeType.Outer) { - throw new NotSupportedException("Under Inner, /// does not support the ICustomRouteMessage!"); + throw new NotSupportedException( + "Under Inner, /// does not support the ICustomRouteMessage!"); } - opcodeInfo.Code = OpCode.Create(protocolOpCodeType, protocolOpCode.RouteRequest, protocolOpCode.ARouteRequest++); + + opcodeInfo.Code = OpCode.Create(protocolOpCodeType, + protocolOpCode.RouteRequest, protocolOpCode.ARouteRequest++); break; } case "IRouteResponse": { if (opCodeType == NetworkProtocolOpCodeType.Outer) { - throw new NotSupportedException("Under Inner, /// does not support the ICustomRouteMessage!"); + throw new NotSupportedException( + "Under Inner, /// does not support the ICustomRouteMessage!"); } - opcodeInfo.Code = OpCode.Create(protocolOpCodeType, protocolOpCode.RouteResponse, protocolOpCode.ARouteResponse++); + + opcodeInfo.Code = OpCode.Create(protocolOpCodeType, + protocolOpCode.RouteResponse, protocolOpCode.ARouteResponse++); if (!string.IsNullOrEmpty(protocolMember)) { errorCodeStr.AppendLine($"\t\t[{protocolMember}(ErrorCodeKeyIndex)]"); } + errorCodeStr.AppendLine("\t\tpublic uint ErrorCode { get; set; }"); disposeStr.AppendLine($"\t\t\tErrorCode = default;"); break; @@ -694,7 +620,6 @@ public sealed class ProtocolExporter case "}": { isMsgHead = false; - currentHelperInfo = null; // 清空当前Helper信息 errorCodeStr = errorCodeStr.Replace("ErrorCodeKeyIndex", keyIndex.ToString()); messageStr = messageStr.Replace("<<<>>", disposeStr.ToString()); messageStr.Append(errorCodeStr); @@ -718,35 +643,28 @@ public sealed class ProtocolExporter if (currentLine.StartsWith("//")) { - messageStr.AppendFormat("\t\t///\r\n" + "\t\t/// {0}\r\n" + "\t\t///\r\n", currentLine.TrimStart('/', '/')); + messageStr.AppendFormat("\t\t///\r\n" + "\t\t/// {0}\r\n" + "\t\t///\r\n", + currentLine.TrimStart('/', '/')); continue; } - if (currentLine.StartsWith("repeatedArray")) + if (currentLine.StartsWith("repeated")) { - Repeated(messageStr, disposeStr, currentLine, protocolMember, ref keyIndex, currentHelperInfo, "repeatedArray", className, currentFilePath, currentFieldNames, currentFieldNumbers); - } - else if (currentLine.StartsWith("repeatedList")) - { - Repeated(messageStr, disposeStr, currentLine, protocolMember, ref keyIndex, currentHelperInfo, "repeatedList", className, currentFilePath, currentFieldNames, currentFieldNumbers); - } - else if (currentLine.StartsWith("repeated")) - { - Repeated(messageStr, disposeStr, currentLine, protocolMember, ref keyIndex, currentHelperInfo, "repeated", className, currentFilePath, currentFieldNames, currentFieldNumbers); + Repeated(messageStr, disposeStr, currentLine, protocolMember, ref keyIndex); } else { - Members(messageStr, disposeStr, currentLine, protocolMember, ref keyIndex, currentHelperInfo, className, currentFilePath, currentFieldNames, currentFieldNumbers); + Members(messageStr, disposeStr, currentLine, protocolMember, ref keyIndex); } } - + var namespaceBuilder = new StringBuilder(); - + foreach (var @namespace in usingNamespace) { namespaceBuilder.Append($"using {@namespace};\n"); } - + var csName = $"{Path.GetFileNameWithoutExtension(filePath)}.cs"; foreach (var (directory, template) in saveDirectory) { @@ -755,25 +673,12 @@ public sealed class ProtocolExporter content = content.Replace("(UsingNamespace)", namespaceBuilder.ToString()); await File.WriteAllTextAsync(csFile, content); } + file.Clear(); } #endregion - #region GenerateNetworkProtocolHelper - - // 为客户端生成NetworkProtocolHelper - if (opCodeType == NetworkProtocolOpCodeType.Outer && - ExporterAges.Instance.ExportPlatform.HasFlag(ExportPlatform.Client) && - helperInfos.Count > 0) - { - var helperContent = GenerateNetworkProtocolHelperFile(helperInfos); - var helperFilePath = Path.Combine(_networkProtocolClientDirectory, "NetworkProtocolHelper.cs"); - await File.WriteAllTextAsync(helperFilePath, helperContent); - } - - #endregion - #region GenerateOpCode file.Clear(); @@ -786,7 +691,7 @@ public sealed class ProtocolExporter { file.AppendLine($"\t\t public const uint {opcode.Name} = {opcode.Code};"); } - + _opcodes.Clear(); file.AppendLine("\t}"); file.AppendLine("}"); @@ -796,10 +701,12 @@ public sealed class ProtocolExporter var csFile = Path.Combine(directory, $"{opCodeName}.cs"); await File.WriteAllTextAsync(csFile, file.ToString()); } + #endregion } - - private void Repeated(StringBuilder file, StringBuilder disposeStr, string newline, string protocolMember, ref int keyIndex, MessageHelperInfo currentHelperInfo, string repeatedType, string messageName, string filePath, HashSet fieldNames, HashSet fieldNumbers) + + private void Repeated(StringBuilder file, StringBuilder disposeStr, string newline, string protocolMember, + ref int keyIndex) { try { @@ -808,97 +715,21 @@ public sealed class ProtocolExporter var property = newline.Split(SplitChars, StringSplitOptions.RemoveEmptyEntries); var type = property[1]; var name = property[2]; - var fieldNumber = int.Parse(property[4]); + // var memberIndex = int.Parse(property[4]); type = ConvertType(type); - // 检测字段名重复 - if (!fieldNames.Add(name)) - { - var errorMsg = $"协议格式错误!\n文件: {filePath}\n消息: {messageName}\n字段名重复: {name}"; - Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine(errorMsg); - Console.ResetColor(); - throw new ProtocolFormatException(errorMsg); - } - - // 检测字段编号重复 - if (!fieldNumbers.Add(fieldNumber)) - { - var errorMsg = $"协议格式错误!\n文件: {filePath}\n消息: {messageName}\n字段编号重复: {fieldNumber} (字段: {name})"; - Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine(errorMsg); - Console.ResetColor(); - throw new ProtocolFormatException(errorMsg); - } - file.AppendLine($"\t\t[{protocolMember}({keyIndex++})]"); - - switch (repeatedType) - { - case "repeated": - // public List List = new List(); - file.AppendLine($"\t\tpublic List<{type}> {name} = new List<{type}>();"); - disposeStr.AppendLine($"\t\t\t{name}.Clear();"); - - // 收集字段信息到Helper - if (currentHelperInfo != null) - { - currentHelperInfo.Fields.Add(new MessageFieldInfo - { - FieldName = name, - FieldType = $"List<{type}>" - }); - } - break; - - case "repeatedArray": - // public string[] List; - file.AppendLine($"\t\tpublic {type}[] {name};"); - disposeStr.AppendLine($"\t\t\t{name} = default;"); - - // 收集字段信息到Helper - if (currentHelperInfo != null) - { - currentHelperInfo.Fields.Add(new MessageFieldInfo - { - FieldName = name, - FieldType = $"{type}[]" - }); - } - break; - - case "repeatedList": - // public List List; - file.AppendLine($"\t\tpublic List<{type}> {name};"); - disposeStr.AppendLine($"\t\t\t{name} = default;"); - - // 收集字段信息到Helper - if (currentHelperInfo != null) - { - currentHelperInfo.Fields.Add(new MessageFieldInfo - { - FieldName = name, - FieldType = $"List<{type}>" - }); - } - break; - } - } - catch (ProtocolFormatException) - { - // 格式错误已经打印过了,直接重新抛出 - throw; + file.AppendLine($"\t\tpublic List<{type}> {name} = new List<{type}>();"); + disposeStr.AppendLine($"\t\t\t{name}.Clear();"); } catch (Exception e) { - Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine($"解析字段时出错: {newline}\n错误: {e.Message}"); - Console.ResetColor(); - throw; + Log.Error($"{newline}\n {e}"); } } - private void Members(StringBuilder file, StringBuilder disposeStr, string currentLine, string protocolMember, ref int keyIndex, MessageHelperInfo currentHelperInfo, string messageName, string filePath, HashSet fieldNames, HashSet fieldNumbers) + private void Members(StringBuilder file, StringBuilder disposeStr, string currentLine, string protocolMember, + ref int keyIndex) { try { @@ -907,56 +738,19 @@ public sealed class ProtocolExporter var property = currentLine.Split(SplitChars, StringSplitOptions.RemoveEmptyEntries); var type = property[0]; var name = property[1]; - var fieldNumber = int.Parse(property[3]); + // var memberIndex = int.Parse(property[3]); var typeCs = ConvertType(type); - - // 检测字段名重复 - if (!fieldNames.Add(name)) - { - var errorMsg = $"协议格式错误!\n文件: {filePath}\n消息: {messageName}\n字段名重复: {name}"; - Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine(errorMsg); - Console.ResetColor(); - throw new ProtocolFormatException(errorMsg); - } - - // 检测字段编号重复 - if (!fieldNumbers.Add(fieldNumber)) - { - var errorMsg = $"协议格式错误!\n文件: {filePath}\n消息: {messageName}\n字段编号重复: {fieldNumber} (字段: {name})"; - Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine(errorMsg); - Console.ResetColor(); - throw new ProtocolFormatException(errorMsg); - } if (protocolMember != null) { file.AppendLine($"\t\t[{protocolMember}({keyIndex++})]"); } + file.AppendLine($"\t\tpublic {typeCs} {name} {{ get; set; }}"); disposeStr.AppendLine($"\t\t\t{name} = default;"); - - // 收集字段信息到Helper - if (currentHelperInfo != null) - { - currentHelperInfo.Fields.Add(new MessageFieldInfo - { - FieldName = name, - FieldType = typeCs - }); - } - } - catch (ProtocolFormatException) - { - // 格式错误已经打印过了,直接重新抛出 - throw; } catch (Exception e) { - Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine($"解析字段时出错: {currentLine}\n错误: {e.Message}"); - Console.ResetColor(); - throw; + Log.Error($"{currentLine}\n {e}"); } } @@ -1029,9 +823,10 @@ public sealed class ProtocolExporter { roamingTypeFileSb.AppendLine($"\t\t\t\tyield return {roamingType};"); } + roamingTypeFileSb.AppendLine("\t\t\t}\n\t\t}"); } - + roamingTypeFileSb.AppendLine("\t}\n}"); var file = roamingTypeFileSb.ToString(); @@ -1046,7 +841,7 @@ public sealed class ProtocolExporter await File.WriteAllTextAsync($"{_networkProtocolClientDirectory}RoamingType.cs", file); } } - + private async Task RouteType() { var routeTypeFile = $"{_networkProtocolDirectory}RouteType.Config"; @@ -1060,6 +855,7 @@ public sealed class ProtocolExporter { protoFileText = await File.ReadAllTextAsync(routeTypeFile); } + var routeTypeFileSb = new StringBuilder(); routeTypeFileSb.AppendLine("namespace Fantasy\n{"); routeTypeFileSb.AppendLine("\t// Route协议定义(需要定义1000以上、因为1000以内的框架预留)\t"); @@ -1101,10 +897,16 @@ public sealed class ProtocolExporter await File.WriteAllTextAsync($"{_networkProtocolClientDirectory}RouteType.cs", file); } } - + + private void LoadTemplate() { - string[] lines = NetworkProtocolTemplate.Template.Split(["\r\n", "\n"], StringSplitOptions.None); + // 获取当前运行根目录(即应用程序的工作目录) + string currentDirectory = Directory.GetCurrentDirectory(); + string filePath = Path.Combine(currentDirectory, "Template.txt"); + + // 读取文件所有行 + string[] lines = File.ReadAllLines(filePath); StringBuilder serverSb = new StringBuilder(); StringBuilder clientSb = new StringBuilder(); @@ -1119,12 +921,14 @@ public sealed class ProtocolExporter flag = 1; continue; } - else if (trim.StartsWith("#else")) + + if (trim.StartsWith("#else")) { flag = 2; continue; } - else if (trim.StartsWith($"#endif")) + + if (trim.StartsWith($"#endif")) { flag = 0; continue; @@ -1154,109 +958,4 @@ public sealed class ProtocolExporter _serverTemplate = serverSb.Replace("(NetworkProtocol)", "ProtoBuf").ToString(); _clientTemplate = clientSb.Replace("(NetworkProtocol)", "ProtoBuf").ToString(); } - - private string GenerateNetworkProtocolHelperFile(List helperInfos) - { - var helperSb = new StringBuilder(); - - // 添加文件头和命名空间 - helperSb.AppendLine("using System.Runtime.CompilerServices;"); - helperSb.AppendLine("using Fantasy;"); - helperSb.AppendLine("using Fantasy.Async;"); - helperSb.AppendLine("using Fantasy.Network;"); - helperSb.AppendLine("using System.Collections.Generic;"); - helperSb.AppendLine("#pragma warning disable CS8618"); - helperSb.AppendLine(); - helperSb.AppendLine("namespace Fantasy"); - helperSb.AppendLine("{"); - helperSb.AppendLine("\tpublic static class NetworkProtocolHelper"); - helperSb.AppendLine("\t{"); - - foreach (var info in helperInfos) - { - if (info.MessageType == "IMessage") - { - // 版本1: 生成接受消息对象的 Send 方法 - helperSb.AppendLine($"\t\t[MethodImpl(MethodImplOptions.AggressiveInlining)]"); - helperSb.AppendLine($"\t\tpublic static void {info.MessageName}(this Session session, {info.MessageName} message)"); - helperSb.AppendLine("\t\t{"); - helperSb.AppendLine("\t\t\tsession.Send(message);"); - helperSb.AppendLine("\t\t}"); - helperSb.AppendLine(); - - // 版本2: 生成接受属性参数的 Send 方法 - if (info.Fields.Count > 0) - { - var parameters = string.Join(", ", info.Fields.Select(f => $"{f.FieldType} {char.ToLower(f.FieldName[0])}{f.FieldName.Substring(1)}")); - helperSb.AppendLine($"\t\t[MethodImpl(MethodImplOptions.AggressiveInlining)]"); - helperSb.AppendLine($"\t\tpublic static void {info.MessageName}(this Session session, {parameters})"); - helperSb.AppendLine("\t\t{"); - helperSb.AppendLine($"\t\t\tusing var message = Fantasy.{info.MessageName}.Create(session.Scene);"); - foreach (var field in info.Fields) - { - var paramName = $"{char.ToLower(field.FieldName[0])}{field.FieldName.Substring(1)}"; - helperSb.AppendLine($"\t\t\tmessage.{field.FieldName} = {paramName};"); - } - helperSb.AppendLine("\t\t\tsession.Send(message);"); - helperSb.AppendLine("\t\t}"); - helperSb.AppendLine(); - } - else - { - // 没有字段的消息,生成无参数版本 - helperSb.AppendLine($"\t\t[MethodImpl(MethodImplOptions.AggressiveInlining)]"); - helperSb.AppendLine($"\t\tpublic static void {info.MessageName}(this Session session)"); - helperSb.AppendLine("\t\t{"); - helperSb.AppendLine($"\t\t\tusing var message = Fantasy.{info.MessageName}.Create(session.Scene);"); - helperSb.AppendLine("\t\t\tsession.Send(message);"); - helperSb.AppendLine("\t\t}"); - helperSb.AppendLine(); - } - } - else if (info.MessageType == "IRequest") - { - // 版本1: 生成接受请求对象的 Call 方法 - helperSb.AppendLine($"\t\t[MethodImpl(MethodImplOptions.AggressiveInlining)]"); - helperSb.AppendLine($"\t\tpublic static async FTask<{info.ResponseType}> {info.MessageName}(this Session session, {info.MessageName} request)"); - helperSb.AppendLine("\t\t{"); - helperSb.AppendLine($"\t\t\treturn ({info.ResponseType})await session.Call(request);"); - helperSb.AppendLine("\t\t}"); - helperSb.AppendLine(); - - // 版本2: 生成接受属性参数的 Call 方法 - if (info.Fields.Count > 0) - { - var parameters = string.Join(", ", info.Fields.Select(f => $"{f.FieldType} {char.ToLower(f.FieldName[0])}{f.FieldName.Substring(1)}")); - helperSb.AppendLine($"\t\t[MethodImpl(MethodImplOptions.AggressiveInlining)]"); - helperSb.AppendLine($"\t\tpublic static async FTask<{info.ResponseType}> {info.MessageName}(this Session session, {parameters})"); - helperSb.AppendLine("\t\t{"); - helperSb.AppendLine($"\t\t\tusing var request = Fantasy.{info.MessageName}.Create(session.Scene);"); - foreach (var field in info.Fields) - { - var paramName = $"{char.ToLower(field.FieldName[0])}{field.FieldName.Substring(1)}"; - helperSb.AppendLine($"\t\t\trequest.{field.FieldName} = {paramName};"); - } - helperSb.AppendLine($"\t\t\treturn ({info.ResponseType})await session.Call(request);"); - helperSb.AppendLine("\t\t}"); - helperSb.AppendLine(); - } - else - { - // 没有字段的请求,生成无参数版本 - helperSb.AppendLine($"\t\t[MethodImpl(MethodImplOptions.AggressiveInlining)]"); - helperSb.AppendLine($"\t\tpublic static async FTask<{info.ResponseType}> {info.MessageName}(this Session session)"); - helperSb.AppendLine("\t\t{"); - helperSb.AppendLine($"\t\t\tusing var request = Fantasy.{info.MessageName}.Create(session.Scene);"); - helperSb.AppendLine($"\t\t\treturn ({info.ResponseType})await session.Call(request);"); - helperSb.AppendLine("\t\t}"); - helperSb.AppendLine(); - } - } - } - - helperSb.AppendLine("\t}"); - helperSb.AppendLine("}"); - - return helperSb.ToString(); - } } \ No newline at end of file diff --git a/Tools/SourceCode/Fantasy.Tools.NetworkProtocol/ExporterSettings.json b/Tools/SourceCode/Fantasy.Tools.NetworkProtocol/ExporterSettings.json index f093812..809f350 100644 --- a/Tools/SourceCode/Fantasy.Tools.NetworkProtocol/ExporterSettings.json +++ b/Tools/SourceCode/Fantasy.Tools.NetworkProtocol/ExporterSettings.json @@ -1,15 +1,15 @@ { "Export": { "NetworkProtocolDirectory": { - "Value": "../../../Examples/Config/NetworkProtocol/", + "Value": "../../../Config/NetworkProtocol/", "Comment": "ProtoBuf文件所在的文件夹位置" }, "NetworkProtocolServerDirectory": { - "Value": "../../../Examples/Server/Entity/Generate/NetworkProtocol/", + "Value": "../../../Entity/Generate/NetworkProtocol/", "Comment": "ProtoBuf生成到服务端的文件夹位置" }, "NetworkProtocolClientDirectory": { - "Value": "../../../Examples/Client/Unity/Assets/Scripts/Hotfix/Generate/NetworkProtocol/", + "Value": "../../../../Fishing2/Assets/Scripts/Generate/NetworkProtocol/", "Comment": "ProtoBuf生成到客户端的文件夹位置" }, "Serializes": { diff --git a/Tools/SourceCode/Fantasy.Tools.NetworkProtocol/Fantasy.Tools.NetworkProtocol.csproj b/Tools/SourceCode/Fantasy.Tools.NetworkProtocol/Fantasy.Tools.NetworkProtocol.csproj index 2fac91f..01b17d9 100644 --- a/Tools/SourceCode/Fantasy.Tools.NetworkProtocol/Fantasy.Tools.NetworkProtocol.csproj +++ b/Tools/SourceCode/Fantasy.Tools.NetworkProtocol/Fantasy.Tools.NetworkProtocol.csproj @@ -32,16 +32,16 @@ - + Core\FileHelper.cs - + Core\HashCodeHelper.cs - + Core\JsonHelper.cs - + ProtocalExporter\OpCode.cs @@ -53,15 +53,15 @@ Always - - Always - Always Always + + Always + diff --git a/Tools/SourceCode/Fantasy.Tools.NetworkProtocol/NetworkProtocolTemplate.README.md b/Tools/SourceCode/Fantasy.Tools.NetworkProtocol/NetworkProtocolTemplate.README.md deleted file mode 100644 index 7428e22..0000000 --- a/Tools/SourceCode/Fantasy.Tools.NetworkProtocol/NetworkProtocolTemplate.README.md +++ /dev/null @@ -1,48 +0,0 @@ -# NetworkProtocolTemplate.txt 使用说明 - -## 概述 -这个模板文件用于生成网络协议的 C# 代码文件。您可以根据需要自定义模板内容。 - -## 占位符说明 - -模板中包含以下占位符,会在代码生成时被替换: - -### `(UsingNamespace)` -- 用于插入自定义命名空间的 using 语句 -- 例如:当使用自定义序列化器时,会在此处插入相关的 using 语句 - -### `(Content)` -- 用于插入生成的协议类代码 -- 所有 message 定义都会被转换为 C# 类并插入到此处 - -## 模板结构 - -### 服务端部分(#if SERVER) -包含服务端特有的引用和配置: -- `using MongoDB.Bson.Serialization.Attributes` - 用于 MongoDB 持久化 -- 额外的编译器指令和 ReSharper 配置 - -### 客户端部分(#else) -包含客户端需要的基本引用: -- 较少的 using 语句 -- 简化的警告抑制 - -## 自定义方法 - -如果需要修改生成的代码格式,可以: -1. 修改 using 语句部分 -2. 添加或删除编译器指令 -3. 修改命名空间结构 -4. 添加全局特性或注释 - -## 注意事项 - -⚠️ **不要删除占位符**:`(UsingNamespace)` 和 `(Content)` 是必需的,删除会导致代码生成失败 - -⚠️ **保持条件编译**:`#if SERVER` / `#else` / `#endif` 结构用于区分服务端和客户端代码 - -⚠️ **编码格式**:文件应使用 UTF-8 编码 - -## 文件位置 - -编译后,模板文件会被复制到输出目录,确保与可执行文件在同一位置。 diff --git a/Tools/SourceCode/Fantasy.Tools.NetworkProtocol/Program.cs b/Tools/SourceCode/Fantasy.Tools.NetworkProtocol/Program.cs index cad02cb..7f83f0c 100644 --- a/Tools/SourceCode/Fantasy.Tools.NetworkProtocol/Program.cs +++ b/Tools/SourceCode/Fantasy.Tools.NetworkProtocol/Program.cs @@ -16,15 +16,9 @@ try // 运行导出协议的代码 new ProtocolExporter().Run(); } -catch (Fantasy.Tools.ProtocalExporter.ProtocolFormatException) -{ - // 协议格式错误已经打印过详细信息,这里不再重复打印 -} catch (Exception e) { - Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine($"程序执行出错: {e.Message}"); - Console.ResetColor(); + Log.Error(e); } finally { diff --git a/Tools/NetworkProtocol/NetworkProtocolTemplate.txt b/Tools/SourceCode/Fantasy.Tools.NetworkProtocol/Template.txt similarity index 93% rename from Tools/NetworkProtocol/NetworkProtocolTemplate.txt rename to Tools/SourceCode/Fantasy.Tools.NetworkProtocol/Template.txt index 37c9df6..6eb76c5 100644 --- a/Tools/NetworkProtocol/NetworkProtocolTemplate.txt +++ b/Tools/SourceCode/Fantasy.Tools.NetworkProtocol/Template.txt @@ -1,7 +1,6 @@ #if SERVER using ProtoBuf; (UsingNamespace) -using System; using System.Collections.Generic; using MongoDB.Bson.Serialization.Attributes; using Fantasy; @@ -17,11 +16,10 @@ using Fantasy.Serialize; #pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. #pragma warning disable CS8618 -namespace Fantasy -{ +namespace NBC +{ #else using ProtoBuf; -using System; (UsingNamespace) using System.Collections.Generic; using Fantasy; @@ -32,4 +30,4 @@ using Fantasy.Serialize; namespace Fantasy { #endif -(Content)} +(Content)} \ No newline at end of file diff --git a/Tools/SourceCode/Fantasy.Tools.SourceCode.sln.DotSettings.user b/Tools/SourceCode/Fantasy.Tools.SourceCode.sln.DotSettings.user index 35b3967..a2fe3b5 100644 --- a/Tools/SourceCode/Fantasy.Tools.SourceCode.sln.DotSettings.user +++ b/Tools/SourceCode/Fantasy.Tools.SourceCode.sln.DotSettings.user @@ -10,6 +10,7 @@ ForceIncluded ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded