饭太稀
This commit is contained in:
31
Fantasy/Fantays.Console/Fantasy.Console.csproj
Normal file
31
Fantasy/Fantays.Console/Fantasy.Console.csproj
Normal file
@@ -0,0 +1,31 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
|
||||
<LangVersion>default</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<DefineConstants>TRACE;FANTASY_CONSOLE</DefineConstants>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||
<DefineConstants>TRACE;FANTASY_CONSOLE</DefineConstants>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CommandLineParser" Version="2.9.1" />
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App"/>
|
||||
<PackageReference Include="MongoDB.Bson" Version="2.29.0" />
|
||||
<PackageReference Include="MongoDB.Driver" Version="2.29.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="protobuf-net" Version="3.2.30" />
|
||||
<PackageReference Include="System.IO.Pipelines" Version="8.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// AssemblyInfo提供有关程序集和类型的信息
|
||||
/// </summary>
|
||||
public sealed class AssemblyInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// 唯一标识
|
||||
/// </summary>
|
||||
public readonly long AssemblyIdentity;
|
||||
/// <summary>
|
||||
/// 获取或设置与此程序集相关联的 <see cref="Assembly"/> 实例。
|
||||
/// </summary>
|
||||
public System.Reflection.Assembly Assembly { get; private set; }
|
||||
/// <summary>
|
||||
/// 程序集类型集合,获取一个列表,包含从程序集加载的所有类型。
|
||||
/// </summary>
|
||||
public readonly List<Type> AssemblyTypeList = new List<Type>();
|
||||
/// <summary>
|
||||
/// 程序集类型分组集合,获取一个分组列表,将接口类型映射到实现这些接口的类型。
|
||||
/// </summary>
|
||||
public readonly OneToManyList<Type, Type> AssemblyTypeGroupList = new OneToManyList<Type, Type>();
|
||||
|
||||
/// <summary>
|
||||
/// 初始化 <see cref="AssemblyInfo"/> 类的新实例。
|
||||
/// </summary>
|
||||
/// <param name="assemblyIdentity"></param>
|
||||
public AssemblyInfo(long assemblyIdentity)
|
||||
{
|
||||
AssemblyIdentity = assemblyIdentity;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从指定的程序集加载类型信息并进行分类。
|
||||
/// </summary>
|
||||
/// <param name="assembly">要加载信息的程序集。</param>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重新加载程序集的类型信息。
|
||||
/// </summary>
|
||||
/// <param name="assembly"></param>
|
||||
public void ReLoad(System.Reflection.Assembly assembly)
|
||||
{
|
||||
Unload();
|
||||
Load(assembly);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 卸载程序集的类型信息。
|
||||
/// </summary>
|
||||
public void Unload()
|
||||
{
|
||||
AssemblyTypeList.Clear();
|
||||
AssemblyTypeGroupList.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
285
Fantasy/Fantays.Console/Runtime/Core/Assembly/AssemblySystem.cs
Normal file
285
Fantasy/Fantays.Console/Runtime/Core/Assembly/AssemblySystem.cs
Normal file
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 管理程序集加载和卸载的帮助类。
|
||||
/// </summary>
|
||||
public static class AssemblySystem
|
||||
{
|
||||
#if FANTASY_WEBGL
|
||||
private static readonly List<IAssembly> AssemblySystems = new List<IAssembly>();
|
||||
private static readonly Dictionary<long, AssemblyInfo> AssemblyList = new Dictionary<long, AssemblyInfo>();
|
||||
#else
|
||||
private static readonly ConcurrentQueue<IAssembly> AssemblySystems = new ConcurrentQueue<IAssembly>();
|
||||
private static readonly ConcurrentDictionary<long, AssemblyInfo> AssemblyList = new ConcurrentDictionary<long, AssemblyInfo>();
|
||||
#endif
|
||||
/// <summary>
|
||||
/// 初始化 AssemblySystem。(仅限内部)
|
||||
/// </summary>
|
||||
/// <param name="assemblies"></param>
|
||||
internal static async FTask InnerInitialize(params System.Reflection.Assembly[] assemblies)
|
||||
{
|
||||
await LoadAssembly(typeof(AssemblySystem).Assembly);
|
||||
foreach (var assembly in assemblies)
|
||||
{
|
||||
await LoadAssembly(assembly);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 加载指定的程序集,并触发相应的事件。
|
||||
/// </summary>
|
||||
/// <param name="assembly">要加载的程序集。</param>
|
||||
/// <param name="isCurrentDomain">如果当前Domain中已经存在同名的Assembly,使用Domain中的程序集。</param>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 卸载程序集
|
||||
/// </summary>
|
||||
/// <param name="assembly"></param>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将AssemblySystem接口的object注册到程序集管理中心
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 程序集管理中心卸载注册的Load、ReLoad、UnLoad的接口
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
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
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有已加载程序集中的所有类型。
|
||||
/// </summary>
|
||||
/// <returns>所有已加载程序集中的类型。</returns>
|
||||
public static IEnumerable<Type> ForEach()
|
||||
{
|
||||
foreach (var (_, assemblyInfo) in AssemblyList)
|
||||
{
|
||||
foreach (var type in assemblyInfo.AssemblyTypeList)
|
||||
{
|
||||
yield return type;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定程序集中的所有类型。
|
||||
/// </summary>
|
||||
/// <param name="assemblyIdentity">程序集唯一标识。</param>
|
||||
/// <returns>指定程序集中的类型。</returns>
|
||||
public static IEnumerable<Type> ForEach(long assemblyIdentity)
|
||||
{
|
||||
if (!AssemblyList.TryGetValue(assemblyIdentity, out var assemblyInfo))
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
foreach (var type in assemblyInfo.AssemblyTypeList)
|
||||
{
|
||||
yield return type;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有已加载程序集中实现指定类型的所有类型。
|
||||
/// </summary>
|
||||
/// <param name="findType">要查找的基类或接口类型。</param>
|
||||
/// <returns>所有已加载程序集中实现指定类型的类型。</returns>
|
||||
public static IEnumerable<Type> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定程序集中实现指定类型的所有类型。
|
||||
/// </summary>
|
||||
/// <param name="assemblyIdentity">程序集唯一标识。</param>
|
||||
/// <param name="findType">要查找的基类或接口类型。</param>
|
||||
/// <returns>指定程序集中实现指定类型的类型。</returns>
|
||||
public static IEnumerable<Type> 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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定程序集的实例。
|
||||
/// </summary>
|
||||
/// <param name="assemblyIdentity">程序集名称。</param>
|
||||
/// <returns>指定程序集的实例,如果未加载则返回 null。</returns>
|
||||
public static System.Reflection.Assembly GetAssembly(long assemblyIdentity)
|
||||
{
|
||||
return !AssemblyList.TryGetValue(assemblyIdentity, out var assemblyInfo) ? null : assemblyInfo.Assembly;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前框架注册的Assembly
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static IEnumerable<System.Reflection.Assembly> ForEachAssembly
|
||||
{
|
||||
get
|
||||
{
|
||||
foreach (var (_, assemblyInfo) in AssemblyList)
|
||||
{
|
||||
yield return assemblyInfo.Assembly;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据Assembly的强命名计算唯一标识。
|
||||
/// </summary>
|
||||
/// <param name="assembly"></param>
|
||||
/// <returns></returns>
|
||||
private static long AssemblyIdentity(System.Reflection.Assembly assembly)
|
||||
{
|
||||
return HashCodeHelper.ComputeHash64(assembly.GetName().Name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放资源,卸载所有加载的程序集。
|
||||
/// </summary>
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
27
Fantasy/Fantays.Console/Runtime/Core/Assembly/IAssembly.cs
Normal file
27
Fantasy/Fantays.Console/Runtime/Core/Assembly/IAssembly.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using Fantasy.Async;
|
||||
|
||||
namespace Fantasy.Assembly
|
||||
{
|
||||
/// <summary>
|
||||
/// 实现这个接口、会再程序集首次加载、卸载、重载的时候调用
|
||||
/// </summary>
|
||||
public interface IAssembly : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// 程序集加载时调用
|
||||
/// </summary>
|
||||
/// <param name="assemblyIdentity">程序集标识</param>
|
||||
public FTask Load(long assemblyIdentity);
|
||||
/// <summary>
|
||||
/// 程序集重新加载的时候调用
|
||||
/// </summary>
|
||||
/// <param name="assemblyIdentity">程序集标识</param>
|
||||
public FTask ReLoad(long assemblyIdentity);
|
||||
/// <summary>
|
||||
/// 卸载的时候调用
|
||||
/// </summary>
|
||||
/// <param name="assemblyIdentity">程序集标识</param>
|
||||
public FTask OnUnLoad(long assemblyIdentity);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using Fantasy.Async;
|
||||
using Fantasy.InnerMessage;
|
||||
using Fantasy.Network.Interface;
|
||||
|
||||
#if FANTASY_NET
|
||||
namespace Fantasy.Network.Benchmark.Handler;
|
||||
|
||||
/// <summary>
|
||||
/// BenchmarkRequestHandler
|
||||
/// </summary>
|
||||
public sealed class BenchmarkRequestHandler : MessageRPC<BenchmarkRequest, BenchmarkResponse>
|
||||
{
|
||||
/// <summary>
|
||||
/// Run方法
|
||||
/// </summary>
|
||||
/// <param name="session"></param>
|
||||
/// <param name="request"></param>
|
||||
/// <param name="response"></param>
|
||||
/// <param name="reply"></param>
|
||||
protected override async FTask Run(Session session, BenchmarkRequest request, BenchmarkResponse response, Action reply)
|
||||
{
|
||||
await FTask.CompletedTask;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,20 @@
|
||||
// ReSharper disable CheckNamespace
|
||||
// ReSharper disable InconsistentNaming
|
||||
#if FANTASY_NET
|
||||
namespace Fantasy.DataBase;
|
||||
|
||||
/// <summary>
|
||||
/// 数据库类型
|
||||
/// </summary>
|
||||
public enum DataBaseType
|
||||
{
|
||||
/// <summary>
|
||||
/// 默认
|
||||
/// </summary>
|
||||
None = 0,
|
||||
/// <summary>
|
||||
/// MongoDB
|
||||
/// </summary>
|
||||
MongoDB = 1
|
||||
}
|
||||
#endif
|
||||
210
Fantasy/Fantays.Console/Runtime/Core/DataBase/IDataBase.cs
Normal file
210
Fantasy/Fantays.Console/Runtime/Core/DataBase/IDataBase.cs
Normal file
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 数据库设置助手
|
||||
/// </summary>
|
||||
public static class DataBaseSetting
|
||||
{
|
||||
/// <summary>
|
||||
/// 初始化自定义委托,当设置了这个委托后,就不会自动创建MongoClient,需要自己在委托里创建MongoClient。
|
||||
/// </summary>
|
||||
public static Func<DataBaseCustomConfig, MongoClient>? MongoDBCustomInitialize;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// MongoDB自定义连接参数
|
||||
/// </summary>
|
||||
public sealed class DataBaseCustomConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// 当前Scene
|
||||
/// </summary>
|
||||
public Scene Scene;
|
||||
/// <summary>
|
||||
/// 连接字符串
|
||||
/// </summary>
|
||||
public string ConnectionString;
|
||||
/// <summary>
|
||||
/// 数据库名字
|
||||
/// </summary>
|
||||
public string DBName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 表示用于执行各种数据库操作的数据库接口。
|
||||
/// </summary>
|
||||
public interface IDataBase : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// 获得当前数据的类型
|
||||
/// </summary>
|
||||
public DataBaseType GetDataBaseType { get;}
|
||||
/// <summary>
|
||||
/// 获得对应数据的操作实例
|
||||
/// </summary>
|
||||
/// <returns>如MongoDB就是IMongoDatabase</returns>
|
||||
public object GetDataBaseInstance { get;}
|
||||
/// <summary>
|
||||
/// 初始化数据库连接。
|
||||
/// </summary>
|
||||
IDataBase Initialize(Scene scene, string connectionString, string dbName);
|
||||
/// <summary>
|
||||
/// 在指定的集合中检索类型 <typeparamref name="T"/> 的实体数量。
|
||||
/// </summary>
|
||||
FTask<long> Count<T>(string collection = null) where T : Entity;
|
||||
/// <summary>
|
||||
/// 在指定的集合中检索满足给定筛选条件的类型 <typeparamref name="T"/> 的实体数量。
|
||||
/// </summary>
|
||||
FTask<long> Count<T>(Expression<Func<T, bool>> filter, string collection = null) where T : Entity;
|
||||
/// <summary>
|
||||
/// 检查指定集合中是否存在类型 <typeparamref name="T"/> 的实体。
|
||||
/// </summary>
|
||||
FTask<bool> Exist<T>(string collection = null) where T : Entity;
|
||||
/// <summary>
|
||||
/// 检查指定集合中是否存在满足给定筛选条件的类型 <typeparamref name="T"/> 的实体。
|
||||
/// </summary>
|
||||
FTask<bool> Exist<T>(Expression<Func<T, bool>> filter, string collection = null) where T : Entity;
|
||||
/// <summary>
|
||||
/// 从指定集合中检索指定 ID 的类型 <typeparamref name="T"/> 的实体,不锁定。
|
||||
/// </summary>
|
||||
FTask<T> QueryNotLock<T>(long id, bool isDeserialize = false, string collection = null) where T : Entity;
|
||||
/// <summary>
|
||||
/// 从指定集合中检索指定 ID 的类型 <typeparamref name="T"/> 的实体。
|
||||
/// </summary>
|
||||
FTask<T> Query<T>(long id, bool isDeserialize = false, string collection = null) where T : Entity;
|
||||
/// <summary>
|
||||
/// 按页查询满足给定筛选条件的类型 <typeparamref name="T"/> 的实体数量和日期。
|
||||
/// </summary>
|
||||
FTask<(int count, List<T> dates)> QueryCountAndDatesByPage<T>(Expression<Func<T, bool>> filter, int pageIndex, int pageSize, bool isDeserialize = false, string collection = null) where T : Entity;
|
||||
/// <summary>
|
||||
/// 按页查询满足给定筛选条件的类型 <typeparamref name="T"/> 的实体数量和日期。
|
||||
/// </summary>
|
||||
FTask<(int count, List<T> dates)> QueryCountAndDatesByPage<T>(Expression<Func<T, bool>> filter, int pageIndex, int pageSize, string[] cols, bool isDeserialize = false, string collection = null) where T : Entity;
|
||||
/// <summary>
|
||||
/// 分页查询指定集合中满足给定筛选条件的类型 <typeparamref name="T"/> 的实体列表。
|
||||
/// </summary>
|
||||
FTask<List<T>> QueryByPage<T>(Expression<Func<T, bool>> filter, int pageIndex, int pageSize, bool isDeserialize = false, string collection = null) where T : Entity;
|
||||
/// <summary>
|
||||
/// 分页查询指定集合中满足给定筛选条件的类型 <typeparamref name="T"/> 的实体列表,仅返回指定列的数据。
|
||||
/// </summary>
|
||||
FTask<List<T>> QueryByPage<T>(Expression<Func<T, bool>> filter, int pageIndex, int pageSize, string[] cols, bool isDeserialize = false, string collection = null) where T : Entity;
|
||||
/// <summary>
|
||||
/// 从指定集合中按页查询满足给定筛选条件的类型 <typeparamref name="T"/> 的实体列表,按指定字段排序。
|
||||
/// </summary>
|
||||
FTask<List<T>> QueryByPageOrderBy<T>(Expression<Func<T, bool>> filter, int pageIndex, int pageSize, Expression<Func<T, object>> orderByExpression, bool isAsc = true, bool isDeserialize = false, string collection = null) where T : Entity;
|
||||
/// <summary>
|
||||
/// 检索满足给定筛选条件的类型 <typeparamref name="T"/> 的第一个实体,从指定集合中。
|
||||
/// </summary>
|
||||
FTask<T?> First<T>(Expression<Func<T, bool>> filter, bool isDeserialize = false, string collection = null) where T : Entity;
|
||||
/// <summary>
|
||||
/// 查询指定集合中满足给定 JSON 查询字符串的类型 <typeparamref name="T"/> 的第一个实体,仅返回指定列的数据。
|
||||
/// </summary>
|
||||
FTask<T> First<T>(string json, string[] cols, bool isDeserialize = false, string collection = null) where T : Entity;
|
||||
/// <summary>
|
||||
/// 从指定集合中按页查询满足给定筛选条件的类型 <typeparamref name="T"/> 的实体列表,按指定字段排序。
|
||||
/// </summary>
|
||||
FTask<List<T>> QueryOrderBy<T>(Expression<Func<T, bool>> filter, Expression<Func<T, object>> orderByExpression, bool isAsc = true, bool isDeserialize = false, string collection = null) where T : Entity;
|
||||
/// <summary>
|
||||
/// 从指定集合中按页查询满足给定筛选条件的类型 <typeparamref name="T"/> 的实体列表。
|
||||
/// </summary>
|
||||
FTask<List<T>> Query<T>(Expression<Func<T, bool>> filter, bool isDeserialize = false, string collection = null) where T : Entity;
|
||||
/// <summary>
|
||||
/// 查询指定集合中满足给定筛选条件的类型 <typeparamref name="T"/> 实体列表,仅返回指定字段的数据。
|
||||
/// </summary>
|
||||
FTask<List<T>> Query<T>(Expression<Func<T, bool>> filter, Expression<Func<T, object>>[] cols, bool isDeserialize = false, string collection = null) where T : Entity;
|
||||
/// <summary>
|
||||
/// 查询指定 ID 的多个集合,将结果存储在给定的实体列表中。
|
||||
/// </summary>
|
||||
FTask Query(long id, List<string> collectionNames, List<Entity> result, bool isDeserialize = false);
|
||||
/// <summary>
|
||||
/// 根据给定的 JSON 查询字符串查询指定集合中的类型 <typeparamref name="T"/> 实体列表。
|
||||
/// </summary>
|
||||
FTask<List<T>> QueryJson<T>(string json, bool isDeserialize = false, string collection = null) where T : Entity;
|
||||
/// <summary>
|
||||
/// 根据给定的 JSON 查询字符串查询指定集合中的类型 <typeparamref name="T"/> 实体列表,仅返回指定列的数据。
|
||||
/// </summary>
|
||||
FTask<List<T>> QueryJson<T>(string json, string[] cols, bool isDeserialize = false, string collection = null) where T : Entity;
|
||||
/// <summary>
|
||||
/// 根据给定的 JSON 查询字符串查询指定集合中的类型 <typeparamref name="T"/> 实体列表,通过指定的任务 ID 进行标识。
|
||||
/// </summary>
|
||||
FTask<List<T>> QueryJson<T>(long taskId, string json, bool isDeserialize = false, string collection = null) where T : Entity;
|
||||
/// <summary>
|
||||
/// 查询指定集合中满足给定筛选条件的类型 <typeparamref name="T"/> 实体列表,仅返回指定列的数据。
|
||||
/// </summary>
|
||||
FTask<List<T>> Query<T>(Expression<Func<T, bool>> filter, string[] cols, bool isDeserialize = false, string collection = null) where T : Entity;
|
||||
/// <summary>
|
||||
/// 保存类型 <typeparamref name="T"/> 实体到指定集合中,如果集合不存在将自动创建。
|
||||
/// </summary>
|
||||
FTask Save<T>(T entity, string collection = null) where T : Entity, new();
|
||||
/// <summary>
|
||||
/// 保存一组实体到数据库中,根据实体列表的 ID 进行区分和存储。
|
||||
/// </summary>
|
||||
FTask Save(long id, List<Entity> entities);
|
||||
/// <summary>
|
||||
/// 通过事务会话将类型 <typeparamref name="T"/> 实体保存到指定集合中,如果集合不存在将自动创建。
|
||||
/// </summary>
|
||||
FTask Save<T>(object transactionSession, T entity, string collection = null) where T : Entity;
|
||||
/// <summary>
|
||||
/// 向指定集合中插入一个类型 <typeparamref name="T"/> 实体,如果集合不存在将自动创建。
|
||||
/// </summary>
|
||||
FTask Insert<T>(T entity, string collection = null) where T : Entity, new();
|
||||
/// <summary>
|
||||
/// 批量插入一组类型 <typeparamref name="T"/> 实体到指定集合中,如果集合不存在将自动创建。
|
||||
/// </summary>
|
||||
FTask InsertBatch<T>(IEnumerable<T> list, string collection = null) where T : Entity, new();
|
||||
/// <summary>
|
||||
/// 通过事务会话,批量插入一组类型 <typeparamref name="T"/> 实体到指定集合中,如果集合不存在将自动创建。
|
||||
/// </summary>
|
||||
FTask InsertBatch<T>(object transactionSession, IEnumerable<T> list, string collection = null) where T : Entity, new();
|
||||
/// <summary>
|
||||
/// 通过事务会话,根据指定的 ID 从数据库中删除指定类型 <typeparamref name="T"/> 实体。
|
||||
/// </summary>
|
||||
FTask<long> Remove<T>(object transactionSession, long id, string collection = null) where T : Entity, new();
|
||||
/// <summary>
|
||||
/// 根据指定的 ID 从数据库中删除指定类型 <typeparamref name="T"/> 实体。
|
||||
/// </summary>
|
||||
FTask<long> Remove<T>(long id, string collection = null) where T : Entity, new();
|
||||
/// <summary>
|
||||
/// 通过事务会话,根据给定的筛选条件从数据库中删除指定类型 <typeparamref name="T"/> 实体。
|
||||
/// </summary>
|
||||
FTask<long> Remove<T>(long coroutineLockQueueKey, object transactionSession, Expression<Func<T, bool>> filter, string collection = null) where T : Entity, new();
|
||||
/// <summary>
|
||||
/// 根据给定的筛选条件从数据库中删除指定类型 <typeparamref name="T"/> 实体。
|
||||
/// </summary>
|
||||
FTask<long> Remove<T>(long coroutineLockQueueKey, Expression<Func<T, bool>> filter, string collection = null) where T : Entity, new();
|
||||
/// <summary>
|
||||
/// 根据给定的筛选条件计算指定集合中类型 <typeparamref name="T"/> 实体某个属性的总和。
|
||||
/// </summary>
|
||||
FTask<long> Sum<T>(Expression<Func<T, bool>> filter, Expression<Func<T, object>> sumExpression, string collection = null) where T : Entity;
|
||||
/// <summary>
|
||||
/// 在指定的集合中创建索引,以提高类型 <typeparamref name="T"/> 实体的查询性能。
|
||||
/// </summary>
|
||||
FTask CreateIndex<T>(string collection, params object[] keys) where T : Entity;
|
||||
/// <summary>
|
||||
/// 在默认集合中创建索引,以提高类型 <typeparamref name="T"/> 实体的查询性能。
|
||||
/// </summary>
|
||||
FTask CreateIndex<T>(params object[] keys) where T : Entity;
|
||||
/// <summary>
|
||||
/// 创建指定类型 <typeparamref name="T"/> 的数据库,用于存储实体。
|
||||
/// </summary>
|
||||
FTask CreateDB<T>() where T : Entity;
|
||||
/// <summary>
|
||||
/// 根据指定类型创建数据库,用于存储实体。
|
||||
/// </summary>
|
||||
FTask CreateDB(Type type);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
1081
Fantasy/Fantays.Console/Runtime/Core/DataBase/MongoDataBase.cs
Normal file
1081
Fantasy/Fantays.Console/Runtime/Core/DataBase/MongoDataBase.cs
Normal file
File diff suppressed because it is too large
Load Diff
77
Fantasy/Fantays.Console/Runtime/Core/DataBase/World.cs
Normal file
77
Fantasy/Fantays.Console/Runtime/Core/DataBase/World.cs
Normal file
@@ -0,0 +1,77 @@
|
||||
#pragma warning disable CS8603 // Possible null reference return.
|
||||
#if FANTASY_NET
|
||||
using Fantasy.Platform.Net;
|
||||
|
||||
namespace Fantasy.DataBase
|
||||
{
|
||||
/// <summary>
|
||||
/// 表示一个游戏世界。
|
||||
/// </summary>
|
||||
public sealed class World : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取游戏世界的唯一标识。
|
||||
/// </summary>
|
||||
public byte Id { get; private init; }
|
||||
/// <summary>
|
||||
/// 获取游戏世界的数据库接口。
|
||||
/// </summary>
|
||||
public IDataBase DataBase { get; private init; }
|
||||
/// <summary>
|
||||
/// 获取游戏世界的配置信息。
|
||||
/// </summary>
|
||||
public WorldConfig Config => WorldConfigData.Instance.Get(Id);
|
||||
|
||||
/// <summary>
|
||||
/// 使用指定的配置信息创建一个游戏世界实例。
|
||||
/// </summary>
|
||||
/// <param name="scene"></param>
|
||||
/// <param name="worldConfigId"></param>
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个指定唯一标识的游戏世界实例。
|
||||
/// </summary>
|
||||
/// <param name="scene"></param>
|
||||
/// <param name="id">游戏世界的唯一标识。</param>
|
||||
/// <returns>游戏世界实例。</returns>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放游戏世界资源。
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
DataBase.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -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、当读取完成后用过的环会放在缓存中,不会销毁掉。
|
||||
/// <summary>
|
||||
/// 自增式缓存类,继承自 Stream 和 IDisposable 接口。
|
||||
/// 环形缓存具有自动扩充的特性,但不会收缩,适用于操作不过大的 IO 流。
|
||||
/// </summary>
|
||||
public sealed class CircularBuffer : Stream, IDisposable
|
||||
{
|
||||
private byte[] _lastBuffer;
|
||||
/// <summary>
|
||||
/// 环形缓存块的默认大小
|
||||
/// </summary>
|
||||
public const int ChunkSize = 8192;
|
||||
private readonly Queue<byte[]> _bufferCache = new Queue<byte[]>();
|
||||
private readonly Queue<byte[]> _bufferQueue = new Queue<byte[]>();
|
||||
/// <summary>
|
||||
/// 获取或设置环形缓存的第一个索引位置
|
||||
/// </summary>
|
||||
public int FirstIndex { get; set; }
|
||||
/// <summary>
|
||||
/// 获取或设置环形缓存的最后一个索引位置
|
||||
/// </summary>
|
||||
public int LastIndex { get; set; }
|
||||
/// <summary>
|
||||
/// 获取环形缓存的总长度
|
||||
/// </summary>
|
||||
public override long Length
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_bufferQueue.Count == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (_bufferQueue.Count - 1) * ChunkSize + LastIndex - FirstIndex;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取环形缓存的第一个块
|
||||
/// </summary>
|
||||
public byte[] First
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_bufferQueue.Count == 0)
|
||||
{
|
||||
AddLast();
|
||||
}
|
||||
|
||||
return _bufferQueue.Peek();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取环形缓存的最后一个块
|
||||
/// </summary>
|
||||
public byte[] Last
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_bufferQueue.Count == 0)
|
||||
{
|
||||
AddLast();
|
||||
}
|
||||
|
||||
return _lastBuffer;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 向环形缓存中添加一个新的块
|
||||
/// </summary>
|
||||
public void AddLast()
|
||||
{
|
||||
var buffer = _bufferCache.Count > 0 ? _bufferCache.Dequeue() : new byte[ChunkSize];
|
||||
_bufferQueue.Enqueue(buffer);
|
||||
_lastBuffer = buffer;
|
||||
}
|
||||
/// <summary>
|
||||
/// 从环形缓存中移除第一个块
|
||||
/// </summary>
|
||||
public void RemoveFirst()
|
||||
{
|
||||
_bufferCache.Enqueue(_bufferQueue.Dequeue());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从流中读取指定数量的数据到缓存。
|
||||
/// </summary>
|
||||
/// <param name="stream">源数据流。</param>
|
||||
/// <param name="count">要读取的字节数。</param>
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从缓存中读取指定数量的数据到内存。
|
||||
/// </summary>
|
||||
/// <param name="memory">目标内存。</param>
|
||||
/// <param name="count">要读取的字节数。</param>
|
||||
public void Read(Memory<byte> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从自定义流中读取数据到指定的缓冲区。
|
||||
/// </summary>
|
||||
/// <param name="buffer">目标缓冲区,用于存储读取的数据。</param>
|
||||
/// <param name="offset">目标缓冲区中的起始偏移量。</param>
|
||||
/// <param name="count">要读取的字节数。</param>
|
||||
/// <returns>实际读取的字节数。</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将数据从给定的字节数组写入流中。
|
||||
/// </summary>
|
||||
/// <param name="buffer">包含要写入的数据的字节数组。</param>
|
||||
public void Write(byte[] buffer)
|
||||
{
|
||||
Write(buffer, 0, buffer.Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将数据从给定的流写入流中。
|
||||
/// </summary>
|
||||
/// <param name="stream">包含要写入的数据的流。</param>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将数据从给定的字节数组写入流中。
|
||||
/// </summary>
|
||||
/// <param name="buffer">包含要写入的数据的字节数组。</param>
|
||||
/// <param name="offset">开始写入的缓冲区中的索引。</param>
|
||||
/// <param name="count">要写入的字节数。</param>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个值,指示流是否支持读取操作。
|
||||
/// </summary>
|
||||
public override bool CanRead { get; } = true;
|
||||
/// <summary>
|
||||
/// 获取一个值,指示流是否支持寻找操作。
|
||||
/// </summary>
|
||||
public override bool CanSeek { get; } = false;
|
||||
/// <summary>
|
||||
/// 获取一个值,指示流是否支持写入操作。
|
||||
/// </summary>
|
||||
public override bool CanWrite { get; } = true;
|
||||
/// <summary>
|
||||
/// 获取或设置流中的位置。
|
||||
/// </summary>
|
||||
public override long Position { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 刷新流(在此实现中引发未实现异常)。
|
||||
/// </summary>
|
||||
public override void Flush()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在流中寻找特定位置(在此实现中引发未实现异常)。
|
||||
/// </summary>
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置流的长度(在此实现中引发未实现异常)。
|
||||
/// </summary>
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放 CustomStream 使用的所有资源。
|
||||
/// </summary>
|
||||
public new void Dispose()
|
||||
{
|
||||
_bufferQueue.Clear();
|
||||
_lastBuffer = null;
|
||||
FirstIndex = 0;
|
||||
LastIndex = 0;
|
||||
base.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 并发的一对多列表池,用于维护具有相同键的多个值的关联关系,实现了 <see cref="IDisposable"/> 接口。
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">关键字的类型,不能为空。</typeparam>
|
||||
/// <typeparam name="TValue">值的类型。</typeparam>
|
||||
public class ConcurrentOneToManyListPool<TKey, TValue> : ConcurrentOneToManyList<TKey, TValue>, IDisposable, IPool where TKey : notnull
|
||||
{
|
||||
private bool _isPool;
|
||||
private bool _isDispose;
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个 <see cref="ConcurrentOneToManyListPool{TKey, TValue}"/> 的实例。
|
||||
/// </summary>
|
||||
/// <returns>创建的实例。</returns>
|
||||
public static ConcurrentOneToManyListPool<TKey, TValue> Create()
|
||||
{
|
||||
var a = MultiThreadPool.Rent<ConcurrentOneToManyListPool<TKey, TValue>>();
|
||||
a._isDispose = false;
|
||||
a._isPool = true;
|
||||
return a;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放实例占用的资源。
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_isDispose)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isDispose = true;
|
||||
// 清空实例的数据
|
||||
Clear();
|
||||
// 将实例返回到池中以便重用
|
||||
MultiThreadPool.Return(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsPool()
|
||||
{
|
||||
return _isPool;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <param name="isPool"></param>
|
||||
public void SetIsPool(bool isPool)
|
||||
{
|
||||
_isPool = isPool;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 并发的一对多列表,用于维护具有相同键的多个值的关联关系。
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">关键字的类型,不能为空。</typeparam>
|
||||
/// <typeparam name="TValue">值的类型。</typeparam>
|
||||
public class ConcurrentOneToManyList<TKey, TValue> : ConcurrentDictionary<TKey, List<TValue>> where TKey : notnull
|
||||
{
|
||||
private readonly Queue<List<TValue>> _queue = new Queue<List<TValue>>();
|
||||
private readonly int _recyclingLimit = 120;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化 <see cref="ConcurrentOneToManyList{TKey, TValue}"/> 类的新实例。
|
||||
/// </summary>
|
||||
public ConcurrentOneToManyList()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置最大缓存数量
|
||||
/// </summary>
|
||||
/// <param name="recyclingLimit">
|
||||
/// 1:防止数据量过大、所以超过recyclingLimit的数据还是走GC.
|
||||
/// 2:设置成0不控制数量,全部缓存
|
||||
/// </param>
|
||||
public ConcurrentOneToManyList(int recyclingLimit)
|
||||
{
|
||||
_recyclingLimit = recyclingLimit;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断指定键的列表是否包含指定值。
|
||||
/// </summary>
|
||||
/// <param name="key">要搜索的键。</param>
|
||||
/// <param name="value">要搜索的值。</param>
|
||||
/// <returns>如果列表包含值,则为 true;否则为 false。</returns>
|
||||
public bool Contains(TKey key, TValue value)
|
||||
{
|
||||
TryGetValue(key, out var list);
|
||||
|
||||
return list != null && list.Contains(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 向指定键的列表中添加一个值。
|
||||
/// </summary>
|
||||
/// <param name="key">要添加值的键。</param>
|
||||
/// <param name="value">要添加的值。</param>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定键的列表中的第一个值。
|
||||
/// </summary>
|
||||
/// <param name="key">要获取第一个值的键。</param>
|
||||
/// <returns>指定键的列表中的第一个值,如果不存在则为默认值。</returns>
|
||||
public TValue First(TKey key)
|
||||
{
|
||||
return !TryGetValue(key, out var list) ? default : list.FirstOrDefault();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从指定键的列表中移除一个值。
|
||||
/// </summary>
|
||||
/// <param name="key">要移除值的键。</param>
|
||||
/// <param name="value">要移除的值。</param>
|
||||
public void RemoveValue(TKey key, TValue value)
|
||||
{
|
||||
if (!TryGetValue(key, out var list)) return;
|
||||
|
||||
list.Remove(value);
|
||||
|
||||
if (list.Count == 0) RemoveKey(key);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从字典中移除指定键以及其关联的列表。
|
||||
/// </summary>
|
||||
/// <param name="key">要移除的键。</param>
|
||||
public void RemoveKey(TKey key)
|
||||
{
|
||||
if (!TryRemove(key, out var list)) return;
|
||||
|
||||
Recycle(list);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从队列中获取一个列表,如果队列为空则创建一个新的列表。
|
||||
/// </summary>
|
||||
/// <returns>获取的列表。</returns>
|
||||
private List<TValue> Fetch()
|
||||
{
|
||||
return _queue.Count <= 0 ? new List<TValue>() : _queue.Dequeue();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将一个列表回收到队列中。
|
||||
/// </summary>
|
||||
/// <param name="list">要回收的列表。</param>
|
||||
private void Recycle(List<TValue> list)
|
||||
{
|
||||
list.Clear();
|
||||
|
||||
if (_recyclingLimit != 0 && _queue.Count > _recyclingLimit) return;
|
||||
|
||||
_queue.Enqueue(list);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清空当前类的数据,包括从基类继承的数据以及自定义的数据队列。
|
||||
/// </summary>
|
||||
protected new void Clear()
|
||||
{
|
||||
base.Clear();
|
||||
_queue.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 表示一个并发的一对多队列池,用于维护具有相同键的多个值的关联关系,实现了 <see cref="IDisposable"/> 接口。
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">关键字的类型,不能为空。</typeparam>
|
||||
/// <typeparam name="TValue">值的类型。</typeparam>
|
||||
public class ConcurrentOneToManyQueuePool<TKey, TValue> : ConcurrentOneToManyQueue<TKey, TValue>, IDisposable, IPool where TKey : notnull
|
||||
{
|
||||
private bool _isPool;
|
||||
private bool _isDispose;
|
||||
|
||||
/// <summary>
|
||||
/// 创建并返回一个 <see cref="ConcurrentOneToManyQueuePool{TKey, TValue}"/> 的实例。
|
||||
/// </summary>
|
||||
/// <returns>创建的实例。</returns>
|
||||
public static ConcurrentOneToManyQueuePool<TKey, TValue> Create()
|
||||
{
|
||||
var a = MultiThreadPool.Rent<ConcurrentOneToManyQueuePool<TKey, TValue>>();
|
||||
a._isDispose = false;
|
||||
a._isPool = true;
|
||||
return a;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放当前实例所占用的资源,并将实例返回到对象池中,以便重用。
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_isDispose)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isDispose = true;
|
||||
Clear();
|
||||
// 将实例返回到对象池中,以便重用
|
||||
MultiThreadPool.Return(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsPool()
|
||||
{
|
||||
return _isPool;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <param name="isPool"></param>
|
||||
public void SetIsPool(bool isPool)
|
||||
{
|
||||
_isPool = isPool;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 表示一个并发的一对多队列,用于维护具有相同键的多个值的关联关系。
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">关键字的类型,不能为空。</typeparam>
|
||||
/// <typeparam name="TValue">值的类型。</typeparam>
|
||||
public class ConcurrentOneToManyQueue<TKey, TValue> : ConcurrentDictionary<TKey, Queue<TValue>> where TKey : notnull
|
||||
{
|
||||
private readonly Queue<Queue<TValue>> _queue = new Queue<Queue<TValue>>();
|
||||
private readonly int _recyclingLimit;
|
||||
|
||||
/// <summary>
|
||||
/// 设置最大缓存数量
|
||||
/// </summary>
|
||||
/// <param name="recyclingLimit">
|
||||
/// 1:防止数据量过大、所以超过recyclingLimit的数据还是走GC.
|
||||
/// 2:设置成0不控制数量,全部缓存
|
||||
/// </param>
|
||||
public ConcurrentOneToManyQueue(int recyclingLimit = 0)
|
||||
{
|
||||
_recyclingLimit = recyclingLimit;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断指定键的队列是否包含指定值。
|
||||
/// </summary>
|
||||
/// <param name="key">要搜索的键。</param>
|
||||
/// <param name="value">要搜索的值。</param>
|
||||
/// <returns>如果队列包含值,则为 true;否则为 false。</returns>
|
||||
public bool Contains(TKey key, TValue value)
|
||||
{
|
||||
TryGetValue(key, out var list);
|
||||
|
||||
return list != null && list.Contains(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 向指定键的队列中添加一个值。
|
||||
/// </summary>
|
||||
/// <param name="key">要添加值的键。</param>
|
||||
/// <param name="value">要添加的值。</param>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从指定键的队列中出队并返回一个值。
|
||||
/// </summary>
|
||||
/// <param name="key">要出队的键。</param>
|
||||
/// <returns>出队的值,如果队列为空则为默认值。</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试从指定键的队列中出队一个值。
|
||||
/// </summary>
|
||||
/// <param name="key">要出队的键。</param>
|
||||
/// <param name="value">出队的值,如果队列为空则为默认值。</param>
|
||||
/// <returns>如果成功出队,则为 true;否则为 false。</returns>
|
||||
public bool TryDequeue(TKey key, out TValue value)
|
||||
{
|
||||
value = Dequeue(key);
|
||||
|
||||
return value != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从字典中移除指定键以及其关联的队列。
|
||||
/// </summary>
|
||||
/// <param name="key">要移除的键。</param>
|
||||
public void RemoveKey(TKey key)
|
||||
{
|
||||
if (!TryGetValue(key, out var list)) return;
|
||||
|
||||
TryRemove(key, out _);
|
||||
Recycle(list);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从队列中获取一个新的队列,如果队列为空则创建一个新的队列。
|
||||
/// </summary>
|
||||
/// <returns>获取的队列。</returns>
|
||||
private Queue<TValue> Fetch()
|
||||
{
|
||||
return _queue.Count <= 0 ? new Queue<TValue>() : _queue.Dequeue();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将一个队列回收到队列池中。
|
||||
/// </summary>
|
||||
/// <param name="list">要回收的队列。</param>
|
||||
private void Recycle(Queue<TValue> list)
|
||||
{
|
||||
list.Clear();
|
||||
|
||||
if (_recyclingLimit != 0 && _queue.Count > _recyclingLimit) return;
|
||||
|
||||
_queue.Enqueue(list);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清空当前类的数据,包括从基类继承的键值对字典中的数据以及自定义的队列池。
|
||||
/// </summary>
|
||||
protected new void Clear()
|
||||
{
|
||||
base.Clear();
|
||||
_queue.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,134 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Fantasy.Pool;
|
||||
|
||||
namespace Fantasy.DataStructure.Collection
|
||||
{
|
||||
/// <summary>
|
||||
/// 可释放的哈希集合对象池。
|
||||
/// </summary>
|
||||
/// <typeparam name="T">哈希集合中元素的类型。</typeparam>
|
||||
public sealed class HashSetPool<T> : HashSet<T>, IDisposable, IPool
|
||||
{
|
||||
private bool _isPool;
|
||||
private bool _isDispose;
|
||||
|
||||
/// <summary>
|
||||
/// 释放实例所占用的资源,并将实例返回到对象池中,以便重用。
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_isDispose)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isDispose = true;
|
||||
Clear();
|
||||
#if FANTASY_WEBGL
|
||||
Pool<HashSetPool<T>>.Return(this);
|
||||
#else
|
||||
MultiThreadPool.Return(this);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个 <see cref="HashSetPool{T}"/> 哈希集合池的实例。
|
||||
/// </summary>
|
||||
/// <returns>创建的实例。</returns>
|
||||
public static HashSetPool<T> Create()
|
||||
{
|
||||
#if FANTASY_WEBGL
|
||||
var list = Pool<HashSetPool<T>>.Rent();
|
||||
list._isDispose = false;
|
||||
list._isPool = true;
|
||||
return list;
|
||||
#else
|
||||
var list = MultiThreadPool.Rent<HashSetPool<T>>();
|
||||
list._isDispose = false;
|
||||
list._isPool = true;
|
||||
return list;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsPool()
|
||||
{
|
||||
return _isPool;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <param name="isPool"></param>
|
||||
public void SetIsPool(bool isPool)
|
||||
{
|
||||
_isPool = isPool;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 基本哈希集合对象池,他自持有实际的哈希集合。
|
||||
/// </summary>
|
||||
/// <typeparam name="T">哈希集合中元素的类型。</typeparam>
|
||||
public sealed class HashSetBasePool<T> : IDisposable, IPool
|
||||
{
|
||||
private bool _isPool;
|
||||
|
||||
/// <summary>
|
||||
/// 存储实际的哈希集合
|
||||
/// </summary>
|
||||
public HashSet<T> Set = new HashSet<T>();
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个 <see cref="HashSetBasePool{T}"/> 基本哈希集合对象池的实例。
|
||||
/// </summary>
|
||||
/// <returns>创建的实例。</returns>
|
||||
public static HashSetBasePool<T> Create()
|
||||
{
|
||||
#if FANTASY_WEBGL
|
||||
var hashSetBasePool = Pool<HashSetBasePool<T>>.Rent();
|
||||
hashSetBasePool._isPool = true;
|
||||
return hashSetBasePool;
|
||||
#else
|
||||
var hashSetBasePool = MultiThreadPool.Rent<HashSetBasePool<T>>();
|
||||
hashSetBasePool._isPool = true;
|
||||
return hashSetBasePool;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放实例所占用的资源,并将实例返回到对象池中,以便重用。
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
Set.Clear();
|
||||
#if FANTASY_WEBGL
|
||||
Pool<HashSetBasePool<T>>.Return(this);
|
||||
#else
|
||||
MultiThreadPool.Return(this);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsPool()
|
||||
{
|
||||
return _isPool;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <param name="isPool"></param>
|
||||
public void SetIsPool(bool isPool)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Fantasy.Pool;
|
||||
|
||||
// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
|
||||
|
||||
namespace Fantasy.DataStructure.Collection
|
||||
{
|
||||
/// <summary>
|
||||
/// 可释放的列表(List)对象池。
|
||||
/// </summary>
|
||||
/// <typeparam name="T">列表中元素的类型。</typeparam>
|
||||
public sealed class ListPool<T> : List<T>, IDisposable, IPool
|
||||
{
|
||||
private bool _isPool;
|
||||
private bool _isDispose;
|
||||
|
||||
/// <summary>
|
||||
/// 释放实例所占用的资源,并将实例返回到对象池中,以便重用。
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_isDispose)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isDispose = true;
|
||||
Clear();
|
||||
#if FANTASY_WEBGL
|
||||
Pool<ListPool<T>>.Return(this);
|
||||
#else
|
||||
MultiThreadPool.Return(this);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用指定的元素创建一个 <see cref="ListPool{T}"/> 列表(List)对象池的实例。
|
||||
/// </summary>
|
||||
/// <param name="args">要添加到列表的元素。</param>
|
||||
/// <returns>创建的实例。</returns>
|
||||
public static ListPool<T> Create(params T[] args)
|
||||
{
|
||||
#if FANTASY_WEBGL
|
||||
var list = Pool<ListPool<T>>.Rent();
|
||||
#else
|
||||
var list = MultiThreadPool.Rent<ListPool<T>>();
|
||||
#endif
|
||||
list._isDispose = false;
|
||||
list._isPool = true;
|
||||
|
||||
if (args != null)
|
||||
{
|
||||
list.AddRange(args);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用指定的列表创建一个 <see cref="ListPool{T}"/> 列表(List)对象池的实例。
|
||||
/// </summary>
|
||||
/// <param name="args">要添加到列表的元素列表。</param>
|
||||
/// <returns>创建的实例。</returns>
|
||||
public static ListPool<T> Create(List<T> args)
|
||||
{
|
||||
#if FANTASY_WEBGL
|
||||
var list = Pool<ListPool<T>>.Rent();
|
||||
#else
|
||||
var list = MultiThreadPool.Rent<ListPool<T>>();
|
||||
#endif
|
||||
list._isDispose = false;
|
||||
list._isPool = true;
|
||||
|
||||
if (args != null)
|
||||
{
|
||||
list.AddRange(args);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsPool()
|
||||
{
|
||||
return _isPool;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <param name="isPool"></param>
|
||||
public void SetIsPool(bool isPool)
|
||||
{
|
||||
_isPool = isPool;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 一对多哈希集合(OneToManyHashSet)对象池。
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">键的类型。</typeparam>
|
||||
/// <typeparam name="TValue">值的类型。</typeparam>
|
||||
public class OneToManyHashSetPool<TKey, TValue> : OneToManyHashSet<TKey, TValue>, IDisposable, IPool where TKey : notnull
|
||||
{
|
||||
private bool _isPool;
|
||||
private bool _isDispose;
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个 <see cref="OneToManyHashSetPool{TKey, TValue}"/> 一对多哈希集合(OneToManyHashSet)对象池的实例。
|
||||
/// </summary>
|
||||
/// <returns>创建的实例。</returns>
|
||||
public static OneToManyHashSetPool<TKey, TValue> Create()
|
||||
{
|
||||
#if FANTASY_WEBGL
|
||||
var a = Pool<OneToManyHashSetPool<TKey, TValue>>.Rent();
|
||||
#else
|
||||
var a = MultiThreadPool.Rent<OneToManyHashSetPool<TKey, TValue>>();
|
||||
#endif
|
||||
a._isDispose = false;
|
||||
a._isPool = true;
|
||||
return a;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放实例所占用的资源,并将实例返回到对象池中,以便重用。
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_isDispose)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isDispose = true;
|
||||
Clear();
|
||||
#if FANTASY_WEBGL
|
||||
Pool<OneToManyHashSetPool<TKey, TValue>>.Return(this);
|
||||
#else
|
||||
MultiThreadPool.Return(this);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsPool()
|
||||
{
|
||||
return _isPool;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <param name="isPool"></param>
|
||||
public void SetIsPool(bool isPool)
|
||||
{
|
||||
_isPool = isPool;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 一对多哈希集合(OneToManyHashSet),用于创建和管理键对应多个值的集合。
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">键的类型。</typeparam>
|
||||
/// <typeparam name="TValue">值的类型。</typeparam>
|
||||
public class OneToManyHashSet<TKey, TValue> : Dictionary<TKey, HashSet<TValue>> where TKey : notnull
|
||||
{
|
||||
/// 用于回收和重用的空闲值集合队列。
|
||||
private readonly Queue<HashSet<TValue>> _queue = new Queue<HashSet<TValue>>();
|
||||
/// 设置最大回收限制,用于控制值集合的最大数量。
|
||||
private readonly int _recyclingLimit = 120;
|
||||
/// 一个空的、不包含任何元素的哈希集合,用于在查找失败时返回。
|
||||
private static HashSet<TValue> _empty = new HashSet<TValue>();
|
||||
|
||||
/// <summary>
|
||||
/// 初始化 <see cref="OneToManyHashSet{TKey, TValue}"/> 类的新实例。
|
||||
/// </summary>
|
||||
public OneToManyHashSet() { }
|
||||
|
||||
/// <summary>
|
||||
/// 设置最大缓存数量
|
||||
/// </summary>
|
||||
/// <param name="recyclingLimit">
|
||||
/// 1:防止数据量过大、所以超过recyclingLimit的数据还是走GC.
|
||||
/// 2:设置成0不控制数量,全部缓存
|
||||
/// </param>
|
||||
public OneToManyHashSet(int recyclingLimit)
|
||||
{
|
||||
_recyclingLimit = recyclingLimit;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断指定的键值对是否存在于集合中。
|
||||
/// </summary>
|
||||
/// <param name="key">键。</param>
|
||||
/// <param name="value">值。</param>
|
||||
/// <returns>如果存在则为 true,否则为 false。</returns>
|
||||
public bool Contains(TKey key, TValue value)
|
||||
{
|
||||
TryGetValue(key, out var list);
|
||||
|
||||
return list != null && list.Contains(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加指定的键值对到集合中。
|
||||
/// </summary>
|
||||
/// <param name="key">键。</param>
|
||||
/// <param name="value">值。</param>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从集合中移除指定键对应的值。
|
||||
/// </summary>
|
||||
/// <param name="key">键。</param>
|
||||
/// <param name="value">要移除的值。</param>
|
||||
public void RemoveValue(TKey key, TValue value)
|
||||
{
|
||||
if (!TryGetValue(key, out var list)) return;
|
||||
|
||||
list.Remove(value);
|
||||
|
||||
if (list.Count == 0) RemoveKey(key);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从集合中移除指定键及其对应的值集合。
|
||||
/// </summary>
|
||||
/// <param name="key">键。</param>
|
||||
public void RemoveKey(TKey key)
|
||||
{
|
||||
if (!TryGetValue(key, out var list)) return;
|
||||
|
||||
Remove(key);
|
||||
Recycle(list);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定键对应的值集合,如果不存在则返回一个空的哈希集合。
|
||||
/// </summary>
|
||||
/// <param name="key">键。</param>
|
||||
/// <returns>对应的值集合或空的哈希集合。</returns>
|
||||
public HashSet<TValue> GetValue(TKey key)
|
||||
{
|
||||
if (TryGetValue(key, out HashSet<TValue> value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
return _empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从队列中获取一个空闲的值集合,或者创建一个新的。
|
||||
/// </summary>
|
||||
/// <returns>值集合。</returns>
|
||||
private HashSet<TValue> Fetch()
|
||||
{
|
||||
return _queue.Count <= 0 ? new HashSet<TValue>() : _queue.Dequeue();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 回收值集合到队列中,以便重复利用。
|
||||
/// </summary>
|
||||
/// <param name="list">要回收的值集合。</param>
|
||||
private void Recycle(HashSet<TValue> list)
|
||||
{
|
||||
list.Clear();
|
||||
|
||||
if (_recyclingLimit != 0 && _queue.Count > _recyclingLimit) return;
|
||||
|
||||
_queue.Enqueue(list);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清空集合中的数据并和队列。
|
||||
/// </summary>
|
||||
protected new void Clear()
|
||||
{
|
||||
base.Clear();
|
||||
_queue.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 可回收的、一对多关系的列表池。
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">键的类型。</typeparam>
|
||||
/// <typeparam name="TValue">值的类型。</typeparam>
|
||||
public class OneToManyListPool<TKey, TValue> : OneToManyList<TKey, TValue>, IDisposable, IPool where TKey : notnull
|
||||
{
|
||||
private bool _isPool;
|
||||
private bool _isDispose;
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个 <see cref="OneToManyListPool{TKey, TValue}"/> 一对多关系的列表池的实例。
|
||||
/// </summary>
|
||||
/// <returns>创建的实例。</returns>
|
||||
public static OneToManyListPool<TKey, TValue> Create()
|
||||
{
|
||||
#if FANTASY_WEBGL || FANTASY_EXPORTER
|
||||
var list = Pool<OneToManyListPool<TKey, TValue>>.Rent();
|
||||
#else
|
||||
var list = MultiThreadPool.Rent<OneToManyListPool<TKey, TValue>>();
|
||||
#endif
|
||||
list._isDispose = false;
|
||||
list._isPool = true;
|
||||
return list;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放当前对象所占用的资源,并将对象回收到对象池中。
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_isDispose)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isDispose = true;
|
||||
Clear();
|
||||
#if FANTASY_WEBGL || FANTASY_EXPORTER
|
||||
Pool<OneToManyListPool<TKey, TValue>>.Return(this);
|
||||
#else
|
||||
MultiThreadPool.Return(this);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsPool()
|
||||
{
|
||||
return _isPool;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <param name="isPool"></param>
|
||||
public void SetIsPool(bool isPool)
|
||||
{
|
||||
_isPool = isPool;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 一对多关系的列表字典。
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">键的类型。</typeparam>
|
||||
/// <typeparam name="TValue">值的类型。</typeparam>
|
||||
public class OneToManyList<TKey, TValue> : Dictionary<TKey, List<TValue>> where TKey : notnull
|
||||
{
|
||||
private readonly int _recyclingLimit = 120;
|
||||
private static readonly List<TValue> Empty = new List<TValue>();
|
||||
private readonly Queue<List<TValue>> _queue = new Queue<List<TValue>>();
|
||||
|
||||
/// <summary>
|
||||
/// 初始化一个新的 <see cref="OneToManyList{TKey, TValue}"/> 实例。
|
||||
/// </summary>
|
||||
public OneToManyList() { }
|
||||
|
||||
/// <summary>
|
||||
/// 设置最大缓存数量
|
||||
/// </summary>
|
||||
/// <param name="recyclingLimit">
|
||||
/// 1:防止数据量过大、所以超过recyclingLimit的数据还是走GC.
|
||||
/// 2:设置成0不控制数量,全部缓存
|
||||
/// </param>
|
||||
public OneToManyList(int recyclingLimit)
|
||||
{
|
||||
_recyclingLimit = recyclingLimit;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断给定的键和值是否存在于列表中。
|
||||
/// </summary>
|
||||
/// <param name="key">要搜索的键。</param>
|
||||
/// <param name="value">要搜索的值。</param>
|
||||
/// <returns>如果存在则为 <see langword="true"/>,否则为 <see langword="false"/>。</returns>
|
||||
public bool Contains(TKey key, TValue value)
|
||||
{
|
||||
TryGetValue(key, out var list);
|
||||
|
||||
return list != null && list.Contains(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 向列表中添加指定键和值。
|
||||
/// </summary>
|
||||
/// <param name="key">要添加值的键。</param>
|
||||
/// <param name="value">要添加的值。</param>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定键对应的列表中的第一个值。
|
||||
/// </summary>
|
||||
/// <param name="key">要获取值的键。</param>
|
||||
/// <returns>键对应的列表中的第一个值。</returns>
|
||||
public TValue First(TKey key)
|
||||
{
|
||||
return !TryGetValue(key, out var list) ? default : list.FirstOrDefault();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从列表中移除指定键和值。
|
||||
/// </summary>
|
||||
/// <param name="key">要移除值的键。</param>
|
||||
/// <param name="value">要移除的值。</param>
|
||||
/// <returns>如果成功移除则为 <see langword="true"/>,否则为 <see langword="false"/>。</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从列表中移除指定键及其关联的所有值。
|
||||
/// </summary>
|
||||
/// <param name="key">要移除的键。</param>
|
||||
/// <returns>如果成功移除则为 <see langword="true"/>,否则为 <see langword="false"/>。</returns>
|
||||
public bool RemoveByKey(TKey key)
|
||||
{
|
||||
if (!TryGetValue(key, out var list))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Remove(key);
|
||||
Recycle(list);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定键关联的所有值的列表。
|
||||
/// </summary>
|
||||
/// <param name="key">要获取值的键。</param>
|
||||
/// <returns>键关联的所有值的列表。</returns>
|
||||
public List<TValue> GetValues(TKey key)
|
||||
{
|
||||
if (TryGetValue(key, out List<TValue> list))
|
||||
{
|
||||
return list;
|
||||
}
|
||||
|
||||
return Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清除字典中的所有键值对,并回收相关的值集合。
|
||||
/// </summary>
|
||||
public new void Clear()
|
||||
{
|
||||
foreach (var keyValuePair in this) Recycle(keyValuePair.Value);
|
||||
|
||||
base.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从空闲值集合队列中获取一个值集合,如果队列为空则创建一个新的值集合。
|
||||
/// </summary>
|
||||
/// <returns>从队列中获取的值集合。</returns>
|
||||
private List<TValue> Fetch()
|
||||
{
|
||||
return _queue.Count <= 0 ? new List<TValue>() : _queue.Dequeue();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 回收一个不再使用的值集合到空闲值集合队列中。
|
||||
/// </summary>
|
||||
/// <param name="list">要回收的值集合。</param>
|
||||
private void Recycle(List<TValue> list)
|
||||
{
|
||||
list.Clear();
|
||||
|
||||
if (_recyclingLimit != 0 && _queue.Count > _recyclingLimit) return;
|
||||
|
||||
_queue.Enqueue(list);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Fantasy.Pool;
|
||||
|
||||
#pragma warning disable CS8603
|
||||
|
||||
namespace Fantasy.DataStructure.Collection
|
||||
{
|
||||
/// <summary>
|
||||
/// 支持一对多关系的队列池,用于存储具有相同键的值的队列集合。
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">键的类型。</typeparam>
|
||||
/// <typeparam name="TValue">值的类型。</typeparam>
|
||||
public class OneToManyQueuePool<TKey, TValue> : OneToManyQueue<TKey, TValue>, IDisposable, IPool where TKey : notnull
|
||||
{
|
||||
private bool _isPool;
|
||||
private bool _isDispose;
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个 <see cref="OneToManyQueuePool{TKey, TValue}"/> 一对多关系的队列池的实例。
|
||||
/// </summary>
|
||||
/// <returns>创建的实例。</returns>
|
||||
public static OneToManyQueuePool<TKey, TValue> Create()
|
||||
{
|
||||
#if FANTASY_WEBGL
|
||||
var a = Pool<OneToManyQueuePool<TKey, TValue>>.Rent();
|
||||
#else
|
||||
var a = MultiThreadPool.Rent<OneToManyQueuePool<TKey, TValue>>();
|
||||
#endif
|
||||
a._isDispose = false;
|
||||
a._isPool = true;
|
||||
return a;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放当前实例所占用的资源,并将实例回收到对象池中。
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_isDispose)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isDispose = true;
|
||||
Clear();
|
||||
#if FANTASY_WEBGL
|
||||
Pool<OneToManyQueuePool<TKey, TValue>>.Return(this);
|
||||
#else
|
||||
MultiThreadPool.Return(this);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsPool()
|
||||
{
|
||||
return _isPool;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <param name="isPool"></param>
|
||||
public void SetIsPool(bool isPool)
|
||||
{
|
||||
_isPool = isPool;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 支持一对多关系的队列,用于存储具有相同键的值的队列集合。
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">键的类型。</typeparam>
|
||||
/// <typeparam name="TValue">值的类型。</typeparam>
|
||||
public class OneToManyQueue<TKey, TValue> : Dictionary<TKey, Queue<TValue>> where TKey : notnull
|
||||
{
|
||||
private readonly Queue<Queue<TValue>> _queue = new Queue<Queue<TValue>>();
|
||||
private readonly int _recyclingLimit;
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个 <see cref="OneToManyQueue{TKey, TValue}"/> 一对多关系的队列的实例。设置最大缓存数量
|
||||
/// </summary>
|
||||
/// <param name="recyclingLimit">
|
||||
/// 1:防止数据量过大、所以超过recyclingLimit的数据还是走GC.
|
||||
/// 2:设置成0不控制数量,全部缓存
|
||||
/// </param>
|
||||
public OneToManyQueue(int recyclingLimit = 0)
|
||||
{
|
||||
_recyclingLimit = recyclingLimit;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断指定键的值队列是否包含指定的值。
|
||||
/// </summary>
|
||||
/// <param name="key">要查找的键。</param>
|
||||
/// <param name="value">要查找的值。</param>
|
||||
/// <returns>如果存在,则为 <c>true</c>;否则为 <c>false</c>。</returns>
|
||||
public bool Contains(TKey key, TValue value)
|
||||
{
|
||||
TryGetValue(key, out var list);
|
||||
|
||||
return list != null && list.Contains(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将指定的值添加到指定键的值队列中。
|
||||
/// </summary>
|
||||
/// <param name="key">要添加值的键。</param>
|
||||
/// <param name="value">要添加的值。</param>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从指定键的值队列中出队一个值。
|
||||
/// </summary>
|
||||
/// <param name="key">要出队的键。</param>
|
||||
/// <returns>出队的值。</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试从指定键的值队列中出队一个值。
|
||||
/// </summary>
|
||||
/// <param name="key">要出队的键。</param>
|
||||
/// <param name="value">出队的值。</param>
|
||||
/// <returns>如果成功出队,则为 <c>true</c>;否则为 <c>false</c>。</returns>
|
||||
public bool TryDequeue(TKey key, out TValue value)
|
||||
{
|
||||
value = Dequeue(key);
|
||||
|
||||
return value != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从字典中移除指定键及其对应的值队列。
|
||||
/// </summary>
|
||||
/// <param name="key">要移除的键。</param>
|
||||
public void RemoveKey(TKey key)
|
||||
{
|
||||
if (!TryGetValue(key, out var list)) return;
|
||||
|
||||
Remove(key);
|
||||
Recycle(list);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从队列池中获取一个值队列。如果队列池为空,则创建一个新的值队列。
|
||||
/// </summary>
|
||||
/// <returns>获取的值队列。</returns>
|
||||
private Queue<TValue> Fetch()
|
||||
{
|
||||
return _queue.Count <= 0 ? new Queue<TValue>() : _queue.Dequeue();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 回收一个不再使用的值队列到队列池中,以便重用。
|
||||
/// </summary>
|
||||
/// <param name="list">要回收的值队列。</param>
|
||||
private void Recycle(Queue<TValue> list)
|
||||
{
|
||||
list.Clear();
|
||||
|
||||
if (_recyclingLimit != 0 && _queue.Count > _recyclingLimit) return;
|
||||
|
||||
_queue.Enqueue(list);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清空当前实例的数据,同时回收所有值队列。
|
||||
/// </summary>
|
||||
protected new void Clear()
|
||||
{
|
||||
base.Clear();
|
||||
_queue.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Fantasy.Pool;
|
||||
|
||||
namespace Fantasy.DataStructure.Collection
|
||||
{
|
||||
/// <summary>
|
||||
/// 可重用的列表,继承自 <see cref="List{T}"/> 类。该类支持通过对象池重用列表实例,以减少对象分配和释放的开销。
|
||||
/// </summary>
|
||||
/// <typeparam name="T">列表中元素的类型。</typeparam>
|
||||
public sealed class ReuseList<T> : List<T>, IDisposable, IPool
|
||||
{
|
||||
private bool _isPool;
|
||||
private bool _isDispose;
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个 <see cref="ReuseList{T}"/> 可重用的列表的实例。
|
||||
/// </summary>
|
||||
/// <returns>创建的实例。</returns>
|
||||
public static ReuseList<T> Create()
|
||||
{
|
||||
#if FANTASY_WEBGL
|
||||
var list = Pool<ReuseList<T>>.Rent();
|
||||
#else
|
||||
var list = MultiThreadPool.Rent<ReuseList<T>>();
|
||||
#endif
|
||||
list._isDispose = false;
|
||||
list._isPool = true;
|
||||
return list;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放该实例所占用的资源,并将实例返回到对象池中,以便重用。
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_isDispose)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isDispose = true;
|
||||
Clear();
|
||||
#if FANTASY_WEBGL
|
||||
Pool<ReuseList<T>>.Return(this);
|
||||
#else
|
||||
MultiThreadPool.Return(this);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsPool()
|
||||
{
|
||||
return _isPool;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <param name="isPool"></param>
|
||||
public void SetIsPool(bool isPool)
|
||||
{
|
||||
_isPool = isPool;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 基于排序字典和并发集合实现的一对多映射列表的对象池包装类,继承自 <see cref="SortedConcurrentOneToManyList{TKey, TValue}"/> 类,
|
||||
/// 同时实现了 <see cref="IDisposable"/> 接口,以支持对象的重用和释放。
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">键的类型。</typeparam>
|
||||
/// <typeparam name="TValue">值的类型。</typeparam>
|
||||
public class SortedConcurrentOneToManyListPool<TKey, TValue> : SortedConcurrentOneToManyList<TKey, TValue>, IDisposable, IPool where TKey : notnull
|
||||
{
|
||||
private bool _isPool;
|
||||
private bool _isDispose;
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个新的 <see cref="SortedConcurrentOneToManyListPool{TKey, TValue}"/> 实例,使用默认的参数设置。
|
||||
/// </summary>
|
||||
/// <returns>新创建的 <see cref="SortedConcurrentOneToManyListPool{TKey, TValue}"/> 实例。</returns>
|
||||
public static SortedConcurrentOneToManyListPool<TKey, TValue> Create()
|
||||
{
|
||||
var a = MultiThreadPool.Rent<SortedConcurrentOneToManyListPool<TKey, TValue>>();
|
||||
a._isDispose = false;
|
||||
a._isPool = true;
|
||||
return a;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放当前对象池实例,将其返回到对象池以供重用。
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_isDispose)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isDispose = true;
|
||||
Clear();
|
||||
MultiThreadPool.Return(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsPool()
|
||||
{
|
||||
return _isPool;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <param name="isPool"></param>
|
||||
public void SetIsPool(bool isPool)
|
||||
{
|
||||
_isPool = isPool;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 基于排序字典和并发集合实现的一多对映射列表类,继承自 <see cref="SortedDictionary{TKey, TValue}"/> 类,
|
||||
/// 用于在多个值与一个键关联的情况下进行管理和存储。该类支持并发操作,适用于多线程环境。
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">键的类型。</typeparam>
|
||||
/// <typeparam name="TValue">值的类型。</typeparam>
|
||||
public class SortedConcurrentOneToManyList<TKey, TValue> : SortedDictionary<TKey, List<TValue>> where TKey : notnull
|
||||
{
|
||||
/// 用于同步操作的锁对象,它确保在多线程环境下对数据的安全访问。
|
||||
private readonly object _lockObject = new object();
|
||||
/// 用于存储缓存的队列。
|
||||
private readonly Queue<List<TValue>> _queue = new Queue<List<TValue>>();
|
||||
/// 控制缓存回收的限制。当缓存的数量超过此限制时,旧的缓存将会被回收。
|
||||
private readonly int _recyclingLimit;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化一个新的 <see cref="SortedConcurrentOneToManyList{TKey, TValue}"/> 类的实例,使用默认的参数设置。
|
||||
/// </summary>
|
||||
public SortedConcurrentOneToManyList()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化一个新的 <see cref="SortedConcurrentOneToManyList{TKey, TValue}"/> 类的实例,指定最大缓存数量。
|
||||
/// </summary>
|
||||
/// <param name="recyclingLimit">
|
||||
/// 1:防止数据量过大、所以超过recyclingLimit的数据还是走GC.
|
||||
/// 2:设置成0不控制数量,全部缓存
|
||||
/// </param>
|
||||
public SortedConcurrentOneToManyList(int recyclingLimit = 0)
|
||||
{
|
||||
_recyclingLimit = recyclingLimit;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查指定的键和值是否存在于映射列表中。
|
||||
/// </summary>
|
||||
/// <param name="key">要检查的键。</param>
|
||||
/// <param name="value">要检查的值。</param>
|
||||
/// <returns>如果存在,则为 true;否则为 false。</returns>
|
||||
public bool Contains(TKey key, TValue value)
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
TryGetValue(key, out var list);
|
||||
|
||||
return list != null && list.Contains(value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将指定的值添加到与指定键关联的列表中。
|
||||
/// </summary>
|
||||
/// <param name="key">要关联值的键。</param>
|
||||
/// <param name="value">要添加到列表的值。</param>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取与指定键关联的列表中的第一个值。
|
||||
/// 如果列表不存在或为空,则返回默认值。
|
||||
/// </summary>
|
||||
/// <param name="key">要获取第一个值的键。</param>
|
||||
/// <returns>第一个值,或默认值。</returns>
|
||||
public TValue First(TKey key)
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
return !TryGetValue(key, out var list) ? default : list.FirstOrDefault();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从与指定键关联的列表中移除指定的值。
|
||||
/// 如果列表不存在或值不存在于列表中,则不执行任何操作。
|
||||
/// </summary>
|
||||
/// <param name="key">要移除值的键。</param>
|
||||
/// <param name="value">要移除的值。</param>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从映射列表中移除指定的键及其关联的列表。
|
||||
/// 如果键不存在于映射列表中,则不执行任何操作。
|
||||
/// </summary>
|
||||
/// <param name="key">要移除的键。</param>
|
||||
public void RemoveKey(TKey key)
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
if (!TryGetValue(key, out var list)) return;
|
||||
|
||||
Remove(key);
|
||||
|
||||
Recycle(list);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从缓存中获取一个可重用的列表。如果缓存中不存在列表,则创建一个新的列表并返回。
|
||||
/// </summary>
|
||||
/// <returns>可重用的列表。</returns>
|
||||
private List<TValue> Fetch()
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
return _queue.Count <= 0 ? new List<TValue>() : _queue.Dequeue();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将不再使用的列表回收到缓存中,以便重复利用。如果缓存数量超过限制,则丢弃列表而不进行回收。
|
||||
/// </summary>
|
||||
/// <param name="list">要回收的列表。</param>
|
||||
private void Recycle(List<TValue> list)
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
list.Clear();
|
||||
|
||||
if (_recyclingLimit != 0 && _queue.Count > _recyclingLimit) return;
|
||||
|
||||
_queue.Enqueue(list);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清空映射列表以及队列。
|
||||
/// </summary>
|
||||
protected new void Clear()
|
||||
{
|
||||
base.Clear();
|
||||
_queue.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,192 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Fantasy.Pool;
|
||||
|
||||
namespace Fantasy.DataStructure.Collection
|
||||
{
|
||||
/// <summary>
|
||||
/// 基于排序字典实现的一对多关系的映射哈希集合的对象池包装类,将唯一键映射到多个值的哈希集合。
|
||||
/// 同时实现了 <see cref="IDisposable"/> 接口,以支持对象的重用和释放。
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">字典中键的类型。</typeparam>
|
||||
/// <typeparam name="TValue">哈希集合中值的类型。</typeparam>
|
||||
public class SortedOneToManyHashSetPool<TKey, TValue> : SortedOneToManyHashSet<TKey, TValue>, IDisposable, IPool where TKey : notnull
|
||||
{
|
||||
private bool _isPool;
|
||||
private bool _isDispose;
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个 <see cref="SortedOneToManyHashSetPool{TKey, TValue}"/> 实例。
|
||||
/// </summary>
|
||||
/// <returns>新创建的实例。</returns>
|
||||
public static SortedOneToManyHashSetPool<TKey, TValue> Create()
|
||||
{
|
||||
#if FANTASY_WEBGL
|
||||
var a = Pool<SortedOneToManyHashSetPool<TKey, TValue>>.Rent();
|
||||
#else
|
||||
var a = MultiThreadPool.Rent<SortedOneToManyHashSetPool<TKey, TValue>>();
|
||||
#endif
|
||||
a._isDispose = false;
|
||||
a._isPool = true;
|
||||
return a;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放当前对象池实例,将其返回到对象池以供重用。
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_isDispose)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isDispose = true;
|
||||
Clear();
|
||||
#if FANTASY_WEBGL
|
||||
Pool<SortedOneToManyHashSetPool<TKey, TValue>>.Return(this);
|
||||
#else
|
||||
MultiThreadPool.Return(this);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsPool()
|
||||
{
|
||||
return _isPool;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <param name="isPool"></param>
|
||||
public void SetIsPool(bool isPool)
|
||||
{
|
||||
_isPool = isPool;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 基于排序字典实现的一对多关系的映射哈希集合类,将唯一键映射到多个值的哈希集合。
|
||||
/// 用于在多个值与一个键关联的情况下进行管理和存储。
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">字典中键的类型。</typeparam>
|
||||
/// <typeparam name="TValue">集合中值的类型。</typeparam>
|
||||
public class SortedOneToManyHashSet<TKey, TValue> : SortedDictionary<TKey, HashSet<TValue>> where TKey : notnull
|
||||
{
|
||||
private readonly Queue<HashSet<TValue>> _queue = new Queue<HashSet<TValue>>();
|
||||
private readonly int _recyclingLimit = 120;
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个新的 <see cref="SortedOneToManyHashSet{TKey, TValue}"/> 实例。
|
||||
/// </summary>
|
||||
public SortedOneToManyHashSet() { }
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个新的 <see cref="SortedOneToManyHashSet{TKey, TValue}"/> 实例,设置最大缓存数量
|
||||
/// </summary>
|
||||
/// <param name="recyclingLimit">
|
||||
/// 1:防止数据量过大、所以超过recyclingLimit的数据还是走GC.
|
||||
/// 2:设置成0不控制数量,全部缓存
|
||||
/// </param>
|
||||
public SortedOneToManyHashSet(int recyclingLimit)
|
||||
{
|
||||
_recyclingLimit = recyclingLimit;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断哈希集合中是否包含指定的键值对。
|
||||
/// </summary>
|
||||
/// <param name="key">要查找的键。</param>
|
||||
/// <param name="value">要查找的值。</param>
|
||||
/// <returns>如果键值对存在,则为 true;否则为 false。</returns>
|
||||
public bool Contains(TKey key, TValue value)
|
||||
{
|
||||
TryGetValue(key, out var list);
|
||||
|
||||
return list != null && list.Contains(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将指定值添加到给定键关联的哈希集合中。
|
||||
/// </summary>
|
||||
/// <param name="key">要添加值的键。</param>
|
||||
/// <param name="value">要添加的值。</param>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从指定键关联的哈希集合中移除特定值。
|
||||
/// 如果哈希集合不存在或值不存在于集合中,则不执行任何操作。
|
||||
/// </summary>
|
||||
/// <param name="key">要移除值的键。</param>
|
||||
/// <param name="value">要移除的值。</param>
|
||||
public void RemoveValue(TKey key, TValue value)
|
||||
{
|
||||
if (!TryGetValue(key, out var list)) return;
|
||||
|
||||
list.Remove(value);
|
||||
|
||||
if (list.Count == 0) RemoveKey(key);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从字典中移除指定键以及关联的哈希集合,并将集合进行回收。
|
||||
/// 如果键不存在于映射列表中,则不执行任何操作。
|
||||
/// </summary>
|
||||
/// <param name="key">要移除的键。</param>
|
||||
public void RemoveKey(TKey key)
|
||||
{
|
||||
if (!TryGetValue(key, out var list)) return;
|
||||
|
||||
Remove(key);
|
||||
|
||||
Recycle(list);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个空的或回收的哈希集合。
|
||||
/// </summary>
|
||||
/// <returns>获取的哈希集合实例。</returns>
|
||||
private HashSet<TValue> Fetch()
|
||||
{
|
||||
return _queue.Count <= 0 ? new HashSet<TValue>() : _queue.Dequeue();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 回收一个哈希集合,将其清空并放入回收队列中。
|
||||
/// </summary>
|
||||
/// <param name="list">要回收的哈希集合。</param>
|
||||
private void Recycle(HashSet<TValue> list)
|
||||
{
|
||||
list.Clear();
|
||||
|
||||
if (_recyclingLimit != 0 && _queue.Count > _recyclingLimit) return;
|
||||
|
||||
_queue.Enqueue(list);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重写 Clear 方法,清空字典并清空回收队列。
|
||||
/// </summary>
|
||||
protected new void Clear()
|
||||
{
|
||||
base.Clear();
|
||||
_queue.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Fantasy.Pool;
|
||||
|
||||
#pragma warning disable CS8603
|
||||
|
||||
namespace Fantasy.DataStructure.Collection
|
||||
{
|
||||
/// <summary>
|
||||
/// 基于排序字典实现的一对多映射列表的对象池包装类,继承自 <see cref="SortedOneToManyList{TKey, TValue}"/> 类,
|
||||
/// 同时实现了 <see cref="IDisposable"/> 接口,以支持对象的重用和释放。
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">字典中键的类型。</typeparam>
|
||||
/// <typeparam name="TValue">列表中值的类型。</typeparam>
|
||||
public class SortedOneToManyListPool<TKey, TValue> : SortedOneToManyList<TKey, TValue>, IDisposable, IPool where TKey : notnull
|
||||
{
|
||||
private bool _isPool;
|
||||
private bool _isDispose;
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个 <see cref="SortedOneToManyListPool{TKey, TValue}"/> 实例。
|
||||
/// </summary>
|
||||
/// <returns>新创建的实例。</returns>
|
||||
public static SortedOneToManyListPool<TKey, TValue> Create()
|
||||
{
|
||||
#if FANTASY_WEBGL
|
||||
var a = Pool<SortedOneToManyListPool<TKey, TValue>>.Rent();
|
||||
#else
|
||||
var a = MultiThreadPool.Rent<SortedOneToManyListPool<TKey, TValue>>();
|
||||
#endif
|
||||
a._isDispose = false;
|
||||
a._isPool = true;
|
||||
return a;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放当前对象池实例,将其返回到对象池以供重用。
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_isDispose)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isDispose = true;
|
||||
Clear();
|
||||
#if FANTASY_WEBGL
|
||||
Pool<SortedOneToManyListPool<TKey, TValue>>.Return(this);
|
||||
#else
|
||||
MultiThreadPool.Return(this);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsPool()
|
||||
{
|
||||
return _isPool;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <param name="isPool"></param>
|
||||
public void SetIsPool(bool isPool)
|
||||
{
|
||||
_isPool = isPool;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 基于排序字典实现的一对多关系的映射列表类,将唯一键映射到包含多个值的列表。
|
||||
/// 用于在多个值与一个键关联的情况下进行管理和存储。
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">字典中键的类型。</typeparam>
|
||||
/// <typeparam name="TValue">列表中值的类型。</typeparam>
|
||||
public class SortedOneToManyList<TKey, TValue> : SortedDictionary<TKey, List<TValue>> where TKey : notnull
|
||||
{
|
||||
private readonly Queue<List<TValue>> _queue = new Queue<List<TValue>>();
|
||||
private readonly int _recyclingLimit;
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个新的 <see cref="SortedOneToManyList{TKey, TValue}"/> 实例。
|
||||
/// </summary>
|
||||
public SortedOneToManyList()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个新的 <see cref="SortedOneToManyList{TKey, TValue}"/> 实例,设置最大缓存数量
|
||||
/// </summary>
|
||||
/// <param name="recyclingLimit">
|
||||
/// 1:防止数据量过大、所以超过recyclingLimit的数据还是走GC.
|
||||
/// 2:设置成0不控制数量,全部缓存
|
||||
/// </param>
|
||||
public SortedOneToManyList(int recyclingLimit = 0)
|
||||
{
|
||||
_recyclingLimit = recyclingLimit;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断列表中是否包含指定的键值对。
|
||||
/// </summary>
|
||||
/// <param name="key">要查找的键。</param>
|
||||
/// <param name="value">要查找的值。</param>
|
||||
/// <returns>如果键值对存在,则为 true;否则为 false。</returns>
|
||||
public bool Contains(TKey key, TValue value)
|
||||
{
|
||||
TryGetValue(key, out var list);
|
||||
|
||||
return list != null && list.Contains(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将指定值添加到给定键关联的列表中。
|
||||
/// </summary>
|
||||
/// <param name="key">要添加值的键。</param>
|
||||
/// <param name="value">要添加的值。</param>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定键关联的列表中的第一个值。
|
||||
/// </summary>
|
||||
/// <param name="key">要查找值的键。</param>
|
||||
/// <returns>指定键关联的列表中的第一个值,如果列表为空则返回默认值。</returns>
|
||||
public TValue First(TKey key)
|
||||
{
|
||||
return !TryGetValue(key, out var list) ? default : list.FirstOrDefault();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从指定键关联的列表中移除特定值。
|
||||
/// </summary>
|
||||
/// <param name="key">要移除值的键。</param>
|
||||
/// <param name="value">要移除的值。</param>
|
||||
|
||||
public void RemoveValue(TKey key, TValue value)
|
||||
{
|
||||
if (!TryGetValue(key, out var list))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
list.Remove(value);
|
||||
|
||||
if (list.Count == 0)
|
||||
{
|
||||
RemoveKey(key);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从字典中移除指定键以及关联的列表,并将列表进行回收。
|
||||
/// </summary>
|
||||
/// <param name="key">要移除的键。</param>
|
||||
|
||||
public void RemoveKey(TKey key)
|
||||
{
|
||||
if (!TryGetValue(key, out var list))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Remove(key);
|
||||
Recycle(list);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个空的或回收的列表。
|
||||
/// </summary>
|
||||
/// <returns>获取的列表实例。</returns>
|
||||
private List<TValue> Fetch()
|
||||
{
|
||||
return _queue.Count <= 0 ? new List<TValue>() : _queue.Dequeue();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 回收一个列表,将其清空并放入回收队列中。如果缓存数量超过限制,则丢弃列表而不进行回收
|
||||
/// </summary>
|
||||
/// <param name="list">要回收的列表。</param>
|
||||
private void Recycle(List<TValue> list)
|
||||
{
|
||||
list.Clear();
|
||||
|
||||
if (_recyclingLimit != 0 && _queue.Count > _recyclingLimit)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_queue.Enqueue(list);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重写 Clear 方法,清空字典并清空回收队列。
|
||||
/// </summary>
|
||||
protected new void Clear()
|
||||
{
|
||||
base.Clear();
|
||||
_queue.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using System.Collections.Generic;
|
||||
#pragma warning disable CS8601 // Possible null reference assignment.
|
||||
|
||||
namespace Fantasy.DataStructure.Dictionary
|
||||
{
|
||||
/// <summary>
|
||||
/// 提供对字典的扩展方法。
|
||||
/// </summary>
|
||||
public static class DictionaryExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 尝试从字典中移除指定键,并返回相应的值。
|
||||
/// </summary>
|
||||
/// <typeparam name="T">字典中键的类型。</typeparam>
|
||||
/// <typeparam name="TV">字典中值的类型。</typeparam>
|
||||
/// <param name="self">要操作的字典实例。</param>
|
||||
/// <param name="key">要移除的键。</param>
|
||||
/// <param name="value">从字典中移除的值(如果成功移除)。</param>
|
||||
/// <returns>如果成功移除键值对,则为 true;否则为 false。</returns>
|
||||
public static bool TryRemove<T, TV>(this IDictionary<T, TV> self, T key, out TV value)
|
||||
{
|
||||
if (!self.TryGetValue(key, out value))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
self.Remove(key);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Fantasy.Pool;
|
||||
|
||||
namespace Fantasy.DataStructure.Dictionary
|
||||
{
|
||||
/// <summary>
|
||||
/// 提供一个可以使用对象池管理的字典类。
|
||||
/// </summary>
|
||||
/// <typeparam name="TM">字典中键的类型。</typeparam>
|
||||
/// <typeparam name="TN">字典中值的类型。</typeparam>
|
||||
public sealed class DictionaryPool<TM, TN> : Dictionary<TM, TN>, IDisposable, IPool where TM : notnull
|
||||
{
|
||||
private bool _isPool;
|
||||
private bool _isDispose;
|
||||
|
||||
/// <summary>
|
||||
/// 释放实例占用的资源。
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_isDispose)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isDispose = true;
|
||||
Clear();
|
||||
#if FANTASY_WEBGL
|
||||
Pool<DictionaryPool<TM, TN>>.Return(this);
|
||||
#else
|
||||
MultiThreadPool.Return(this);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个新的 <see cref="DictionaryPool{TM, TN}"/> 实例。
|
||||
/// </summary>
|
||||
/// <returns>新创建的实例。</returns>
|
||||
public static DictionaryPool<TM, TN> Create()
|
||||
{
|
||||
#if FANTASY_WEBGL
|
||||
var dictionary = Pool<DictionaryPool<TM, TN>>.Rent();
|
||||
#else
|
||||
var dictionary = MultiThreadPool.Rent<DictionaryPool<TM, TN>>();
|
||||
#endif
|
||||
dictionary._isDispose = false;
|
||||
dictionary._isPool = true;
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsPool()
|
||||
{
|
||||
return _isPool;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <param name="isPool"></param>
|
||||
public void SetIsPool(bool isPool)
|
||||
{
|
||||
_isPool = isPool;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 提供一个双向映射字典对象池类,用于双向键值对映射。
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">字典中键的类型。</typeparam>
|
||||
/// <typeparam name="TValue">字典中值的类型。</typeparam>
|
||||
public class DoubleMapDictionaryPool<TKey, TValue> : DoubleMapDictionary<TKey, TValue>, IDisposable, IPool where TKey : notnull where TValue : notnull
|
||||
{
|
||||
private bool _isPool;
|
||||
private bool _isDispose;
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个新的 <see cref="DoubleMapDictionaryPool{TKey, TValue}"/> 实例。
|
||||
/// </summary>
|
||||
/// <returns>新创建的实例。</returns>
|
||||
public static DoubleMapDictionaryPool<TKey, TValue> Create()
|
||||
{
|
||||
#if FANTASY_WEBGL
|
||||
var a = Pool<DoubleMapDictionaryPool<TKey, TValue>>.Rent();
|
||||
#else
|
||||
var a = MultiThreadPool.Rent<DoubleMapDictionaryPool<TKey, TValue>>();
|
||||
#endif
|
||||
a._isDispose = false;
|
||||
a._isPool = true;
|
||||
return a;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放实例占用的资源。
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_isDispose)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isDispose = true;
|
||||
Clear();
|
||||
#if FANTASY_WEBGL
|
||||
Pool<DoubleMapDictionaryPool<TKey, TValue>>.Return(this);
|
||||
#else
|
||||
MultiThreadPool.Return(this);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsPool()
|
||||
{
|
||||
return _isPool;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <param name="isPool"></param>
|
||||
public void SetIsPool(bool isPool)
|
||||
{
|
||||
_isPool = isPool;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 可以实现双向映射的字典类,用于将键和值进行双向映射。
|
||||
/// </summary>
|
||||
/// <typeparam name="TK">键的类型,不能为 null。</typeparam>
|
||||
/// <typeparam name="TV">值的类型,不能为 null。</typeparam>
|
||||
public class DoubleMapDictionary<TK, TV> where TK : notnull where TV : notnull
|
||||
{
|
||||
private readonly Dictionary<TK, TV> _kv = new Dictionary<TK, TV>();
|
||||
private readonly Dictionary<TV, TK> _vk = new Dictionary<TV, TK>();
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个新的空的 <see cref="DoubleMapDictionary{TK, TV}"/> 实例。
|
||||
/// </summary>
|
||||
public DoubleMapDictionary() { }
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个新的具有指定初始容量的 <see cref="DoubleMapDictionary{TK, TV}"/> 实例。
|
||||
/// </summary>
|
||||
/// <param name="capacity">初始容量。</param>
|
||||
public DoubleMapDictionary(int capacity)
|
||||
{
|
||||
_kv = new Dictionary<TK, TV>(capacity);
|
||||
_vk = new Dictionary<TV, TK>(capacity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取包含字典中所有键的列表。
|
||||
/// </summary>
|
||||
public List<TK> Keys => new List<TK>(_kv.Keys);
|
||||
|
||||
/// <summary>
|
||||
/// 获取包含字典中所有值的列表。
|
||||
/// </summary>
|
||||
public List<TV> Values => new List<TV>(_vk.Keys);
|
||||
|
||||
/// <summary>
|
||||
/// 对字典中的每个键值对执行指定的操作。
|
||||
/// </summary>
|
||||
/// <param name="action">要执行的操作。</param>
|
||||
public void ForEach(Action<TK, TV> action)
|
||||
{
|
||||
if (action == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var keys = _kv.Keys;
|
||||
foreach (var key in keys)
|
||||
{
|
||||
action(key, _kv[key]);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将指定的键值对添加到字典中。
|
||||
/// </summary>
|
||||
/// <param name="key">要添加的键。</param>
|
||||
/// <param name="value">要添加的值。</param>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据指定的键获取相应的值。
|
||||
/// </summary>
|
||||
/// <param name="key">要查找值的键。</param>
|
||||
/// <returns>与指定键关联的值,如果找不到键,则返回默认值。</returns>
|
||||
public TV GetValueByKey(TK key)
|
||||
{
|
||||
if (key != null && _kv.ContainsKey(key))
|
||||
{
|
||||
return _kv[key];
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试根据指定的键获取相应的值。
|
||||
/// </summary>
|
||||
/// <param name="key">要查找值的键。</param>
|
||||
/// <param name="value">如果找到,则为与指定键关联的值;否则为值的默认值。</param>
|
||||
/// <returns>如果找到键,则为 true;否则为 false。</returns>
|
||||
public bool TryGetValueByKey(TK key, out TV value)
|
||||
{
|
||||
var result = key != null && _kv.ContainsKey(key);
|
||||
|
||||
value = result ? _kv[key] : default;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据指定的值获取相应的键。
|
||||
/// </summary>
|
||||
/// <param name="value">要查找键的值。</param>
|
||||
/// <returns>与指定值关联的键,如果找不到值,则返回默认键。</returns>
|
||||
public TK GetKeyByValue(TV value)
|
||||
{
|
||||
if (value != null && _vk.ContainsKey(value))
|
||||
{
|
||||
return _vk[value];
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试根据指定的值获取相应的键。
|
||||
/// </summary>
|
||||
/// <param name="value">要查找键的值。</param>
|
||||
/// <param name="key">如果找到,则为与指定值关联的键;否则为键的默认值。</param>
|
||||
/// <returns>如果找到值,则为 true;否则为 false。</returns>
|
||||
public bool TryGetKeyByValue(TV value, out TK key)
|
||||
{
|
||||
var result = value != null && _vk.ContainsKey(value);
|
||||
|
||||
key = result ? _vk[value] : default;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据指定的键移除键值对。
|
||||
/// </summary>
|
||||
/// <param name="key">要移除的键。</param>
|
||||
public void RemoveByKey(TK key)
|
||||
{
|
||||
if (key == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_kv.TryGetValue(key, out var value))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_kv.Remove(key);
|
||||
_vk.Remove(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据指定的值移除键值对。
|
||||
/// </summary>
|
||||
/// <param name="value">要移除的值。</param>
|
||||
public void RemoveByValue(TV value)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_vk.TryGetValue(value, out var key))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_kv.Remove(key);
|
||||
_vk.Remove(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清空字典中的所有键值对。
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
_kv.Clear();
|
||||
_vk.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断字典是否包含指定的键。
|
||||
/// </summary>
|
||||
/// <param name="key">要检查的键。</param>
|
||||
/// <returns>如果字典包含指定的键,则为 true;否则为 false。</returns>
|
||||
public bool ContainsKey(TK key)
|
||||
{
|
||||
return key != null && _kv.ContainsKey(key);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断字典是否包含指定的值。
|
||||
/// </summary>
|
||||
/// <param name="value">要检查的值。</param>
|
||||
/// <returns>如果字典包含指定的值,则为 true;否则为 false。</returns>
|
||||
public bool ContainsValue(TV value)
|
||||
{
|
||||
return value != null && _vk.ContainsKey(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断字典是否包含指定的键值对。
|
||||
/// </summary>
|
||||
/// <param name="key">要检查的键。</param>
|
||||
/// <param name="value">要检查的值。</param>
|
||||
/// <returns>如果字典包含指定的键值对,则为 true;否则为 false。</returns>
|
||||
public bool Contains(TK key, TV value)
|
||||
{
|
||||
if (key == null || value == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return _kv.ContainsKey(key) && _vk.ContainsKey(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Fantasy.Pool;
|
||||
|
||||
namespace Fantasy.DataStructure.Dictionary
|
||||
{
|
||||
/// <summary>
|
||||
/// 提供一个带资源释放功能的实体字典类,支持使用对象池管理。
|
||||
/// </summary>
|
||||
/// <typeparam name="TM">字典中键的类型。</typeparam>
|
||||
/// <typeparam name="TN">字典中值的类型,必须实现 IDisposable 接口。</typeparam>
|
||||
public sealed class EntityDictionary<TM, TN> : Dictionary<TM, TN>, IDisposable, IPool where TN : IDisposable where TM : notnull
|
||||
{
|
||||
private bool _isPool;
|
||||
private bool _isDispose;
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个新的 <see cref="EntityDictionary{TM, TN}"/> 实例。
|
||||
/// </summary>
|
||||
/// <returns>新创建的实例。</returns>
|
||||
public static EntityDictionary<TM, TN> Create()
|
||||
{
|
||||
#if FANTASY_WEBGL
|
||||
var entityDictionary = Pool<EntityDictionary<TM, TN>>.Rent();
|
||||
#else
|
||||
var entityDictionary = MultiThreadPool.Rent<EntityDictionary<TM, TN>>();
|
||||
#endif
|
||||
entityDictionary._isDispose = false;
|
||||
entityDictionary._isPool = true;
|
||||
return entityDictionary;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清空字典中的所有键值对,并释放值的资源。
|
||||
/// </summary>
|
||||
public new void Clear()
|
||||
{
|
||||
foreach (var keyValuePair in this)
|
||||
{
|
||||
keyValuePair.Value.Dispose();
|
||||
}
|
||||
|
||||
base.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清空字典中的所有键值对,但不释放值的资源。
|
||||
/// </summary>
|
||||
public void ClearNotDispose()
|
||||
{
|
||||
base.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放实例占用的资源。
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_isDispose)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isDispose = true;
|
||||
Clear();
|
||||
#if FANTASY_WEBGL
|
||||
Pool<EntityDictionary<TM, TN>>.Return(this);
|
||||
#else
|
||||
MultiThreadPool.Return(this);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsPool()
|
||||
{
|
||||
return _isPool;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <param name="isPool"></param>
|
||||
public void SetIsPool(bool isPool)
|
||||
{
|
||||
_isPool = isPool;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 一对多映射关系的字典对象池。
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">外部字典中的键类型。</typeparam>
|
||||
/// <typeparam name="TValueKey">内部字典中的键类型。</typeparam>
|
||||
/// <typeparam name="TValue">内部字典中的值类型。</typeparam>
|
||||
public class OneToManyDictionaryPool<TKey, TValueKey, TValue> : OneToManyDictionary<TKey, TValueKey, TValue>, IDisposable, IPool where TKey : notnull where TValueKey : notnull
|
||||
{
|
||||
private bool _isPool;
|
||||
private bool _isDispose;
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个 <see cref="OneToManyDictionaryPool{TKey, TValueKey, TValue}"/> 的实例。
|
||||
/// </summary>
|
||||
/// <returns>新创建的 OneToManyDictionaryPool 实例。</returns>
|
||||
public static OneToManyDictionaryPool<TKey, TValueKey, TValue> Create()
|
||||
{
|
||||
#if FANTASY_WEBGL
|
||||
var a = Pool<OneToManyDictionaryPool<TKey, TValueKey, TValue>>.Rent();
|
||||
#else
|
||||
var a = MultiThreadPool.Rent<OneToManyDictionaryPool<TKey, TValueKey, TValue>>();
|
||||
#endif
|
||||
a._isDispose = false;
|
||||
a._isPool = true;
|
||||
return a;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放当前实例及其资源。
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_isDispose)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isDispose = true;
|
||||
Clear();
|
||||
#if FANTASY_WEBGL
|
||||
Pool<OneToManyDictionaryPool<TKey, TValueKey, TValue>>.Return(this);
|
||||
#else
|
||||
MultiThreadPool.Return(this);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsPool()
|
||||
{
|
||||
return _isPool;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <param name="isPool"></param>
|
||||
public void SetIsPool(bool isPool)
|
||||
{
|
||||
_isPool = isPool;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 一对多映射关系的字典。每个键都对应一个内部字典,该内部字典将键值映射到相应的值。
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">外部字典中的键类型。</typeparam>
|
||||
/// <typeparam name="TValueKey">内部字典中的键类型。</typeparam>
|
||||
/// <typeparam name="TValue">内部字典中的值类型。</typeparam>
|
||||
public class OneToManyDictionary<TKey, TValueKey, TValue> : Dictionary<TKey, Dictionary<TValueKey, TValue>>
|
||||
where TKey : notnull where TValueKey : notnull
|
||||
{
|
||||
private readonly Queue<Dictionary<TValueKey, TValue>> _queue = new Queue<Dictionary<TValueKey, TValue>>();
|
||||
private readonly int _recyclingLimit = 120;
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个新的 <see cref="OneToManyDictionary{TKey, TValueKey, TValue}"/> 实例。
|
||||
/// </summary>
|
||||
public OneToManyDictionary() { }
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个新的 <see cref="OneToManyDictionary{TKey, TValueKey, TValue}"/> 实例,并指定最大缓存数量。
|
||||
/// </summary>
|
||||
/// <param name="recyclingLimit">
|
||||
/// 1:防止数据量过大、所以超过recyclingLimit的数据还是走GC.
|
||||
/// 2:设置成0不控制数量,全部缓存
|
||||
/// </param>
|
||||
public OneToManyDictionary(int recyclingLimit = 0)
|
||||
{
|
||||
_recyclingLimit = recyclingLimit;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查是否包含指定的键值对。
|
||||
/// </summary>
|
||||
/// <param name="key">外部字典中的键。</param>
|
||||
/// <param name="valueKey">内部字典中的键。</param>
|
||||
/// <returns>如果包含指定的键值对,则为 true;否则为 false。</returns>
|
||||
public bool Contains(TKey key, TValueKey valueKey)
|
||||
{
|
||||
TryGetValue(key, out var dic);
|
||||
|
||||
return dic != null && dic.ContainsKey(valueKey);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试获取指定键值对的值。
|
||||
/// </summary>
|
||||
/// <param name="key">外部字典中的键。</param>
|
||||
/// <param name="valueKey">内部字典中的键。</param>
|
||||
/// <param name="value">获取的值,如果操作成功,则为值;否则为默认值。</param>
|
||||
/// <returns>如果操作成功,则为 true;否则为 false。</returns>
|
||||
public bool TryGetValue(TKey key, TValueKey valueKey, out TValue value)
|
||||
{
|
||||
value = default;
|
||||
return TryGetValue(key, out var dic) && dic.TryGetValue(valueKey, out value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定键的第一个值。
|
||||
/// </summary>
|
||||
/// <param name="key">要获取第一个值的键。</param>
|
||||
public TValue First(TKey key)
|
||||
{
|
||||
return !TryGetValue(key, out var dic) ? default : dic.First().Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 向字典中添加指定的键值对。
|
||||
/// </summary>
|
||||
/// <param name="key">要添加键值对的键。</param>
|
||||
/// <param name="valueKey">要添加键值对的内部字典键。</param>
|
||||
/// <param name="value">要添加的值。</param>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从字典中移除指定的键值对。
|
||||
/// </summary>
|
||||
/// <param name="key">要移除键值对的键。</param>
|
||||
/// <param name="valueKey">要移除键值对的内部字典键。</param>
|
||||
/// <returns>如果成功移除键值对,则为 true;否则为 false。</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从字典中移除指定的键值对。
|
||||
/// </summary>
|
||||
/// <param name="key">要移除键值对的键。</param>
|
||||
/// <param name="valueKey">要移除键值对的内部字典键。</param>
|
||||
/// <param name="value">如果成功移除键值对,则为移除的值;否则为默认值。</param>
|
||||
/// <returns>如果成功移除键值对,则为 true;否则为 false。</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除字典中的指定键及其相关的所有键值对。
|
||||
/// </summary>
|
||||
/// <param name="key">要移除的键。</param>
|
||||
public void RemoveKey(TKey key)
|
||||
{
|
||||
if (!TryGetValue(key, out var dic)) return;
|
||||
|
||||
Remove(key);
|
||||
Recycle(dic);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从对象池中获取一个内部字典实例,如果池中没有,则创建一个新实例。
|
||||
/// </summary>
|
||||
/// <returns>获取的内部字典实例。</returns>
|
||||
private Dictionary<TValueKey, TValue> Fetch()
|
||||
{
|
||||
return _queue.Count <= 0 ? new Dictionary<TValueKey, TValue>() : _queue.Dequeue();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将不再使用的内部字典实例放回对象池中,以便后续重用。
|
||||
/// </summary>
|
||||
/// <param name="dic">要放回对象池的内部字典实例。</param>
|
||||
private void Recycle(Dictionary<TValueKey, TValue> dic)
|
||||
{
|
||||
dic.Clear();
|
||||
|
||||
if (_recyclingLimit != 0 && _queue.Count > _recyclingLimit) return;
|
||||
|
||||
_queue.Enqueue(dic);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清空字典中的所有键值对,并将不再使用的内部字典实例放回对象池中。
|
||||
/// </summary>
|
||||
public new void Clear()
|
||||
{
|
||||
foreach (var keyValuePair in this) Recycle(keyValuePair.Value);
|
||||
|
||||
base.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Fantasy.Pool;
|
||||
|
||||
#pragma warning disable CS8601
|
||||
|
||||
namespace Fantasy.DataStructure.Dictionary
|
||||
{
|
||||
/// <summary>
|
||||
/// 一对多映射关系的排序字典对象池。
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">外部字典中的键类型。</typeparam>
|
||||
/// <typeparam name="TSortedKey">内部字典中的排序键类型。</typeparam>
|
||||
/// <typeparam name="TValue">内部字典中的值类型。</typeparam>
|
||||
public class OneToManySortedDictionaryPool<TKey, TSortedKey, TValue> : OneToManySortedDictionary<TKey, TSortedKey, TValue>, IDisposable, IPool where TKey : notnull where TSortedKey : notnull
|
||||
{
|
||||
private bool _isPool;
|
||||
private bool _isDispose;
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个 <see cref="OneToManySortedDictionaryPool{TKey, TSortedKey, TValue}"/> 的实例。
|
||||
/// </summary>
|
||||
/// <returns>新创建的 OneToManySortedDictionaryPool 实例。</returns>
|
||||
public static OneToManySortedDictionaryPool<TKey, TSortedKey, TValue> Create()
|
||||
{
|
||||
#if FANTASY_WEBGL
|
||||
var a = Pool<OneToManySortedDictionaryPool<TKey, TSortedKey, TValue>>.Rent();
|
||||
#else
|
||||
var a = MultiThreadPool.Rent<OneToManySortedDictionaryPool<TKey, TSortedKey, TValue>>();
|
||||
#endif
|
||||
a._isDispose = false;
|
||||
a._isPool = true;
|
||||
return a;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放当前实例及其资源。
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_isDispose)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isDispose = true;
|
||||
Clear();
|
||||
#if FANTASY_WEBGL
|
||||
Pool<OneToManySortedDictionaryPool<TKey, TSortedKey, TValue>>.Return(this);
|
||||
#else
|
||||
MultiThreadPool.Return(this);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsPool()
|
||||
{
|
||||
return _isPool;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <param name="isPool"></param>
|
||||
public void SetIsPool(bool isPool)
|
||||
{
|
||||
_isPool = isPool;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 一对多映射关系的排序字典。每个外部键映射到一个内部排序字典,该内部排序字典将排序键映射到相应的值。
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">外部字典中的键类型。</typeparam>
|
||||
/// <typeparam name="TSortedKey">内部字典中的排序键类型。</typeparam>
|
||||
/// <typeparam name="TValue">内部字典中的值类型。</typeparam>
|
||||
public class
|
||||
OneToManySortedDictionary<TKey, TSortedKey, TValue> : Dictionary<TKey, SortedDictionary<TSortedKey, TValue>>
|
||||
where TSortedKey : notnull where TKey : notnull
|
||||
{
|
||||
/// 缓存队列的回收限制
|
||||
private readonly int _recyclingLimit = 120;
|
||||
/// 缓存队列,用于存储已回收的内部排序字典
|
||||
private readonly Queue<SortedDictionary<TSortedKey, TValue>> _queue = new Queue<SortedDictionary<TSortedKey, TValue>>();
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个新的 <see cref="OneToManySortedDictionary{TKey, TSortedKey, TValue}"/> 实例。
|
||||
/// </summary>
|
||||
protected OneToManySortedDictionary() { }
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个新的 <see cref="OneToManySortedDictionary{TKey, TSortedKey, TValue}"/> 实例。设置最大缓存数量
|
||||
/// </summary>
|
||||
/// <param name="recyclingLimit">
|
||||
/// 1:防止数据量过大、所以超过recyclingLimit的数据还是走GC.
|
||||
/// 2:设置成0不控制数量,全部缓存
|
||||
/// </param>
|
||||
public OneToManySortedDictionary(int recyclingLimit)
|
||||
{
|
||||
_recyclingLimit = recyclingLimit;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查字典是否包含指定的外部键。
|
||||
/// </summary>
|
||||
/// <param name="key">要检查的外部键。</param>
|
||||
/// <returns>如果字典包含指定的外部键,则为 true;否则为 false。</returns>
|
||||
public bool Contains(TKey key)
|
||||
{
|
||||
return this.ContainsKey(key);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查字典是否包含指定的外部键和排序键。
|
||||
/// </summary>
|
||||
/// <param name="key">要检查的外部键。</param>
|
||||
/// <param name="sortedKey">要检查的排序键。</param>
|
||||
/// <returns>如果字典包含指定的外部键和排序键,则为 true;否则为 false。</returns>
|
||||
public bool Contains(TKey key, TSortedKey sortedKey)
|
||||
{
|
||||
return TryGetValue(key, out var dic) && dic.ContainsKey(sortedKey);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试从字典中获取指定外部键对应的内部排序字典。
|
||||
/// </summary>
|
||||
/// <param name="key">要获取内部排序字典的外部键。</param>
|
||||
/// <param name="dic">获取到的内部排序字典,如果找不到则为 null。</param>
|
||||
/// <returns>如果找到内部排序字典,则为 true;否则为 false。</returns>
|
||||
public new bool TryGetValue(TKey key, out SortedDictionary<TSortedKey, TValue> dic)
|
||||
{
|
||||
return base.TryGetValue(key, out dic);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试从字典中获取指定外部键和排序键对应的值。
|
||||
/// </summary>
|
||||
/// <param name="key">要获取值的外部键。</param>
|
||||
/// <param name="sortedKey">要获取值的排序键。</param>
|
||||
/// <param name="value">获取到的值,如果找不到则为 default。</param>
|
||||
/// <returns>如果找到值,则为 true;否则为 false。</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 向字典中添加一个值,关联到指定的外部键和排序键。
|
||||
/// </summary>
|
||||
/// <param name="key">要关联值的外部键。</param>
|
||||
/// <param name="sortedKey">要关联值的排序键。</param>
|
||||
/// <param name="value">要添加的值。</param>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从字典中移除指定外部键和排序键关联的值。
|
||||
/// </summary>
|
||||
/// <param name="key">要移除值的外部键。</param>
|
||||
/// <param name="sortedKey">要移除值的排序键。</param>
|
||||
/// <returns>如果成功移除值,则为 true;否则为 false。</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从字典中移除指定外部键及其关联的所有值。
|
||||
/// </summary>
|
||||
/// <param name="key">要移除的外部键。</param>
|
||||
/// <returns>如果成功移除外部键及其关联的所有值,则为 true;否则为 false。</returns>
|
||||
public bool RemoveKey(TKey key)
|
||||
{
|
||||
if (!TryGetValue(key, out var list))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Remove(key);
|
||||
Recycle(list);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从缓存队列中获取一个内部排序字典。
|
||||
/// </summary>
|
||||
/// <returns>一个内部排序字典。</returns>
|
||||
private SortedDictionary<TSortedKey, TValue> Fetch()
|
||||
{
|
||||
return _queue.Count <= 0 ? new SortedDictionary<TSortedKey, TValue>() : _queue.Dequeue();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 回收一个内部排序字典到缓存队列。
|
||||
/// </summary>
|
||||
/// <param name="dic">要回收的内部排序字典。</param>
|
||||
private void Recycle(SortedDictionary<TSortedKey, TValue> dic)
|
||||
{
|
||||
dic.Clear();
|
||||
|
||||
if (_recyclingLimit != 0 && _queue.Count > _recyclingLimit)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_queue.Enqueue(dic);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清空字典以及内部排序字典缓存队列,释放所有资源。
|
||||
/// </summary>
|
||||
protected new void Clear()
|
||||
{
|
||||
base.Clear();
|
||||
_queue.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Fantasy.Pool;
|
||||
|
||||
namespace Fantasy.DataStructure.Dictionary
|
||||
{
|
||||
/// <summary>
|
||||
/// 提供一个可以重用的字典类,支持使用对象池管理。
|
||||
/// </summary>
|
||||
/// <typeparam name="TM">字典中键的类型。</typeparam>
|
||||
/// <typeparam name="TN">字典中值的类型。</typeparam>
|
||||
public sealed class ReuseDictionary<TM, TN> : Dictionary<TM, TN>, IDisposable, IPool where TM : notnull
|
||||
{
|
||||
private bool _isPool;
|
||||
private bool _isDispose;
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个新的 <see cref="ReuseDictionary{TM, TN}"/> 实例。
|
||||
/// </summary>
|
||||
/// <returns>新创建的实例。</returns>
|
||||
public static ReuseDictionary<TM, TN> Create()
|
||||
{
|
||||
#if FANTASY_WEBGL
|
||||
var entityDictionary = Pool<ReuseDictionary<TM, TN>>.Rent();
|
||||
#else
|
||||
var entityDictionary = MultiThreadPool.Rent<ReuseDictionary<TM, TN>>();
|
||||
#endif
|
||||
entityDictionary._isDispose = false;
|
||||
entityDictionary._isPool = true;
|
||||
return entityDictionary;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放实例占用的资源。
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_isDispose)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isDispose = true;
|
||||
Clear();
|
||||
#if FANTASY_WEBGL
|
||||
Pool<ReuseDictionary<TM, TN>>.Return(this);
|
||||
#else
|
||||
MultiThreadPool.Return(this);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsPool()
|
||||
{
|
||||
return _isPool;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <param name="isPool"></param>
|
||||
public void SetIsPool(bool isPool)
|
||||
{
|
||||
_isPool = isPool;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Fantasy.Pool;
|
||||
|
||||
namespace Fantasy.DataStructure.Dictionary
|
||||
{
|
||||
/// <summary>
|
||||
/// 提供一个可以使用对象池管理的排序字典类。
|
||||
/// </summary>
|
||||
/// <typeparam name="TM"></typeparam>
|
||||
/// <typeparam name="TN"></typeparam>
|
||||
public sealed class SortedDictionaryPool<TM, TN> : SortedDictionary<TM, TN>, IDisposable, IPool where TM : notnull
|
||||
{
|
||||
private bool _isPool;
|
||||
private bool _isDispose;
|
||||
|
||||
/// <summary>
|
||||
/// 释放实例占用的资源。
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_isDispose)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isDispose = true;
|
||||
Clear();
|
||||
#if FANTASY_WEBGL
|
||||
Pool<SortedDictionaryPool<TM, TN>>.Return(this);
|
||||
#else
|
||||
MultiThreadPool.Return(this);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个新的 <see cref="SortedDictionaryPool{TM, TN}"/> 实例。
|
||||
/// </summary>
|
||||
/// <returns>新创建的实例。</returns>
|
||||
public static SortedDictionaryPool<TM, TN> Create()
|
||||
{
|
||||
#if FANTASY_WEBGL
|
||||
var dictionary = Pool<SortedDictionaryPool<TM, TN>>.Rent();
|
||||
#else
|
||||
var dictionary = MultiThreadPool.Rent<SortedDictionaryPool<TM, TN>>();
|
||||
#endif
|
||||
dictionary._isDispose = false;
|
||||
dictionary._isPool = true;
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsPool()
|
||||
{
|
||||
return _isPool;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <param name="isPool"></param>
|
||||
public void SetIsPool(bool isPool)
|
||||
{
|
||||
_isPool = isPool;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 优先队列
|
||||
/// </summary>
|
||||
/// <typeparam name="TElement">节点数据</typeparam>
|
||||
/// <typeparam name="TPriority">排序的类型、</typeparam>
|
||||
public sealed class PriorityQueue<TElement, TPriority> where TPriority : IComparable<TPriority>
|
||||
{
|
||||
private readonly List<PriorityQueueItem<TElement, TPriority>> _heap;
|
||||
|
||||
public PriorityQueue(int initialCapacity = 16)
|
||||
{
|
||||
_heap = new List<PriorityQueueItem<TElement, TPriority>>(initialCapacity);
|
||||
}
|
||||
|
||||
public int Count => _heap.Count;
|
||||
|
||||
public void Enqueue(TElement element, TPriority priority)
|
||||
{
|
||||
_heap.Add(new PriorityQueueItem<TElement, TPriority>(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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<T>
|
||||
{
|
||||
public T Element { get; set; }
|
||||
public uint Priority { get; set; }
|
||||
|
||||
public PriorityQueueItemUint(T element, uint priority)
|
||||
{
|
||||
Element = element;
|
||||
Priority = priority;
|
||||
}
|
||||
}
|
||||
|
||||
public struct PriorityQueueItem<T, T1>
|
||||
{
|
||||
public T Element { get; }
|
||||
public T1 Priority { get; }
|
||||
|
||||
public PriorityQueueItem(T element, T1 priority)
|
||||
{
|
||||
Element = element;
|
||||
Priority = priority;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<T> where T : IComparable<T>
|
||||
{
|
||||
private readonly List<T> _heap;
|
||||
|
||||
public PriorityQueue(int initialCapacity = 16)
|
||||
{
|
||||
_heap = new List<T>(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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 跳表数据结构(升序版)
|
||||
/// </summary>
|
||||
/// <typeparam name="TValue">跳表中存储的值的类型。</typeparam>
|
||||
public class SkipTable<TValue> : SkipTableBase<TValue>
|
||||
{
|
||||
/// <summary>
|
||||
/// 创建一个新的跳表实例。
|
||||
/// </summary>
|
||||
/// <param name="maxLayer">跳表的最大层数。</param>
|
||||
public SkipTable(int maxLayer = 8) : base(maxLayer) { }
|
||||
|
||||
/// <summary>
|
||||
/// 向跳表中添加一个新节点。
|
||||
/// </summary>
|
||||
/// <param name="sortKey">节点的主排序键。</param>
|
||||
/// <param name="viceKey">节点的副排序键。</param>
|
||||
/// <param name="key">节点的唯一键。</param>
|
||||
/// <param name="value">要添加的值。</param>
|
||||
public override void Add(long sortKey, long viceKey, long key, TValue value)
|
||||
{
|
||||
var rLevel = 1;
|
||||
|
||||
while (rLevel <= MaxLayer && Random.Next(3) == 0)
|
||||
{
|
||||
++rLevel;
|
||||
}
|
||||
|
||||
SkipTableNode<TValue> 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<TValue>(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<TValue> v = cur.Right.Right;
|
||||
|
||||
while (v != null)
|
||||
{
|
||||
v.Index++;
|
||||
v = v.Right;
|
||||
}
|
||||
}
|
||||
|
||||
last = cur.Right;
|
||||
}
|
||||
|
||||
cur = cur.Down;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从跳表中移除一个节点。
|
||||
/// </summary>
|
||||
/// <param name="sortKey">节点的主排序键。</param>
|
||||
/// <param name="viceKey">节点的副排序键。</param>
|
||||
/// <param name="key">节点的唯一键。</param>
|
||||
/// <param name="value">被移除的节点的值。</param>
|
||||
/// <returns>如果成功移除节点,则为 true;否则为 false。</returns>
|
||||
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<TValue> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 抽象的跳表基类,提供跳表的基本功能和操作。
|
||||
/// </summary>
|
||||
/// <typeparam name="TValue">跳表中存储的值的类型。</typeparam>
|
||||
public abstract class SkipTableBase<TValue> : IEnumerable<SkipTableNode<TValue>>
|
||||
{
|
||||
/// <summary>
|
||||
/// 跳表的最大层数
|
||||
/// </summary>
|
||||
public readonly int MaxLayer;
|
||||
/// <summary>
|
||||
/// 跳表的顶部头节点
|
||||
/// </summary>
|
||||
public readonly SkipTableNode<TValue> TopHeader;
|
||||
/// <summary>
|
||||
/// 跳表的底部头节点
|
||||
/// </summary>
|
||||
public SkipTableNode<TValue> BottomHeader;
|
||||
/// <summary>
|
||||
/// 跳表中节点的数量,使用了 Node 字典的计数
|
||||
/// </summary>
|
||||
public int Count => Node.Count;
|
||||
/// <summary>
|
||||
/// 用于生成随机数的随机数生成器
|
||||
/// </summary>
|
||||
protected readonly Random Random = new Random();
|
||||
/// <summary>
|
||||
/// 存储跳表节点的字典
|
||||
/// </summary>
|
||||
protected readonly Dictionary<long, SkipTableNode<TValue>> Node = new();
|
||||
/// <summary>
|
||||
/// 用于辅助反向查找的栈
|
||||
/// </summary>
|
||||
protected readonly Stack<SkipTableNode<TValue>> AntiFindStack = new Stack<SkipTableNode<TValue>>();
|
||||
|
||||
/// <summary>
|
||||
/// 初始化一个新的跳表实例。
|
||||
/// </summary>
|
||||
/// <param name="maxLayer">跳表的最大层数,默认为 8。</param>
|
||||
protected SkipTableBase(int maxLayer = 8)
|
||||
{
|
||||
MaxLayer = maxLayer;
|
||||
var cur = TopHeader = new SkipTableNode<TValue>(long.MinValue, 0, 0, default, 0, null, null, null);
|
||||
|
||||
for (var layer = MaxLayer - 1; layer >= 1; --layer)
|
||||
{
|
||||
cur.Down = new SkipTableNode<TValue>(long.MinValue, 0, 0, default, 0, null, null, null);
|
||||
cur = cur.Down;
|
||||
}
|
||||
|
||||
BottomHeader = cur;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定键的节点的值,若不存在则返回默认值。
|
||||
/// </summary>
|
||||
/// <param name="key">要查找的键。</param>
|
||||
public TValue this[long key] => !TryGetValueByKey(key, out TValue value) ? default : value;
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定键的节点在跳表中的排名。
|
||||
/// </summary>
|
||||
/// <param name="key">要查找的键。</param>
|
||||
/// <returns>节点的排名。</returns>
|
||||
public int GetRanking(long key)
|
||||
{
|
||||
if (!Node.TryGetValue(key, out var node))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return node.Index;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定键的反向排名,即在比该键更大的节点中的排名。
|
||||
/// </summary>
|
||||
/// <param name="key">要查找的键。</param>
|
||||
/// <returns>反向排名。</returns>
|
||||
public int GetAntiRanking(long key)
|
||||
{
|
||||
var ranking = GetRanking(key);
|
||||
|
||||
if (ranking == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return Count + 1 - ranking;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试通过键获取节点的值。
|
||||
/// </summary>
|
||||
/// <param name="key">要查找的键。</param>
|
||||
/// <param name="value">获取到的节点的值,如果键不存在则为默认值。</param>
|
||||
/// <returns>是否成功获取节点的值。</returns>
|
||||
public bool TryGetValueByKey(long key, out TValue value)
|
||||
{
|
||||
if (!Node.TryGetValue(key, out var node))
|
||||
{
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
value = node.Value;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试通过键获取节点。
|
||||
/// </summary>
|
||||
/// <param name="key">要查找的键。</param>
|
||||
/// <param name="node">获取到的节点,如果键不存在则为 <c>null</c>。</param>
|
||||
/// <returns>是否成功获取节点。</returns>
|
||||
public bool TryGetNodeByKey(long key, out SkipTableNode<TValue> node)
|
||||
{
|
||||
if (Node.TryGetValue(key, out node))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在跳表中查找节点,返回从起始位置到结束位置的节点列表。
|
||||
/// </summary>
|
||||
/// <param name="start">起始位置的排名。</param>
|
||||
/// <param name="end">结束位置的排名。</param>
|
||||
/// <param name="list">用于存储节点列表的 <see cref="ListPool{T}"/> 实例。</param>
|
||||
public void Find(int start, int end, ListPool<SkipTableNode<TValue>> 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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在跳表中进行反向查找节点,返回从结束位置到起始位置的节点列表。
|
||||
/// </summary>
|
||||
/// <param name="start">结束位置的排名。</param>
|
||||
/// <param name="end">起始位置的排名。</param>
|
||||
/// <param name="list">用于存储节点列表的 <see cref="ListPool{T}"/> 实例。</param>
|
||||
public void AntiFind(int start, int end, ListPool<SkipTableNode<TValue>> 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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取跳表中最后一个节点的值。
|
||||
/// </summary>
|
||||
/// <returns>最后一个节点的值。</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除跳表中指定键的节点。
|
||||
/// </summary>
|
||||
/// <param name="key">要移除的节点的键。</param>
|
||||
/// <returns>移除是否成功。</returns>
|
||||
public bool Remove(long key)
|
||||
{
|
||||
if (!Node.TryGetValue(key, out var node))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return Remove(node.SortKey, node.ViceKey, key, out _);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 向跳表中添加节点。
|
||||
/// </summary>
|
||||
/// <param name="sortKey">节点的排序键。</param>
|
||||
/// <param name="viceKey">节点的副键。</param>
|
||||
/// <param name="key">节点的键。</param>
|
||||
/// <param name="value">节点的值。</param>
|
||||
public abstract void Add(long sortKey, long viceKey, long key, TValue value);
|
||||
|
||||
/// <summary>
|
||||
/// 从跳表中移除指定键的节点。
|
||||
/// </summary>
|
||||
/// <param name="sortKey">节点的排序键。</param>
|
||||
/// <param name="viceKey">节点的副键。</param>
|
||||
/// <param name="key">节点的键。</param>
|
||||
/// <param name="value">被移除的节点的值。</param>
|
||||
/// <returns>移除是否成功。</returns>
|
||||
public abstract bool Remove(long sortKey, long viceKey, long key, out TValue value);
|
||||
|
||||
/// <summary>
|
||||
/// 返回一个枚举器,用于遍历跳表中的节点。
|
||||
/// </summary>
|
||||
/// <returns>一个可用于遍历跳表节点的枚举器。</returns>
|
||||
public IEnumerator<SkipTableNode<TValue>> GetEnumerator()
|
||||
{
|
||||
var cur = BottomHeader.Right;
|
||||
while (cur != null)
|
||||
{
|
||||
yield return cur;
|
||||
cur = cur.Right;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 返回一个非泛型枚举器,用于遍历跳表中的节点。
|
||||
/// </summary>
|
||||
/// <returns>一个非泛型枚举器,可用于遍历跳表节点。</returns>
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 跳表降序版,用于存储降序排列的数据。
|
||||
/// </summary>
|
||||
/// <typeparam name="TValue">存储的值的类型。</typeparam>
|
||||
public class SkipTableDesc<TValue> : SkipTableBase<TValue>
|
||||
{
|
||||
/// <summary>
|
||||
/// 初始化跳表降序版的新实例。
|
||||
/// </summary>
|
||||
/// <param name="maxLayer">跳表的最大层数,默认为 8。</param>
|
||||
public SkipTableDesc(int maxLayer = 8) : base(maxLayer) { }
|
||||
|
||||
/// <summary>
|
||||
/// 向跳表中添加一个节点,根据降序规则进行插入。
|
||||
/// </summary>
|
||||
/// <param name="sortKey">排序主键。</param>
|
||||
/// <param name="viceKey">副键。</param>
|
||||
/// <param name="key">键。</param>
|
||||
/// <param name="value">值。</param>
|
||||
public override void Add(long sortKey, long viceKey, long key, TValue value)
|
||||
{
|
||||
var rLevel = 1;
|
||||
|
||||
while (rLevel <= MaxLayer && Random.Next(3) == 0)
|
||||
{
|
||||
++rLevel;
|
||||
}
|
||||
|
||||
SkipTableNode<TValue> 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<TValue>(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<TValue> v = cur.Right.Right;
|
||||
|
||||
while (v != null)
|
||||
{
|
||||
v.Index++;
|
||||
v = v.Right;
|
||||
}
|
||||
}
|
||||
|
||||
last = cur.Right;
|
||||
}
|
||||
|
||||
cur = cur.Down;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从跳表中移除一个节点,根据降序规则进行移除。
|
||||
/// </summary>
|
||||
/// <param name="sortKey">排序主键。</param>
|
||||
/// <param name="viceKey">副键。</param>
|
||||
/// <param name="key">键。</param>
|
||||
/// <param name="value">移除的节点值。</param>
|
||||
/// <returns>如果成功移除节点,则返回 true,否则返回 false。</returns>
|
||||
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<TValue> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
namespace Fantasy.DataStructure.SkipTable
|
||||
{
|
||||
/// <summary>
|
||||
/// 跳跃表节点。
|
||||
/// </summary>
|
||||
/// <typeparam name="TValue">节点的值的类型。</typeparam>
|
||||
public class SkipTableNode<TValue>
|
||||
{
|
||||
/// <summary>
|
||||
/// 节点在跳跃表中的索引。
|
||||
/// </summary>
|
||||
public int Index;
|
||||
/// <summary>
|
||||
/// 节点的主键。
|
||||
/// </summary>
|
||||
public long Key;
|
||||
/// <summary>
|
||||
/// 节点的排序键。
|
||||
/// </summary>
|
||||
public long SortKey;
|
||||
/// <summary>
|
||||
/// 节点的副键。
|
||||
/// </summary>
|
||||
public long ViceKey;
|
||||
/// <summary>
|
||||
/// 节点存储的值。
|
||||
/// </summary>
|
||||
public TValue Value;
|
||||
/// <summary>
|
||||
/// 指向左侧节点的引用。
|
||||
/// </summary>
|
||||
public SkipTableNode<TValue> Left;
|
||||
/// <summary>
|
||||
/// 指向右侧节点的引用。
|
||||
/// </summary>
|
||||
public SkipTableNode<TValue> Right;
|
||||
/// <summary>
|
||||
/// 指向下一层节点的引用。
|
||||
/// </summary>
|
||||
public SkipTableNode<TValue> Down;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化跳跃表节点的新实例。
|
||||
/// </summary>
|
||||
/// <param name="sortKey">节点的排序键。</param>
|
||||
/// <param name="viceKey">节点的副键。</param>
|
||||
/// <param name="key">节点的主键。</param>
|
||||
/// <param name="value">节点存储的值。</param>
|
||||
/// <param name="index">节点在跳跃表中的索引。</param>
|
||||
/// <param name="l">指向左侧节点的引用。</param>
|
||||
/// <param name="r">指向右侧节点的引用。</param>
|
||||
/// <param name="d">指向下一层节点的引用。</param>
|
||||
public SkipTableNode(long sortKey, long viceKey, long key, TValue value, int index,
|
||||
SkipTableNode<TValue> l,
|
||||
SkipTableNode<TValue> r,
|
||||
SkipTableNode<TValue> d)
|
||||
{
|
||||
Left = l;
|
||||
Right = r;
|
||||
Down = d;
|
||||
Value = value;
|
||||
Key = key;
|
||||
Index = index;
|
||||
SortKey = sortKey;
|
||||
ViceKey = viceKey;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 协程锁专用的对象池
|
||||
/// </summary>
|
||||
public sealed class CoroutineLockPool : PoolCore<CoroutineLock>
|
||||
{
|
||||
/// <summary>
|
||||
/// 协程锁专用的对象池的构造函数
|
||||
/// </summary>
|
||||
public CoroutineLockPool() : base(2000) { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 协程锁
|
||||
/// </summary>
|
||||
public sealed class CoroutineLock : IPool, IDisposable
|
||||
{
|
||||
private Scene _scene;
|
||||
private CoroutineLockComponent _coroutineLockComponent;
|
||||
private readonly Dictionary<long, CoroutineLockQueue> _queue = new Dictionary<long, CoroutineLockQueue>();
|
||||
/// <summary>
|
||||
/// 表示是否是对象池中创建的
|
||||
/// </summary>
|
||||
private bool _isPool;
|
||||
/// <summary>
|
||||
/// 协程锁的类型
|
||||
/// </summary>
|
||||
public long CoroutineLockType { get; private set; }
|
||||
|
||||
internal void Initialize(CoroutineLockComponent coroutineLockComponent, ref long coroutineLockType)
|
||||
{
|
||||
_scene = coroutineLockComponent.Scene;
|
||||
CoroutineLockType = coroutineLockType;
|
||||
_coroutineLockComponent = coroutineLockComponent;
|
||||
}
|
||||
/// <summary>
|
||||
/// 销毁协程锁,如果调用了该方法,所有使用当前协程锁等待的逻辑会按照顺序释放锁。
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var (_, coroutineLockQueue) in _queue)
|
||||
{
|
||||
while (TryCoroutineLockQueueDequeue(coroutineLockQueue)) { }
|
||||
}
|
||||
|
||||
_queue.Clear();
|
||||
_scene = null;
|
||||
CoroutineLockType = 0;
|
||||
_coroutineLockComponent = null;
|
||||
}
|
||||
/// <summary>
|
||||
/// 等待上一个任务完成
|
||||
/// </summary>
|
||||
/// <param name="coroutineLockQueueKey">需要等待的Id</param>
|
||||
/// <param name="tag">用于查询协程锁的标记,可不传入,只有在超时的时候排查是哪个锁超时时使用</param>
|
||||
/// <param name="timeOut">等待多久会超时,当到达设定的时候会把当前锁给按照超时处理</param>
|
||||
/// <returns></returns>
|
||||
public async FTask<WaitCoroutineLock> 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;
|
||||
}
|
||||
/// <summary>
|
||||
/// 按照先入先出的顺序,释放最早的一个协程锁
|
||||
/// </summary>
|
||||
/// <param name="coroutineLockQueueKey"></param>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsPool()
|
||||
{
|
||||
return _isPool;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <param name="isPool"></param>
|
||||
public void SetIsPool(bool isPool)
|
||||
{
|
||||
_isPool = isPool;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 协程锁组件
|
||||
/// </summary>
|
||||
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<long, CoroutineLock> _coroutineLocks = new Dictionary<long, CoroutineLock>();
|
||||
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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个新的协程锁
|
||||
/// 使用这个方法创建的协程锁,需要手动释放管理CoroutineLock。
|
||||
/// 不会再CoroutineLockComponent理进行管理。
|
||||
/// </summary>
|
||||
/// <param name="coroutineLockType"></param>
|
||||
/// <returns></returns>
|
||||
public CoroutineLock Create(long coroutineLockType)
|
||||
{
|
||||
var coroutineLock = _coroutineLockPool.Rent();
|
||||
coroutineLock.Initialize(this, ref coroutineLockType);
|
||||
return coroutineLock;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 请求一个协程锁。
|
||||
/// 使用这个方法创建的协程锁,会自动释放CoroutineLockQueueType。
|
||||
/// </summary>
|
||||
/// <param name="coroutineLockType">锁类型</param>
|
||||
/// <param name="coroutineLockQueueKey">锁队列Id</param>
|
||||
/// <param name="tag">当某些锁超时,需要一个标记来方便排查问题,正常的情况下这个默认为null就可以。</param>
|
||||
/// <param name="time">设置锁的超时时间,让超过设置的时间会触发超时,保证锁不会因为某一个锁一直不解锁导致卡住的问题。</param>
|
||||
/// <returns>
|
||||
/// 返回的WaitCoroutineLock通过Dispose来解除这个锁、建议用using来保住这个锁。
|
||||
/// 也可以返回的WaitCoroutineLock通过CoroutineLockComponent.UnLock来解除这个锁。
|
||||
/// </returns>
|
||||
public FTask<WaitCoroutineLock> 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 解除一个协程锁。
|
||||
/// </summary>
|
||||
/// <param name="coroutineLockType"></param>
|
||||
/// <param name="coroutineLockQueueKey"></param>
|
||||
public void Release(int coroutineLockType, long coroutineLockQueueKey)
|
||||
{
|
||||
if (IsDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_coroutineLocks.TryGetValue(coroutineLockType, out var coroutineLock))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
coroutineLock.Release(coroutineLockQueueKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<CoroutineLockQueue>
|
||||
{
|
||||
public CoroutineLockQueuePool() : base(2000) { }
|
||||
}
|
||||
|
||||
internal sealed class CoroutineLockQueue : Queue<WaitCoroutineLock>, IPool
|
||||
{
|
||||
private bool _isPool;
|
||||
/// <summary>
|
||||
/// 获取一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsPool()
|
||||
{
|
||||
return _isPool;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <param name="isPool"></param>
|
||||
public void SetIsPool(bool isPool)
|
||||
{
|
||||
_isPool = isPool;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<WaitCoroutineLock>
|
||||
{
|
||||
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<CoroutineLockTimeout>
|
||||
{
|
||||
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}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 一个协程锁的实例,用户可以用过这个手动释放锁
|
||||
/// </summary>
|
||||
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<WaitCoroutineLock> _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;
|
||||
}
|
||||
/// <summary>
|
||||
/// 释放协程锁
|
||||
/// </summary>
|
||||
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<WaitCoroutineLock> Tcs
|
||||
{
|
||||
get { return _tcs ??= FTask<WaitCoroutineLock>.Create(); }
|
||||
}
|
||||
|
||||
internal void SetResult()
|
||||
{
|
||||
if (_isSetResult)
|
||||
{
|
||||
Log.Error("WaitCoroutineLock is already SetResult");
|
||||
return;
|
||||
}
|
||||
|
||||
_isSetResult = true;
|
||||
Tcs.SetResult(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsPool()
|
||||
{
|
||||
return _isPool;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <param name="isPool"></param>
|
||||
public void SetIsPool(bool isPool)
|
||||
{
|
||||
_isPool = isPool;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<CustomEntitiesSystemKey>
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Entity管理组件
|
||||
/// </summary>
|
||||
public sealed class EntityComponent : Entity, ISceneUpdate, IAssembly
|
||||
{
|
||||
private readonly OneToManyList<long, Type> _assemblyList = new();
|
||||
private readonly OneToManyList<long, Type> _assemblyHashCodes = new();
|
||||
|
||||
private readonly Dictionary<Type, IAwakeSystem> _awakeSystems = new();
|
||||
private readonly Dictionary<Type, IUpdateSystem> _updateSystems = new();
|
||||
private readonly Dictionary<Type, IDestroySystem> _destroySystems = new();
|
||||
private readonly Dictionary<Type, IDeserializeSystem> _deserializeSystems = new();
|
||||
private readonly Dictionary<Type, IFrameUpdateSystem> _frameUpdateSystem = new();
|
||||
|
||||
private readonly OneToManyList<long, CustomEntitiesSystemKey> _assemblyCustomSystemList = new();
|
||||
private readonly Dictionary<CustomEntitiesSystemKey, ICustomEntitiesSystem> _customEntitiesSystems = new Dictionary<CustomEntitiesSystemKey, ICustomEntitiesSystem>();
|
||||
|
||||
private readonly Dictionary<Type, long> _hashCodes = new Dictionary<Type, long>();
|
||||
private readonly Queue<UpdateQueueInfo> _updateQueue = new Queue<UpdateQueueInfo>();
|
||||
private readonly Queue<FrameUpdateQueueInfo> _frameUpdateQueue = new Queue<FrameUpdateQueueInfo>();
|
||||
private readonly Dictionary<long, UpdateQueueInfo> _updateQueueDic = new Dictionary<long, UpdateQueueInfo>();
|
||||
|
||||
internal async FTask<EntityComponent> 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
|
||||
|
||||
/// <summary>
|
||||
/// 触发实体的唤醒方法
|
||||
/// </summary>
|
||||
/// <param name="entity">实体对象</param>
|
||||
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}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 触发实体的销毁方法
|
||||
/// </summary>
|
||||
/// <param name="entity">实体对象</param>
|
||||
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}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 触发实体的反序列化方法
|
||||
/// </summary>
|
||||
/// <param name="entity">实体对象</param>
|
||||
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
|
||||
|
||||
/// <summary>
|
||||
/// 将实体加入更新队列,准备进行更新
|
||||
/// </summary>
|
||||
/// <param name="entity">实体对象</param>
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 停止实体进行更新
|
||||
/// </summary>
|
||||
/// <param name="entity">实体对象</param>
|
||||
public void StopUpdate(Entity entity)
|
||||
{
|
||||
if (!_updateQueueDic.Remove(entity.RuntimeId, out var updateQueueInfo))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
updateQueueInfo.IsStop = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行实体系统的更新逻辑
|
||||
/// </summary>
|
||||
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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行实体系统的帧更新逻辑
|
||||
/// </summary>
|
||||
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];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放实体系统管理器资源
|
||||
/// </summary>
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<Type, IEvent> _events = new();
|
||||
private readonly OneToManyList<Type, IAsyncEvent> _asyncEvents = new();
|
||||
private readonly OneToManyList<long, EventCache> _assemblyEvents = new();
|
||||
private readonly OneToManyList<long, EventCache> _assemblyAsyncEvents = new();
|
||||
|
||||
internal async FTask<EventComponent> 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
|
||||
|
||||
/// <summary>
|
||||
/// 发布一个值类型的事件数据。
|
||||
/// </summary>
|
||||
/// <typeparam name="TEventData">事件数据类型(值类型)。</typeparam>
|
||||
/// <param name="eventData">事件数据实例。</param>
|
||||
public void Publish<TEventData>(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发布一个继承自 Entity 的事件数据。
|
||||
/// </summary>
|
||||
/// <typeparam name="TEventData">事件数据类型(继承自 Entity)。</typeparam>
|
||||
/// <param name="eventData">事件数据实例。</param>
|
||||
/// <param name="isDisposed">是否释放事件数据。</param>
|
||||
public void Publish<TEventData>(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();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步发布一个值类型的事件数据。
|
||||
/// </summary>
|
||||
/// <typeparam name="TEventData">事件数据类型(值类型)。</typeparam>
|
||||
/// <param name="eventData">事件数据实例。</param>
|
||||
/// <returns>表示异步操作的任务。</returns>
|
||||
public async FTask PublishAsync<TEventData>(TEventData eventData) where TEventData : struct
|
||||
{
|
||||
if (!_asyncEvents.TryGetValue(typeof(TEventData), out var list))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using var tasks = ListPool<FTask>.Create();
|
||||
|
||||
foreach (var @event in list)
|
||||
{
|
||||
tasks.Add(@event.InvokeAsync(eventData));
|
||||
}
|
||||
|
||||
await FTask.WaitAll(tasks);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步发布一个继承自 Entity 的事件数据。
|
||||
/// </summary>
|
||||
/// <typeparam name="TEventData">事件数据类型(继承自 Entity)。</typeparam>
|
||||
/// <param name="eventData">事件数据实例。</param>
|
||||
/// <param name="isDisposed">是否释放事件数据。</param>
|
||||
/// <returns>表示异步操作的任务。</returns>
|
||||
public async FTask PublishAsync<TEventData>(TEventData eventData, bool isDisposed = true) where TEventData : Entity
|
||||
{
|
||||
if (!_asyncEvents.TryGetValue(eventData.GetType(), out var list))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using var tasks = ListPool<FTask>.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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
using System;
|
||||
using Fantasy.Async;
|
||||
|
||||
namespace Fantasy.Event
|
||||
{
|
||||
/// <summary>
|
||||
/// 事件的接口
|
||||
/// </summary>
|
||||
public interface IEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// 用于指定事件的Type
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Type EventType();
|
||||
/// <summary>
|
||||
/// 时间内部使用的入口
|
||||
/// </summary>
|
||||
/// <param name="self"></param>
|
||||
void Invoke(object self);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步事件的接口
|
||||
/// </summary>
|
||||
public interface IAsyncEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="IEvent.EventType"/>
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Type EventType();
|
||||
/// <summary>
|
||||
/// <see cref="IEvent.Invoke"/>
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
FTask InvokeAsync(object self);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 事件的抽象类,要使用事件必须要继承这个抽象接口。
|
||||
/// </summary>
|
||||
/// <typeparam name="T">要监听的事件泛型类型</typeparam>
|
||||
public abstract class EventSystem<T> : IEvent
|
||||
{
|
||||
private readonly Type _selfType = typeof(T);
|
||||
/// <summary>
|
||||
/// <see cref="IEvent.EventType"/>
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public Type EventType()
|
||||
{
|
||||
return _selfType;
|
||||
}
|
||||
/// <summary>
|
||||
/// 事件调用的方法,要在这个方法里编写事件发生的逻辑
|
||||
/// </summary>
|
||||
/// <param name="self"></param>
|
||||
protected abstract void Handler(T self);
|
||||
/// <summary>
|
||||
/// <see cref="IEvent.Invoke"/>
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public void Invoke(object self)
|
||||
{
|
||||
try
|
||||
{
|
||||
Handler((T) self);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"{_selfType.Name} Error {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 异步事件的抽象类,要使用事件必须要继承这个抽象接口。
|
||||
/// </summary>
|
||||
/// <typeparam name="T">要监听的事件泛型类型</typeparam>
|
||||
public abstract class AsyncEventSystem<T> : IAsyncEvent
|
||||
{
|
||||
private readonly Type _selfType = typeof(T);
|
||||
/// <summary>
|
||||
/// <see cref="IEvent.EventType"/>
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public Type EventType()
|
||||
{
|
||||
return _selfType;
|
||||
}
|
||||
/// <summary>
|
||||
/// 事件调用的方法,要在这个方法里编写事件发生的逻辑
|
||||
/// </summary>
|
||||
/// <param name="self"></param>
|
||||
protected abstract FTask Handler(T self);
|
||||
/// <summary>
|
||||
/// <see cref="IEvent.Invoke"/>
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async FTask InvokeAsync(object self)
|
||||
{
|
||||
try
|
||||
{
|
||||
await Handler((T) self);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"{_selfType.Name} Error {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 消息的对象池组件
|
||||
/// </summary>
|
||||
public sealed class MessagePoolComponent : Entity
|
||||
{
|
||||
private int _poolCount;
|
||||
private const int MaxCapacity = ushort.MaxValue;
|
||||
private readonly OneToManyQueue<Type, AMessage> _poolQueue = new OneToManyQueue<Type, AMessage>();
|
||||
private readonly Dictionary<Type, Func<AMessage>> _typeCheckCache = new Dictionary<Type, Func<AMessage>>();
|
||||
/// <summary>
|
||||
/// 销毁组件
|
||||
/// </summary>
|
||||
public override void Dispose()
|
||||
{
|
||||
_poolCount = 0;
|
||||
_poolQueue.Clear();
|
||||
_typeCheckCache.Clear();
|
||||
base.Dispose();
|
||||
}
|
||||
/// <summary>
|
||||
/// 从对象池里获取一个消息,如果没有就创建一个新的
|
||||
/// </summary>
|
||||
/// <typeparam name="T">消息的泛型类型</typeparam>
|
||||
/// <returns></returns>
|
||||
public T Rent<T>() 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="Rent"/>
|
||||
/// </summary>
|
||||
/// <param name="type">消息的类型</param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="NotSupportedException"></exception>
|
||||
[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;
|
||||
}
|
||||
/// <summary>
|
||||
/// 返还一个消息到对象池中
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="Return"/>
|
||||
/// </summary>
|
||||
/// <param name="obj">返还的消息</param>
|
||||
/// <typeparam name="T">返还的消息泛型类型</typeparam>
|
||||
public void Return<T>(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 用于处理Entity下的实体进行数据库分表存储的组件
|
||||
/// </summary>
|
||||
public sealed class SingleCollectionComponent : Entity, IAssembly
|
||||
{
|
||||
private CoroutineLock _coroutineLock;
|
||||
private readonly OneToManyHashSet<Type, string> _collection = new OneToManyHashSet<Type, string>();
|
||||
|
||||
private readonly OneToManyList<long, SingleCollectionInfo> _assemblyCollections =
|
||||
new OneToManyList<long, SingleCollectionInfo>();
|
||||
|
||||
private sealed class SingleCollectionInfo(Type rootType, string collectionName)
|
||||
{
|
||||
public readonly Type RootType = rootType;
|
||||
public readonly string CollectionName = collectionName;
|
||||
}
|
||||
|
||||
internal async FTask<SingleCollectionComponent> 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
|
||||
|
||||
/// <summary>
|
||||
/// 通过数据库获取某一个实体类型下所有的分表数据到当前实体下,并且会自动建立父子关系。
|
||||
/// </summary>
|
||||
/// <param name="entity">实体实例</param>
|
||||
/// <typeparam name="T">实体泛型类型</typeparam>
|
||||
public async FTask GetCollections<T>(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>(entity.Id, true, collectionName);
|
||||
entity.AddComponent(singleCollection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 存储当前实体下支持分表的组件到数据中,包括存储实体本身。
|
||||
/// </summary>
|
||||
/// <param name="entity">实体实例</param>
|
||||
/// <typeparam name="T">实体泛型类型</typeparam>
|
||||
public async FTask SaveCollections<T>(T entity) where T : Entity, ISingleCollectionRoot
|
||||
{
|
||||
using var collections = ListPool<Entity>.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
|
||||
@@ -0,0 +1,10 @@
|
||||
using Fantasy.Event;
|
||||
|
||||
namespace Fantasy.Timer
|
||||
{
|
||||
/// <summary>
|
||||
/// 计时器抽象类,提供了一个基础框架,用于创建处理计时器事件的具体类。
|
||||
/// </summary>
|
||||
/// <typeparam name="T">事件的类型参数</typeparam>
|
||||
public abstract class TimerHandler<T> : EventSystem<T> { }
|
||||
}
|
||||
@@ -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<ScheduledTask>
|
||||
// {
|
||||
// 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<ScheduledTask> 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;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
@@ -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<ScheduledTask>[] _wheel;
|
||||
// private readonly Queue<ScheduledTask> _tasksToReschedule = new Queue<ScheduledTask>();
|
||||
// private readonly Dictionary<long, ScheduledTask> _taskDictionary = new Dictionary<long, ScheduledTask>();
|
||||
//
|
||||
// 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<ScheduledTask>[_wheelSize];
|
||||
// for (var i = 0; i < wheelSize; i++)
|
||||
// {
|
||||
// _wheel[i] = new LinkedList<ScheduledTask>();
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// 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);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<TimerComponent>
|
||||
{
|
||||
protected override void Update(TimerComponent self)
|
||||
{
|
||||
self.Update();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 时间调度组件
|
||||
/// </summary>
|
||||
public sealed class TimerComponent : Entity
|
||||
{
|
||||
/// <summary>
|
||||
/// 使用系统时间创建的计时器核心。
|
||||
/// </summary>
|
||||
public TimerSchedulerNet Net { get; private set; }
|
||||
#if FANTASY_UNITY
|
||||
/// <summary>
|
||||
/// 使用 Unity 时间创建的计时器核心。
|
||||
/// </summary>
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 基于系统事件的任务调度系统
|
||||
/// </summary>
|
||||
public sealed class TimerSchedulerNet
|
||||
{
|
||||
private readonly Scene _scene;
|
||||
private long _idGenerator;
|
||||
private long _minTime; // 最小时间
|
||||
private readonly Queue<long> _timeOutTime = new Queue<long>();
|
||||
private readonly Queue<long> _timeOutTimerIds = new Queue<long>();
|
||||
private readonly Dictionary<long, TimerAction> _timerActions = new Dictionary<long, TimerAction>();
|
||||
private readonly SortedOneToManyList<long, long> _timeId = new(); // 时间与计时器ID的有序一对多列表
|
||||
private long GetId => ++_idGenerator;
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="scene">当前的Scene</param>
|
||||
public TimerSchedulerNet(Scene scene)
|
||||
{
|
||||
_scene = scene;
|
||||
}
|
||||
|
||||
private long Now()
|
||||
{
|
||||
return TimeHelper.Now;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 驱动方法,只有调用这个方法任务系统才会正常运转。
|
||||
/// </summary>
|
||||
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<bool>)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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步等待指定时间。
|
||||
/// </summary>
|
||||
/// <param name="time">等待的时间长度。</param>
|
||||
/// <param name="cancellationToken">取消令牌。</param>
|
||||
/// <returns>等待是否成功。</returns>
|
||||
public async FTask<bool> WaitAsync(long time, FCancellationToken cancellationToken = null)
|
||||
{
|
||||
if (time <= 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var now = Now();
|
||||
var timerId = GetId;
|
||||
var tcs = FTask<bool>.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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步等待直到指定时间。
|
||||
/// </summary>
|
||||
/// <param name="tillTime">等待的目标时间。</param>
|
||||
/// <param name="cancellationToken">取消令牌。</param>
|
||||
/// <returns>等待是否成功。</returns>
|
||||
public async FTask<bool> WaitTillAsync(long tillTime, FCancellationToken cancellationToken = null)
|
||||
{
|
||||
var now = Now();
|
||||
|
||||
if (now >= tillTime)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var timerId = GetId;
|
||||
var tcs = FTask<bool>.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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步等待一帧时间。
|
||||
/// </summary>
|
||||
/// <returns>等待是否成功。</returns>
|
||||
public async FTask WaitFrameAsync()
|
||||
{
|
||||
#if FANTASY_NET
|
||||
await WaitAsync(100);
|
||||
#else
|
||||
await WaitAsync(1);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个只执行一次的计时器,直到指定时间
|
||||
/// </summary>
|
||||
/// <param name="time">计时器执行的目标时间。</param>
|
||||
/// <param name="action">计时器回调方法。</param>
|
||||
/// <returns></returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个只执行一次的计时器,直到指定时间。
|
||||
/// </summary>
|
||||
/// <param name="tillTime">计时器执行的目标时间。</param>
|
||||
/// <param name="action">计时器回调方法。</param>
|
||||
/// <returns>计时器的 ID。</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个只执行一次的计时器,用于发布指定类型的事件。
|
||||
/// </summary>
|
||||
/// <typeparam name="T">事件类型。</typeparam>
|
||||
/// <param name="time">计时器执行的延迟时间。</param>
|
||||
/// <param name="timerHandlerType">事件处理器类型。</param>
|
||||
/// <returns>计时器的 ID。</returns>
|
||||
public long OnceTimer<T>(long time, T timerHandlerType) where T : struct
|
||||
{
|
||||
void OnceTimerVoid()
|
||||
{
|
||||
_scene.EventComponent.Publish(timerHandlerType);
|
||||
}
|
||||
|
||||
return OnceTimer(time, OnceTimerVoid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个只执行一次的计时器,直到指定时间,用于发布指定类型的事件。
|
||||
/// </summary>
|
||||
/// <typeparam name="T">事件类型。</typeparam>
|
||||
/// <param name="tillTime">计时器执行的目标时间。</param>
|
||||
/// <param name="timerHandlerType">事件处理器类型。</param>
|
||||
/// <returns>计时器的 ID。</returns>
|
||||
public long OnceTillTimer<T>(long tillTime, T timerHandlerType) where T : struct
|
||||
{
|
||||
void OnceTillTimerVoid()
|
||||
{
|
||||
_scene.EventComponent.Publish(timerHandlerType);
|
||||
}
|
||||
|
||||
return OnceTillTimer(tillTime, OnceTillTimerVoid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个帧任务
|
||||
/// </summary>
|
||||
/// <param name="action"></param>
|
||||
/// <returns></returns>
|
||||
public long FrameTimer(Action action)
|
||||
{
|
||||
#if FANTASY_NET
|
||||
return RepeatedTimerInner(100, action);
|
||||
#else
|
||||
return RepeatedTimerInner(0, action);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个重复执行的计时器。
|
||||
/// </summary>
|
||||
/// <param name="time">计时器重复间隔的时间。</param>
|
||||
/// <param name="action">计时器回调方法。</param>
|
||||
/// <returns>计时器的 ID。</returns>
|
||||
public long RepeatedTimer(long time, Action action)
|
||||
{
|
||||
if (time < 0)
|
||||
{
|
||||
Log.Error($"time too small: {time}");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return RepeatedTimerInner(time, action);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个重复执行的计时器,用于发布指定类型的事件。
|
||||
/// </summary>
|
||||
/// <typeparam name="T">事件类型。</typeparam>
|
||||
/// <param name="time">计时器重复间隔的时间。</param>
|
||||
/// <param name="timerHandlerType">事件处理器类型。</param>
|
||||
/// <returns>计时器的 ID。</returns>
|
||||
public long RepeatedTimer<T>(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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除指定 ID 的计时器。
|
||||
/// </summary>
|
||||
/// <param name="timerId"></param>
|
||||
/// <returns></returns>
|
||||
public bool Remove(ref long timerId)
|
||||
{
|
||||
var id = timerId;
|
||||
timerId = 0;
|
||||
return Remove(id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除指定 ID 的计时器。
|
||||
/// </summary>
|
||||
/// <param name="timerId">计时器的 ID。</param>
|
||||
public bool Remove(long timerId)
|
||||
{
|
||||
return timerId != 0 && _timerActions.Remove(timerId, out _);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<long> _timeOutTime = new Queue<long>();
|
||||
private readonly Queue<long> _timeOutTimerIds = new Queue<long>();
|
||||
private readonly Dictionary<long, TimerAction> _timerActions = new Dictionary<long, TimerAction>();
|
||||
private readonly SortedOneToManyList<long, long> _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<bool>)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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步等待指定时间。
|
||||
/// </summary>
|
||||
/// <param name="time">等待的时间长度。</param>
|
||||
/// <param name="cancellationToken">可选的取消令牌。</param>
|
||||
/// <returns>等待是否成功。</returns>
|
||||
public async FTask<bool> WaitAsync(long time, FCancellationToken cancellationToken = null)
|
||||
{
|
||||
if (time <= 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var now = Now();
|
||||
var timerId = GetId;
|
||||
var tcs = FTask<bool>.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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步等待直到指定时间。
|
||||
/// </summary>
|
||||
/// <param name="tillTime">等待的目标时间。</param>
|
||||
/// <param name="cancellationToken">可选的取消令牌。</param>
|
||||
/// <returns>等待是否成功。</returns>
|
||||
public async FTask<bool> WaitTillAsync(long tillTime, FCancellationToken cancellationToken = null)
|
||||
{
|
||||
var now = Now();
|
||||
|
||||
if (now >= tillTime)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var timerId = GetId;
|
||||
var tcs = FTask<bool>.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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步等待一帧时间。
|
||||
/// </summary>
|
||||
/// <returns>等待是否成功。</returns>
|
||||
public async FTask WaitFrameAsync(FCancellationToken cancellationToken = null)
|
||||
{
|
||||
await WaitAsync(1, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个只执行一次的计时器,直到指定时间
|
||||
/// </summary>
|
||||
/// <param name="time">计时器执行的目标时间。</param>
|
||||
/// <param name="action">计时器回调方法。</param>
|
||||
/// <returns></returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个只执行一次的计时器,直到指定时间。
|
||||
/// </summary>
|
||||
/// <param name="tillTime">计时器执行的目标时间。</param>
|
||||
/// <param name="action">计时器回调方法。</param>
|
||||
/// <returns>计时器的 ID。</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个只执行一次的计时器,用于发布指定类型的事件。
|
||||
/// </summary>
|
||||
/// <typeparam name="T">事件类型。</typeparam>
|
||||
/// <param name="time">计时器执行的延迟时间。</param>
|
||||
/// <param name="timerHandlerType">事件处理器类型。</param>
|
||||
/// <returns>计时器的 ID。</returns>
|
||||
public long OnceTimer<T>(long time, T timerHandlerType) where T : struct
|
||||
{
|
||||
void OnceTimerVoid()
|
||||
{
|
||||
_scene.EventComponent.Publish(timerHandlerType);
|
||||
}
|
||||
|
||||
return OnceTimer(time, OnceTimerVoid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个只执行一次的计时器,直到指定时间,用于发布指定类型的事件。
|
||||
/// </summary>
|
||||
/// <typeparam name="T">事件类型。</typeparam>
|
||||
/// <param name="tillTime">计时器执行的目标时间。</param>
|
||||
/// <param name="timerHandlerType">事件处理器类型。</param>
|
||||
/// <returns>计时器的 ID。</returns>
|
||||
public long OnceTillTimer<T>(long tillTime, T timerHandlerType) where T : struct
|
||||
{
|
||||
void OnceTillTimerVoid()
|
||||
{
|
||||
_scene.EventComponent.Publish(timerHandlerType);
|
||||
}
|
||||
|
||||
return OnceTillTimer(tillTime, OnceTillTimerVoid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个帧任务
|
||||
/// </summary>
|
||||
/// <param name="action"></param>
|
||||
/// <returns></returns>
|
||||
public long FrameTimer(Action action)
|
||||
{
|
||||
return RepeatedTimerInner(1, action);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个重复执行的计时器。
|
||||
/// </summary>
|
||||
/// <param name="time">计时器重复间隔的时间。</param>
|
||||
/// <param name="action">计时器回调方法。</param>
|
||||
/// <returns>计时器的 ID。</returns>
|
||||
public long RepeatedTimer(long time, Action action)
|
||||
{
|
||||
if (time < 0)
|
||||
{
|
||||
Log.Error($"time too small: {time}");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return RepeatedTimerInner(time, action);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个重复执行的计时器,用于发布指定类型的事件。
|
||||
/// </summary>
|
||||
/// <typeparam name="T">事件类型。</typeparam>
|
||||
/// <param name="time">计时器重复间隔的时间。</param>
|
||||
/// <param name="timerHandlerType">事件处理器类型。</param>
|
||||
/// <returns>计时器的 ID。</returns>
|
||||
public long RepeatedTimer<T>(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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除指定 ID 的计时器。
|
||||
/// </summary>
|
||||
/// <param name="timerId"></param>
|
||||
/// <returns></returns>
|
||||
public bool Remove(ref long timerId)
|
||||
{
|
||||
var id = timerId;
|
||||
timerId = 0;
|
||||
return Remove(id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除指定 ID 的计时器。
|
||||
/// </summary>
|
||||
/// <param name="timerId">计时器的 ID。</param>
|
||||
public bool Remove(long timerId)
|
||||
{
|
||||
return timerId != 0 && _timerActions.Remove(timerId, out _);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
namespace Fantasy.Timer
|
||||
{
|
||||
/// <summary>
|
||||
/// 枚举对象TimerType
|
||||
/// </summary>
|
||||
public enum TimerType
|
||||
{
|
||||
/// <summary>
|
||||
/// None
|
||||
/// </summary>
|
||||
None,
|
||||
/// <summary>
|
||||
/// 一次等待定时器
|
||||
/// </summary>
|
||||
OnceWaitTimer,
|
||||
/// <summary>
|
||||
/// 一次性定时器
|
||||
/// </summary>
|
||||
OnceTimer,
|
||||
/// <summary>
|
||||
/// 重复定时器
|
||||
/// </summary>
|
||||
RepeatedTimer
|
||||
}
|
||||
}
|
||||
1051
Fantasy/Fantays.Console/Runtime/Core/Entitas/Entity.cs
Normal file
1051
Fantasy/Fantays.Console/Runtime/Core/Entitas/Entity.cs
Normal file
File diff suppressed because it is too large
Load Diff
66
Fantasy/Fantays.Console/Runtime/Core/Entitas/EntityPool.cs
Normal file
66
Fantasy/Fantays.Console/Runtime/Core/Entitas/EntityPool.cs
Normal file
@@ -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<T> : List<T>, IPool where T : Entity
|
||||
{
|
||||
private bool _isPool;
|
||||
/// <summary>
|
||||
/// 获取一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsPool()
|
||||
{
|
||||
return _isPool;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <param name="isPool"></param>
|
||||
public void SetIsPool(bool isPool)
|
||||
{
|
||||
_isPool = isPool;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class EntityListPool<T> : PoolCore<EntityList<T>> where T : Entity
|
||||
{
|
||||
public EntityListPool() : base(4096) { }
|
||||
}
|
||||
|
||||
internal sealed class EntitySortedDictionary<TM, TN> : SortedDictionary<TM, TN>, IPool where TN : Entity
|
||||
{
|
||||
private bool _isPool;
|
||||
/// <summary>
|
||||
/// 获取一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsPool()
|
||||
{
|
||||
return _isPool;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置一个值,该值指示当前实例是否为对象池中的实例。
|
||||
/// </summary>
|
||||
/// <param name="isPool"></param>
|
||||
public void SetIsPool(bool isPool)
|
||||
{
|
||||
_isPool = isPool;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class EntitySortedDictionaryPool<TM, TN> : PoolCore<EntitySortedDictionary<TM, TN>> where TN : Entity
|
||||
{
|
||||
public EntitySortedDictionaryPool() : base(4096) { }
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 实体引用检查组件
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public struct EntityReference<T> 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将一个实体转换为EntityReference
|
||||
/// </summary>
|
||||
/// <param name="t">实体泛型类型</param>
|
||||
/// <returns>返回一个EntityReference</returns>
|
||||
public static implicit operator EntityReference<T>(T t)
|
||||
{
|
||||
return new EntityReference<T>(t);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将一个EntityReference转换为实体
|
||||
/// </summary>
|
||||
/// <param name="v">实体泛型类型</param>
|
||||
/// <returns>当实体已经被销毁过会返回null</returns>
|
||||
public static implicit operator T(EntityReference<T> v)
|
||||
{
|
||||
if (v._entity == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (v._entity.RuntimeId != v._runTimeId)
|
||||
{
|
||||
v._entity = null;
|
||||
}
|
||||
|
||||
return v._entity;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
namespace Fantasy.Entitas.Interface
|
||||
{
|
||||
/// <summary>
|
||||
/// Entity保存到数据库的时候会根据子组件设置分离存储特性分表存储在不同的集合表中
|
||||
/// </summary>
|
||||
public interface ISingleCollectionRoot { }
|
||||
public static class SingleCollectionRootChecker<T> where T : Entity
|
||||
{
|
||||
public static bool IsSupported { get; }
|
||||
|
||||
static SingleCollectionRootChecker()
|
||||
{
|
||||
IsSupported = typeof(ISingleCollectionRoot).IsAssignableFrom(typeof(T));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
namespace Fantasy.Entitas.Interface
|
||||
{
|
||||
/// <summary>
|
||||
/// Entity支持数据库
|
||||
/// </summary>
|
||||
// ReSharper disable once InconsistentNaming
|
||||
public interface ISupportedDataBase { }
|
||||
|
||||
public static class SupportedDataBaseChecker<T> where T : Entity
|
||||
{
|
||||
public static bool IsSupported { get; }
|
||||
|
||||
static SupportedDataBaseChecker()
|
||||
{
|
||||
IsSupported = typeof(ISupportedDataBase).IsAssignableFrom(typeof(T));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
|
||||
namespace Fantasy.Entitas.Interface
|
||||
{
|
||||
/// <summary>
|
||||
/// 支持再一个组件里添加多个同类型组件
|
||||
/// </summary>
|
||||
public interface ISupportedMultiEntity : IDisposable { }
|
||||
|
||||
public static class SupportedMultiEntityChecker<T> where T : Entity
|
||||
{
|
||||
public static bool IsSupported { get; }
|
||||
|
||||
static SupportedMultiEntityChecker()
|
||||
{
|
||||
IsSupported = typeof(ISupportedMultiEntity).IsAssignableFrom(typeof(T));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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的子集
|
||||
/// <summary>
|
||||
/// 定义实体支持单一集合存储的接口。当实体需要单独存储在一个集合中,并且在保存到数据库时不会与父组件一起保存在同一个集合中时,应实现此接口。
|
||||
/// </summary>
|
||||
public interface ISupportedSingleCollection { }
|
||||
public static class SupportedSingleCollectionChecker<T> where T : Entity
|
||||
{
|
||||
public static bool IsSupported { get; }
|
||||
|
||||
static SupportedSingleCollectionChecker()
|
||||
{
|
||||
IsSupported = typeof(ISupportedSingleCollection).IsAssignableFrom(typeof(T));
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 表示用于指定实体的单一集合存储属性。此属性用于配合 <see cref="ISupportedSingleCollection"/> 接口使用,
|
||||
/// 用于定义实体属于哪个父实体的子集合,以及在数据库中使用的集合名称。
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = false)]
|
||||
public class SingleCollectionAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取父实体的类型,指示此实体是属于哪个父实体的子集合。
|
||||
/// </summary>
|
||||
public readonly Type RootType;
|
||||
/// <summary>
|
||||
/// 获取在数据库中使用的集合名称。
|
||||
/// </summary>
|
||||
public readonly string CollectionName;
|
||||
/// <summary>
|
||||
/// 初始化 <see cref="SingleCollectionAttribute"/> 类的新实例,指定父实体类型和集合名称。
|
||||
/// </summary>
|
||||
/// <param name="rootType">父实体的类型。</param>
|
||||
/// <param name="collectionName">在数据库中使用的集合名称。</param>
|
||||
public SingleCollectionAttribute(Type rootType, string collectionName)
|
||||
{
|
||||
RootType = rootType;
|
||||
CollectionName = collectionName;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
#if FANTASY_NET
|
||||
namespace Fantasy.Entitas.Interface
|
||||
{
|
||||
/// <summary>
|
||||
/// Entity支持传送
|
||||
/// </summary>
|
||||
public interface ISupportedTransfer { }
|
||||
public static class SupportedTransferChecker<T> where T : Entity
|
||||
{
|
||||
public static bool IsSupported { get; }
|
||||
|
||||
static SupportedTransferChecker()
|
||||
{
|
||||
IsSupported = typeof(ISupportedTransfer).IsAssignableFrom(typeof(T));
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using Fantasy.Async;
|
||||
|
||||
namespace Fantasy.Entitas.Interface
|
||||
{
|
||||
internal interface IAwakeSystem : IEntitiesSystem { }
|
||||
/// <summary>
|
||||
/// 实体的Awake事件的抽象接口
|
||||
/// </summary>
|
||||
/// <typeparam name="T">实体的泛型类型</typeparam>
|
||||
public abstract class AwakeSystem<T> : IAwakeSystem where T : Entity
|
||||
{
|
||||
/// <summary>
|
||||
/// 实体的类型
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public Type EntitiesType() => typeof(T);
|
||||
/// <summary>
|
||||
/// 事件的抽象方法,需要自己实现这个方法
|
||||
/// </summary>
|
||||
/// <param name="self">触发事件的实体实例</param>
|
||||
protected abstract void Awake(T self);
|
||||
/// <summary>
|
||||
/// 框架内部调用的触发Awake的方法。
|
||||
/// </summary>
|
||||
/// <param name="self">触发事件的实体实例</param>
|
||||
public void Invoke(Entity self)
|
||||
{
|
||||
Awake((T) self);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
using System;
|
||||
|
||||
namespace Fantasy.Entitas.Interface
|
||||
{
|
||||
/// <summary>
|
||||
/// 自定义组件事件系统接口
|
||||
/// 如果需要自定义组件事件系统,请继承此接口。
|
||||
/// 这个接口内部使用。不对外开放。
|
||||
/// </summary>
|
||||
internal interface ICustomEntitiesSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// 事件类型
|
||||
/// 用于触发这个组件事件关键因素。
|
||||
/// </summary>
|
||||
int CustomEventType { get; }
|
||||
/// <summary>
|
||||
/// 实体的类型
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Type EntitiesType();
|
||||
/// <summary>
|
||||
/// 框架内部调用的触发事件方法
|
||||
/// </summary>
|
||||
/// <param name="entity"></param>
|
||||
void Invoke(Entity entity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 自定义组件事件系统抽象类
|
||||
/// 如果需要自定义组件事件系统,请继承此抽象类。
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public abstract class CustomSystem<T> : ICustomEntitiesSystem where T : Entity
|
||||
{
|
||||
/// <summary>
|
||||
/// 这个1表示是一个自定义事件类型,执行这个事件是时候需要用到这个1.
|
||||
/// </summary>
|
||||
public abstract int CustomEventType { get; }
|
||||
/// <summary>
|
||||
/// 事件的抽象方法,需要自己实现这个方法
|
||||
/// </summary>
|
||||
/// <param name="self">触发事件的实体实例</param>
|
||||
protected abstract void Custom(T self);
|
||||
/// <summary>
|
||||
/// 实体的类型
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public abstract Type EntitiesType();
|
||||
/// <summary>
|
||||
/// 框架内部调用的触发Awake的方法。
|
||||
/// </summary>
|
||||
/// <param name="self">触发事件的实体实例</param>
|
||||
public void Invoke(Entity self)
|
||||
{
|
||||
Custom((T) self);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using Fantasy.Async;
|
||||
|
||||
namespace Fantasy.Entitas.Interface
|
||||
{
|
||||
internal interface IDeserializeSystem : IEntitiesSystem { }
|
||||
/// <summary>
|
||||
/// 实体的反序列化事件的抽象接口
|
||||
/// </summary>
|
||||
/// <typeparam name="T">实体的泛型数据</typeparam>
|
||||
public abstract class DeserializeSystem<T> : IDeserializeSystem where T : Entity
|
||||
{
|
||||
/// <summary>
|
||||
/// 实体的类型
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public Type EntitiesType() => typeof(T);
|
||||
/// <summary>
|
||||
/// 事件的抽象方法,需要自己实现这个方法
|
||||
/// </summary>
|
||||
/// <param name="self">触发事件的实体实例</param>
|
||||
protected abstract void Deserialize(T self);
|
||||
/// <summary>
|
||||
/// 框架内部调用的触发Deserialize的方法
|
||||
/// </summary>
|
||||
/// <param name="self">触发事件的实体实例</param>
|
||||
public void Invoke(Entity self)
|
||||
{
|
||||
Deserialize((T) self);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using Fantasy.Async;
|
||||
|
||||
namespace Fantasy.Entitas.Interface
|
||||
{
|
||||
internal interface IDestroySystem : IEntitiesSystem { }
|
||||
/// <summary>
|
||||
/// 实体销毁事件的抽象接口
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public abstract class DestroySystem<T> : IDestroySystem where T : Entity
|
||||
{
|
||||
/// <summary>
|
||||
/// 实体的类型
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public Type EntitiesType() => typeof(T);
|
||||
/// <summary>
|
||||
/// 事件的抽象方法,需要自己实现这个方法
|
||||
/// </summary>
|
||||
/// <param name="self">触发事件的实体实例</param>
|
||||
protected abstract void Destroy(T self);
|
||||
/// <summary>
|
||||
/// 框架内部调用的触发Destroy的方法
|
||||
/// </summary>
|
||||
/// <param name="self"></param>
|
||||
public void Invoke(Entity self)
|
||||
{
|
||||
Destroy((T) self);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using Fantasy.Async;
|
||||
|
||||
namespace Fantasy.Entitas.Interface
|
||||
{
|
||||
/// <summary>
|
||||
/// ECS事件系统的核心接口,任何事件都是要继承这个接口
|
||||
/// </summary>
|
||||
public interface IEntitiesSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// 实体的类型
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Type EntitiesType();
|
||||
/// <summary>
|
||||
/// 框架内部调用的触发事件方法
|
||||
/// </summary>
|
||||
/// <param name="entity"></param>
|
||||
void Invoke(Entity entity);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
|
||||
namespace Fantasy.Entitas.Interface
|
||||
{
|
||||
internal interface IFrameUpdateSystem : IEntitiesSystem { }
|
||||
/// <summary>
|
||||
/// 帧更新时间的抽象接口
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public abstract class FrameUpdateSystem<T> : IFrameUpdateSystem where T : Entity
|
||||
{
|
||||
/// <summary>
|
||||
/// 实体的类型
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public Type EntitiesType() => typeof(T);
|
||||
/// <summary>
|
||||
/// 事件的抽象方法,需要自己实现这个方法
|
||||
/// </summary>
|
||||
/// <param name="self">触发事件的实体实例</param>
|
||||
protected abstract void FrameUpdate(T self);
|
||||
/// <summary>
|
||||
/// 框架内部调用的触发FrameUpdate的方法
|
||||
/// </summary>
|
||||
/// <param name="self"></param>
|
||||
public void Invoke(Entity self)
|
||||
{
|
||||
FrameUpdate((T) self);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
|
||||
namespace Fantasy.Entitas.Interface
|
||||
{
|
||||
internal interface IUpdateSystem : IEntitiesSystem { }
|
||||
/// <summary>
|
||||
/// Update事件的抽象接口
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public abstract class UpdateSystem<T> : IUpdateSystem where T : Entity
|
||||
{
|
||||
/// <summary>
|
||||
/// 实体的类型
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public Type EntitiesType() => typeof(T);
|
||||
/// <summary>
|
||||
/// 事件的抽象方法,需要自己实现这个方法
|
||||
/// </summary>
|
||||
/// <param name="self">触发事件的实体实例</param>
|
||||
protected abstract void Update(T self);
|
||||
/// <summary>
|
||||
/// 框架内部调用的触发Update的方法
|
||||
/// </summary>
|
||||
/// <param name="self">触发事件的实体实例</param>
|
||||
public void Invoke(Entity self)
|
||||
{
|
||||
Update((T) self);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<TStateMachine>(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<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : INotifyCompletion where TStateMachine : IAsyncStateMachine
|
||||
{
|
||||
awaiter.OnCompleted(stateMachine.MoveNext);
|
||||
}
|
||||
[DebuggerHidden]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(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) { }
|
||||
}
|
||||
}
|
||||
@@ -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<TStateMachine>(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<TAwaiter, TStateMachine>(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<TAwaiter, TStateMachine>(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<T>
|
||||
{
|
||||
public FTask<T> Task
|
||||
{
|
||||
[DebuggerHidden]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get;
|
||||
}
|
||||
|
||||
[DebuggerHidden]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static AsyncFTaskMethodBuilder<T> Create()
|
||||
{
|
||||
return new AsyncFTaskMethodBuilder<T>(FTask<T>.Create());
|
||||
}
|
||||
|
||||
public AsyncFTaskMethodBuilder(FTask<T> fTask)
|
||||
{
|
||||
Task = fTask;
|
||||
}
|
||||
|
||||
[DebuggerHidden]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Start<TStateMachine>(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<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : INotifyCompletion where TStateMachine : IAsyncStateMachine
|
||||
{
|
||||
awaiter.OnCompleted(stateMachine.MoveNext);
|
||||
}
|
||||
|
||||
[DebuggerHidden]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : ICriticalNotifyCompletion where TStateMachine : IAsyncStateMachine
|
||||
{
|
||||
awaiter.UnsafeOnCompleted(stateMachine.MoveNext);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetStateMachine(IAsyncStateMachine stateMachine) { }
|
||||
}
|
||||
}
|
||||
@@ -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<TStateMachine>(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<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : INotifyCompletion where TStateMachine : IAsyncStateMachine
|
||||
{
|
||||
awaiter.OnCompleted(stateMachine.MoveNext);
|
||||
}
|
||||
|
||||
[DebuggerHidden]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : ICriticalNotifyCompletion where TStateMachine : IAsyncStateMachine
|
||||
{
|
||||
awaiter.UnsafeOnCompleted(stateMachine.MoveNext);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetStateMachine(IAsyncStateMachine stateMachine) { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Fantasy.Async
|
||||
{
|
||||
/// <summary>
|
||||
/// 用于FTask取消的CancellationToken
|
||||
/// </summary>
|
||||
public sealed class FCancellationToken : IDisposable
|
||||
{
|
||||
private bool _isDispose;
|
||||
private bool _isCancel;
|
||||
private readonly HashSet<Action> _actions = new HashSet<Action>();
|
||||
/// <summary>
|
||||
/// 当前CancellationToken是否已经取消过了
|
||||
/// </summary>
|
||||
public bool IsCancel => _isDispose || _isCancel;
|
||||
/// <summary>
|
||||
/// 添加一个取消要执行的Action
|
||||
/// </summary>
|
||||
/// <param name="action"></param>
|
||||
public void Add(Action action)
|
||||
{
|
||||
if (_isDispose)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_actions.Add(action);
|
||||
}
|
||||
/// <summary>
|
||||
/// 移除一个取消要执行的Action
|
||||
/// </summary>
|
||||
/// <param name="action"></param>
|
||||
public void Remove(Action action)
|
||||
{
|
||||
if (_isDispose)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_actions.Remove(action);
|
||||
}
|
||||
/// <summary>
|
||||
/// 取消CancellationToken
|
||||
/// </summary>
|
||||
public void Cancel()
|
||||
{
|
||||
if (IsCancel)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isCancel = true;
|
||||
|
||||
foreach (var action in _actions)
|
||||
{
|
||||
try
|
||||
{
|
||||
action.Invoke();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e);
|
||||
}
|
||||
}
|
||||
|
||||
_actions.Clear();
|
||||
}
|
||||
/// <summary>
|
||||
/// 销毁掉CancellationToken,会执行Cancel方法。
|
||||
/// </summary>
|
||||
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<FCancellationToken> Caches = new ConcurrentQueue<FCancellationToken>();
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个新的CancellationToken
|
||||
/// </summary>
|
||||
public static FCancellationToken ToKen
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!Caches.TryDequeue(out var fCancellationToken))
|
||||
{
|
||||
fCancellationToken = new FCancellationToken();
|
||||
}
|
||||
|
||||
fCancellationToken._isCancel = false;
|
||||
fCancellationToken._isDispose = false;
|
||||
return fCancellationToken;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 一个异步任务
|
||||
/// </summary>
|
||||
public partial class FTask
|
||||
{
|
||||
private bool _isPool;
|
||||
#if FANTASY_WEBGL
|
||||
private static readonly Queue<FTask> Caches = new Queue<FTask>();
|
||||
#else
|
||||
private static readonly ConcurrentQueue<FTask> Caches = new ConcurrentQueue<FTask>();
|
||||
#endif
|
||||
/// <summary>
|
||||
/// 创建一个空的任务
|
||||
/// </summary>
|
||||
public static FTaskCompleted CompletedTask => new FTaskCompleted();
|
||||
|
||||
private FTask() { }
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个任务
|
||||
/// </summary>
|
||||
/// <param name="isPool">是否从对象池中创建</param>
|
||||
/// <returns></returns>
|
||||
[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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 一个异步任务
|
||||
/// </summary>
|
||||
/// <typeparam name="T">任务的泛型类型</typeparam>
|
||||
public partial class FTask<T>
|
||||
{
|
||||
private bool _isPool;
|
||||
#if FANTASY_WEBGL
|
||||
private static readonly Queue<FTask<T>> Caches = new Queue<FTask<T>>();
|
||||
#else
|
||||
private static readonly ConcurrentQueue<FTask<T>> Caches = new ConcurrentQueue<FTask<T>>();
|
||||
#endif
|
||||
/// <summary>
|
||||
/// 创建一个任务
|
||||
/// </summary>
|
||||
/// <param name="isPool">是否从对象池中创建</param>
|
||||
/// <returns></returns>
|
||||
[DebuggerHidden]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static FTask<T> Create(bool isPool = true)
|
||||
{
|
||||
if (!isPool)
|
||||
{
|
||||
return new FTask<T>();
|
||||
}
|
||||
|
||||
if (!Caches.TryDequeue(out var fTask))
|
||||
{
|
||||
fTask = new FTask<T>();
|
||||
}
|
||||
|
||||
fTask._isPool = true;
|
||||
return fTask;
|
||||
}
|
||||
|
||||
private FTask() { }
|
||||
|
||||
private void Return()
|
||||
{
|
||||
if (!_isPool || Caches.Count > 2000)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_callBack = null;
|
||||
_status = STaskStatus.Pending;
|
||||
Caches.Enqueue(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
/// <summary>
|
||||
/// 异步等待指定时间
|
||||
/// </summary>
|
||||
/// <param name="scene"></param>
|
||||
/// <param name="time"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
public static FTask<bool> Wait(Scene scene, long time, FCancellationToken cancellationToken = null)
|
||||
{
|
||||
return scene.TimerComponent.Net.WaitAsync(time, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步等待直到指定时间
|
||||
/// </summary>
|
||||
/// <param name="scene"></param>
|
||||
/// <param name="time"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
public static FTask<bool> WaitTill(Scene scene, long time, FCancellationToken cancellationToken = null)
|
||||
{
|
||||
return scene.TimerComponent.Net.WaitTillAsync(time, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步等待一帧时间
|
||||
/// </summary>
|
||||
/// <param name="scene"></param>
|
||||
/// <returns></returns>
|
||||
public static FTask WaitFrame(Scene scene)
|
||||
{
|
||||
return scene.TimerComponent.Net.WaitFrameAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个只执行一次的计时器,直到指定时间
|
||||
/// </summary>
|
||||
/// <param name="scene"></param>
|
||||
/// <param name="time"></param>
|
||||
/// <param name="action"></param>
|
||||
/// <returns></returns>
|
||||
public static long OnceTimer(Scene scene, long time, Action action)
|
||||
{
|
||||
return scene.TimerComponent.Net.OnceTimer(time, action);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个只执行一次的计时器,直到指定时间。
|
||||
/// </summary>
|
||||
/// <param name="scene"></param>
|
||||
/// <param name="time"></param>
|
||||
/// <param name="action"></param>
|
||||
/// <returns></returns>
|
||||
public static long OnceTillTimer(Scene scene, long time, Action action)
|
||||
{
|
||||
return scene.TimerComponent.Net.OnceTillTimer(time, action);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个只执行一次的计时器,用于发布指定类型的事件。
|
||||
/// </summary>
|
||||
/// <param name="scene"></param>
|
||||
/// <param name="time"></param>
|
||||
/// <param name="timerHandlerType"></param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
public static long OnceTimer<T>(Scene scene, long time, T timerHandlerType) where T : struct
|
||||
{
|
||||
return scene.TimerComponent.Net.OnceTimer<T>(time, timerHandlerType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个只执行一次的计时器,直到指定时间,用于发布指定类型的事件。
|
||||
/// </summary>
|
||||
/// <param name="scene"></param>
|
||||
/// <param name="tillTime"></param>
|
||||
/// <param name="timerHandlerType"></param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
public static long OnceTillTimer<T>(Scene scene, long tillTime, T timerHandlerType) where T : struct
|
||||
{
|
||||
return scene.TimerComponent.Net.OnceTillTimer<T>(tillTime, timerHandlerType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个重复执行的计时器。
|
||||
/// </summary>
|
||||
/// <param name="scene"></param>
|
||||
/// <param name="time"></param>
|
||||
/// <param name="action"></param>
|
||||
/// <returns></returns>
|
||||
public static long RepeatedTimer(Scene scene, long time, Action action)
|
||||
{
|
||||
return scene.TimerComponent.Net.RepeatedTimer(time, action);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个重复执行的计时器,用于发布指定类型的事件。
|
||||
/// </summary>
|
||||
/// <param name="scene"></param>
|
||||
/// <param name="time"></param>
|
||||
/// <param name="timerHandlerType"></param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
public static long RepeatedTimer<T>(Scene scene, long time, T timerHandlerType) where T : struct
|
||||
{
|
||||
return scene.TimerComponent.Net.RepeatedTimer<T>(time, timerHandlerType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除指定 ID 的计时器。
|
||||
/// </summary>
|
||||
/// <param name="scene"></param>
|
||||
/// <param name="timerId"></param>
|
||||
/// <returns></returns>
|
||||
public static bool RemoveTimer(Scene scene, ref long timerId)
|
||||
{
|
||||
return scene.TimerComponent.Net.Remove(ref timerId);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Unity
|
||||
|
||||
#if FANTASY_UNITY
|
||||
/// <summary>
|
||||
/// 异步等待指定时间。(使用Unity的Time时间)
|
||||
/// </summary>
|
||||
/// <param name="scene"></param>
|
||||
/// <param name="time"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
public static FTask<bool> UnityWait(Scene scene, long time, FCancellationToken cancellationToken = null)
|
||||
{
|
||||
return scene.TimerComponent.Unity.WaitAsync(time, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步等待直到指定时间。(使用Unity的Time时间)
|
||||
/// </summary>
|
||||
/// <param name="scene"></param>
|
||||
/// <param name="time"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
public static FTask<bool> UnityWaitTill(Scene scene, long time, FCancellationToken cancellationToken = null)
|
||||
{
|
||||
return scene.TimerComponent.Unity.WaitTillAsync(time, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步等待一帧时间。(使用Unity的Time时间)
|
||||
/// </summary>
|
||||
/// <param name="scene"></param>
|
||||
/// <returns></returns>
|
||||
public static FTask UnityWaitFrame(Scene scene)
|
||||
{
|
||||
return scene.TimerComponent.Unity.WaitFrameAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个只执行一次的计时器,直到指定时间。(使用Unity的Time时间)
|
||||
/// </summary>
|
||||
/// <param name="scene"></param>
|
||||
/// <param name="time"></param>
|
||||
/// <param name="action"></param>
|
||||
/// <returns></returns>
|
||||
public static long UnityOnceTimer(Scene scene, long time, Action action)
|
||||
{
|
||||
return scene.TimerComponent.Unity.OnceTimer(time, action);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个只执行一次的计时器,直到指定时间。(使用Unity的Time时间)
|
||||
/// </summary>
|
||||
/// <param name="scene"></param>
|
||||
/// <param name="time"></param>
|
||||
/// <param name="action"></param>
|
||||
/// <returns></returns>
|
||||
public static long UnityOnceTillTimer(Scene scene, long time, Action action)
|
||||
{
|
||||
return scene.TimerComponent.Unity.OnceTillTimer(time, action);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个只执行一次的计时器,用于发布指定类型的事件。(使用Unity的Time时间)
|
||||
/// </summary>
|
||||
/// <param name="scene"></param>
|
||||
/// <param name="time"></param>
|
||||
/// <param name="timerHandlerType"></param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
public static long UnityOnceTimer<T>(Scene scene, long time, T timerHandlerType) where T : struct
|
||||
{
|
||||
return scene.TimerComponent.Unity.OnceTimer<T>(time, timerHandlerType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个只执行一次的计时器,直到指定时间,用于发布指定类型的事件。(使用Unity的Time时间)
|
||||
/// </summary>
|
||||
/// <param name="scene"></param>
|
||||
/// <param name="tillTime"></param>
|
||||
/// <param name="timerHandlerType"></param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
public static long UnityOnceTillTimer<T>(Scene scene, long tillTime, T timerHandlerType) where T : struct
|
||||
{
|
||||
return scene.TimerComponent.Unity.OnceTillTimer<T>(tillTime, timerHandlerType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个重复执行的计时器。(使用Unity的Time时间)
|
||||
/// </summary>
|
||||
/// <param name="scene"></param>
|
||||
/// <param name="time"></param>
|
||||
/// <param name="action"></param>
|
||||
/// <returns></returns>
|
||||
public static long UnityRepeatedTimer(Scene scene, long time, Action action)
|
||||
{
|
||||
return scene.TimerComponent.Unity.RepeatedTimer(time, action);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个重复执行的计时器,用于发布指定类型的事件。(使用Unity的Time时间)
|
||||
/// </summary>
|
||||
/// <param name="scene"></param>
|
||||
/// <param name="time"></param>
|
||||
/// <param name="timerHandlerType"></param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
public static long UnityRepeatedTimer<T>(Scene scene, long time, T timerHandlerType) where T : struct
|
||||
{
|
||||
return scene.TimerComponent.Unity.RepeatedTimer<T>(time, timerHandlerType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除指定 ID 的计时器。(使用Unity的Time时间)
|
||||
/// </summary>
|
||||
/// <param name="scene"></param>
|
||||
/// <param name="timerId"></param>
|
||||
/// <returns></returns>
|
||||
public static bool UnityRemoveTimer(Scene scene, ref long timerId)
|
||||
{
|
||||
return scene.TimerComponent.Unity.Remove(ref timerId);
|
||||
}
|
||||
#endif
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// 创建并运行一个异步任务
|
||||
/// </summary>
|
||||
/// <param name="factory"></param>
|
||||
/// <returns></returns>
|
||||
public static FTask Run(Func<FTask> factory)
|
||||
{
|
||||
return factory();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建并运行一个带有结果的异步任务
|
||||
/// </summary>
|
||||
/// <param name="factory"></param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
public static FTask<T> Run<T>(Func<FTask<T>> factory)
|
||||
{
|
||||
return factory();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 等待所有任务完成
|
||||
/// </summary>
|
||||
/// <param name="tasks"></param>
|
||||
public static async FTask WaitAll(List<FTask> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 等待其中一个任务完成
|
||||
/// </summary>
|
||||
/// <param name="tasks"></param>
|
||||
public static async FTask WaitAny(List<FTask> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
263
Fantasy/Fantays.Console/Runtime/Core/FTask/Task/FTask.cs
Normal file
263
Fantasy/Fantays.Console/Runtime/Core/FTask/Task/FTask.cs
Normal file
@@ -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<T> : 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<T> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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) { }
|
||||
}
|
||||
}
|
||||
29
Fantasy/Fantays.Console/Runtime/Core/FTask/Task/FVoid.cs
Normal file
29
Fantasy/Fantays.Console/Runtime/Core/FTask/Task/FVoid.cs
Normal file
@@ -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) { }
|
||||
}
|
||||
}
|
||||
388
Fantasy/Fantays.Console/Runtime/Core/Helper/ByteHelper.cs
Normal file
388
Fantasy/Fantays.Console/Runtime/Core/Helper/ByteHelper.cs
Normal file
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 提供字节操作辅助方法的静态类。
|
||||
/// </summary>
|
||||
public static class ByteHelper
|
||||
{
|
||||
private static readonly string[] Suffix = { "Byte", "KB", "MB", "GB", "TB" };
|
||||
|
||||
/// <summary>
|
||||
/// 从指定的文件流中读取一个 64 位整数。
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从指定的文件流中读取一个 32 位整数。
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从指定的内存流中读取一个 64 位整数。
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从指定的内存流中读取一个 32 位整数。
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将字节转换为十六进制字符串表示。
|
||||
/// </summary>
|
||||
public static string ToHex(this byte b)
|
||||
{
|
||||
return b.ToString("X2");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将字节数组转换为十六进制字符串表示。
|
||||
/// </summary>
|
||||
public static string ToHex(this byte[] bytes)
|
||||
{
|
||||
var stringBuilder = new StringBuilder();
|
||||
foreach (var b in bytes)
|
||||
{
|
||||
stringBuilder.Append(b.ToString("X2"));
|
||||
}
|
||||
|
||||
return stringBuilder.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将字节数组按指定格式转换为十六进制字符串表示。
|
||||
/// </summary>
|
||||
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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将字节数组的指定范围按十六进制格式转换为字符串表示。
|
||||
/// </summary>
|
||||
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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将字节数组转换为默认编码的字符串表示。
|
||||
/// </summary>
|
||||
public static string ToStr(this byte[] bytes)
|
||||
{
|
||||
return Encoding.Default.GetString(bytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将字节数组的指定范围按默认编码转换为字符串表示。
|
||||
/// </summary>
|
||||
public static string ToStr(this byte[] bytes, int index, int count)
|
||||
{
|
||||
return Encoding.Default.GetString(bytes, index, count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将字节数组转换为 UTF-8 编码的字符串表示。
|
||||
/// </summary>
|
||||
public static string Utf8ToStr(this byte[] bytes)
|
||||
{
|
||||
return Encoding.UTF8.GetString(bytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将字节数组的指定范围按 UTF-8 编码转换为字符串表示。
|
||||
/// </summary>
|
||||
public static string Utf8ToStr(this byte[] bytes, int index, int count)
|
||||
{
|
||||
return Encoding.UTF8.GetString(bytes, index, count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将无符号整数写入字节数组的指定偏移位置。
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将有符号整数写入字节数组的指定偏移位置。
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将字节写入字节数组的指定偏移位置。
|
||||
/// </summary>
|
||||
public static void WriteTo(this byte[] bytes, int offset, byte num)
|
||||
{
|
||||
bytes[offset] = num;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将有符号短整数写入字节数组的指定偏移位置。
|
||||
/// </summary>
|
||||
public static void WriteTo(this byte[] bytes, int offset, short num)
|
||||
{
|
||||
bytes[offset] = (byte)(num & 0xff);
|
||||
bytes[offset + 1] = (byte)((num & 0xff00) >> 8);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将无符号短整数写入字节数组的指定偏移位置。
|
||||
/// </summary>
|
||||
public static void WriteTo(this byte[] bytes, int offset, ushort num)
|
||||
{
|
||||
bytes[offset] = (byte)(num & 0xff);
|
||||
bytes[offset + 1] = (byte)((num & 0xff00) >> 8);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将字节数转换为可读的速度表示。
|
||||
/// </summary>
|
||||
/// <param name="byteCount">字节数</param>
|
||||
/// <returns>可读的速度表示</returns>
|
||||
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]}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将字节数转换为可读的速度表示。
|
||||
/// </summary>
|
||||
/// <param name="byteCount">字节数</param>
|
||||
/// <returns>可读的速度表示</returns>
|
||||
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]}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 合并两个字节数组。
|
||||
/// </summary>
|
||||
/// <param name="bytes">第一个字节数组</param>
|
||||
/// <param name="otherBytes">第二个字节数组</param>
|
||||
/// <returns>合并后的字节数组</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据int值获取字节数组。
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="buffer"></param>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
[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
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据int值获取字节数组。
|
||||
/// </summary>
|
||||
/// <param name="memoryStream"></param>
|
||||
/// <param name="value"></param>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void WriteBytes(this MemoryStream memoryStream, int value)
|
||||
{
|
||||
using var memoryOwner = MemoryPool<byte>.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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据uint值获取字节数组。
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="buffer"></param>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
[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
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据uint值获取字节数组。
|
||||
/// </summary>
|
||||
/// <param name="memoryStream"></param>
|
||||
/// <param name="value"></param>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void WriteBytes(this MemoryStream memoryStream, uint value)
|
||||
{
|
||||
using var memoryOwner = MemoryPool<byte>.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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据int值获取字节数组。
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="buffer"></param>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
[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
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据uint值获取字节数组。
|
||||
/// </summary>
|
||||
/// <param name="memoryStream"></param>
|
||||
/// <param name="value"></param>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void WriteBytes(this MemoryStream memoryStream, long value)
|
||||
{
|
||||
using var memoryOwner = MemoryPool<byte>.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c5743903d34d474a818b5c2bafa31459
|
||||
timeCreated: 1726021902
|
||||
@@ -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<AUnityDownload> 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<AssetBundle> DownloadAssetBundle(string url, bool monitor = false, FCancellationToken cancellationToken = null)
|
||||
{
|
||||
return new DownloadAssetBundle(Scene, this).StartDownload(url, monitor, cancellationToken);
|
||||
}
|
||||
|
||||
public FTask<AudioClip> DownloadAudioClip(string url, AudioType audioType, bool monitor = false, FCancellationToken cancellationToken = null)
|
||||
{
|
||||
return new DownloadAudioClip(Scene, this).StartDownload(url, audioType, monitor, cancellationToken);
|
||||
}
|
||||
|
||||
public FTask<Sprite> DownloadSprite(string url, bool monitor = false, FCancellationToken cancellationToken = null)
|
||||
{
|
||||
return new DownloadSprite(Scene, this).StartDownload(url, monitor, cancellationToken);
|
||||
}
|
||||
|
||||
public FTask<Texture> DownloadTexture(string url, bool monitor = false, FCancellationToken cancellationToken = null)
|
||||
{
|
||||
return new DownloadTexture(Scene, this).StartDownload(url, monitor, cancellationToken);
|
||||
}
|
||||
|
||||
public FTask<string> DownloadText(string url, bool monitor = false, FCancellationToken cancellationToken = null)
|
||||
{
|
||||
return new DownloadText(Scene, this).StartDownload(url, monitor, cancellationToken);
|
||||
}
|
||||
|
||||
public FTask<byte[]> DownloadByte(string url, bool monitor = false, FCancellationToken cancellationToken = null)
|
||||
{
|
||||
return new DownloadByte(Scene, this).StartDownload(url, monitor, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5715816370e84842aaab799c9776a5e4
|
||||
timeCreated: 1726023436
|
||||
@@ -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<AssetBundle> StartDownload(string url, bool monitor, FCancellationToken cancellationToken = null)
|
||||
{
|
||||
var task = FTask<AssetBundle>.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
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 07cbb9a010ed4fe1b90919f81847b9ea
|
||||
timeCreated: 1726023471
|
||||
@@ -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<AudioClip> StartDownload(string url, AudioType audioType, bool monitor, FCancellationToken cancellationToken = null)
|
||||
{
|
||||
var task = FTask<AudioClip>.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
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 66d3739ec33845148534e6ecaf134b73
|
||||
timeCreated: 1726023476
|
||||
@@ -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<byte[]> StartDownload(string url, bool monitor, FCancellationToken cancellationToken = null)
|
||||
{
|
||||
var task = FTask<byte[]>.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
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ae87f3ea9f4e4c9ebabedf45b0bb83b1
|
||||
timeCreated: 1726023481
|
||||
@@ -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<Sprite> StartDownload(string url, bool monitor, FCancellationToken cancellationToken = null)
|
||||
{
|
||||
var task = FTask<Sprite>.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
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0c2a0f442e974169b7d8b7a5878fe0e6
|
||||
timeCreated: 1726023487
|
||||
@@ -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<string> StartDownload(string url, bool monitor, FCancellationToken cancellationToken = null)
|
||||
{
|
||||
var task = FTask<string>.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
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4284aafa8572453cb75920d2d58e9c50
|
||||
timeCreated: 1726023491
|
||||
@@ -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<Texture> StartDownload(string url, bool monitor, FCancellationToken cancellationToken = null)
|
||||
{
|
||||
var task = FTask<Texture>.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
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5eaa6023378844ebb51e4b80425d8a4e
|
||||
timeCreated: 1726023496
|
||||
63
Fantasy/Fantays.Console/Runtime/Core/Helper/EncryptHelper.cs
Normal file
63
Fantasy/Fantays.Console/Runtime/Core/Helper/EncryptHelper.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace Fantasy.Helper
|
||||
{
|
||||
/// <summary>
|
||||
/// 提供计算 MD5 散列值的辅助方法。
|
||||
/// </summary>
|
||||
public static partial class EncryptHelper
|
||||
{
|
||||
private static readonly SHA256 Sha256Hash = SHA256.Create();
|
||||
|
||||
/// <summary>
|
||||
/// 计算指定字节数组的Sha256。
|
||||
/// </summary>
|
||||
/// <param name="bytes"></param>
|
||||
/// <returns></returns>
|
||||
public static byte[] ComputeSha256Hash(byte[] bytes)
|
||||
{
|
||||
#if FANTASY_UNITY
|
||||
using var sha256Hash = SHA256.Create();
|
||||
return sha256Hash.ComputeHash(bytes);
|
||||
#else
|
||||
return SHA256.HashData(bytes);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算指定文件的 MD5 散列值。
|
||||
/// </summary>
|
||||
/// <param name="filePath">要计算散列值的文件路径。</param>
|
||||
/// <returns>表示文件的 MD5 散列值的字符串。</returns>
|
||||
public static string FileMD5(string filePath)
|
||||
{
|
||||
using var file = new FileStream(filePath, FileMode.Open);
|
||||
return FileMD5(file);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算给定文件流的 MD5 散列值。
|
||||
/// </summary>
|
||||
/// <param name="fileStream">要计算散列值的文件流。</param>
|
||||
/// <returns>表示文件流的 MD5 散列值的字符串。</returns>
|
||||
public static string FileMD5(FileStream fileStream)
|
||||
{
|
||||
var md5 = MD5.Create();
|
||||
return md5.ComputeHash(fileStream).ToHex("x2");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算给定字节数组的 MD5 散列值。
|
||||
/// </summary>
|
||||
/// <param name="bytes">要计算散列值的字节数组。</param>
|
||||
/// <returns>表示字节数组的 MD5 散列值的字符串。</returns>
|
||||
public static string BytesMD5(byte[] bytes)
|
||||
{
|
||||
var md5 = MD5.Create();
|
||||
bytes = md5.ComputeHash(bytes);
|
||||
return bytes.ToHex("x2");
|
||||
}
|
||||
}
|
||||
}
|
||||
186
Fantasy/Fantays.Console/Runtime/Core/Helper/FileHelper.cs
Normal file
186
Fantasy/Fantays.Console/Runtime/Core/Helper/FileHelper.cs
Normal file
@@ -0,0 +1,186 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Fantasy.Helper
|
||||
{
|
||||
/// <summary>
|
||||
/// 文件操作助手类,提供了各种文件操作方法。
|
||||
/// </summary>
|
||||
public static partial class FileHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取相对路径的完整路径。
|
||||
/// </summary>
|
||||
/// <param name="relativePath">相对路径。</param>
|
||||
/// <returns>完整路径。</returns>
|
||||
public static string GetFullPath(string relativePath)
|
||||
{
|
||||
return Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), relativePath));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取相对路径的完整路径。
|
||||
/// </summary>
|
||||
/// <param name="relativePath">相对于指定的目录的相对路径。</param>
|
||||
/// <param name="srcDir">指定的目录</param>
|
||||
/// <returns>完整路径。</returns>
|
||||
public static string GetFullPath(string relativePath, string srcDir)
|
||||
{
|
||||
return Path.GetFullPath(Path.Combine(srcDir, relativePath));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取相对路径的的文本信息。
|
||||
/// </summary>
|
||||
/// <param name="relativePath"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task<string> GetTextByRelativePath(string relativePath)
|
||||
{
|
||||
var fullPath = GetFullPath(relativePath);
|
||||
return await File.ReadAllTextAsync(fullPath, Encoding.UTF8);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取绝对路径的的文本信息。
|
||||
/// </summary>
|
||||
/// <param name="fullPath"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task<string> GetText(string fullPath)
|
||||
{
|
||||
return await File.ReadAllTextAsync(fullPath, Encoding.UTF8);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据文件夹路径创建文件夹,如果文件夹不存在会自动创建文件夹。
|
||||
/// </summary>
|
||||
/// <param name="directoryPath"></param>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将文件复制到目标路径,如果目标目录不存在会自动创建目录。
|
||||
/// </summary>
|
||||
/// <param name="sourceFile">源文件路径。</param>
|
||||
/// <param name="destinationFile">目标文件路径。</param>
|
||||
/// <param name="overwrite">是否覆盖已存在的目标文件。</param>
|
||||
public static void Copy(string sourceFile, string destinationFile, bool overwrite)
|
||||
{
|
||||
CreateDirectory(destinationFile);
|
||||
File.Copy(sourceFile, destinationFile, overwrite);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取文件路径内的所有文件夹路径。
|
||||
/// </summary>
|
||||
/// <param name="filePath">文件路径。</param>
|
||||
/// <returns>文件夹路径列表。</returns>
|
||||
public static IEnumerable<string> 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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将文件夹内的所有内容复制到目标位置。
|
||||
/// </summary>
|
||||
/// <param name="sourceDirectory">源文件夹路径。</param>
|
||||
/// <param name="destinationDirectory">目标文件夹路径。</param>
|
||||
/// <param name="overwrite">是否覆盖已存在的文件。</param>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取目录下的所有文件
|
||||
/// </summary>
|
||||
/// <param name="folderPath">文件夹路径。</param>
|
||||
/// <param name="searchPattern">需要查找的文件通配符</param>
|
||||
/// <param name="searchOption">查找的类型</param>
|
||||
/// <returns></returns>
|
||||
public static string[] GetDirectoryFile(string folderPath, string searchPattern, SearchOption searchOption)
|
||||
{
|
||||
return Directory.GetFiles(folderPath, searchPattern, searchOption);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清空文件夹内的所有文件。
|
||||
/// </summary>
|
||||
/// <param name="folderPath">文件夹路径。</param>
|
||||
public static void ClearDirectoryFile(string folderPath)
|
||||
{
|
||||
if (!Directory.Exists(folderPath))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var files = Directory.GetFiles(folderPath);
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
File.Delete(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
229
Fantasy/Fantays.Console/Runtime/Core/Helper/HashCodeHelper.cs
Normal file
229
Fantasy/Fantays.Console/Runtime/Core/Helper/HashCodeHelper.cs
Normal file
@@ -0,0 +1,229 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
// ReSharper disable InconsistentNaming
|
||||
|
||||
namespace Fantasy.Helper
|
||||
{
|
||||
/// <summary>
|
||||
/// HashCode算法帮助类
|
||||
/// </summary>
|
||||
public static partial class HashCodeHelper
|
||||
{
|
||||
private static readonly SHA256 Sha256Hash = SHA256.Create();
|
||||
|
||||
/// <summary>
|
||||
/// 计算两个字符串的HashCode
|
||||
/// </summary>
|
||||
/// <param name="a"></param>
|
||||
/// <param name="b"></param>
|
||||
/// <returns></returns>
|
||||
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
|
||||
/// <summary>
|
||||
/// 使用bkdr算法生成一个long的值
|
||||
/// </summary>
|
||||
/// <param name="str"></param>
|
||||
/// <returns></returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用MurmurHash3算法生成一个uint的值
|
||||
/// </summary>
|
||||
/// <param name="str"></param>
|
||||
/// <returns></returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用MurmurHash3算法生成一个long的值
|
||||
/// </summary>
|
||||
/// <param name="str"></param>
|
||||
/// <returns></returns>
|
||||
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
|
||||
/// <summary>
|
||||
/// 使用bkdr算法生成一个long的值
|
||||
/// </summary>
|
||||
/// <param name="str"></param>
|
||||
/// <returns></returns>
|
||||
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;
|
||||
}
|
||||
/// <summary>
|
||||
/// 使用MurmurHash3算法生成一个uint的值
|
||||
/// </summary>
|
||||
/// <param name="str"></param>
|
||||
/// <returns></returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用MurmurHash3算法生成一个long的值
|
||||
/// </summary>
|
||||
/// <param name="str"></param>
|
||||
/// <returns></returns>
|
||||
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
|
||||
/// <summary>
|
||||
/// 根据字符串计算一个Hash值
|
||||
/// </summary>
|
||||
/// <param name="rawData"></param>
|
||||
/// <returns></returns>
|
||||
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];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// HTTP帮助类
|
||||
/// </summary>
|
||||
public static partial class HttpClientHelper
|
||||
{
|
||||
private static readonly HttpClient Client = new HttpClient(new HttpClientHandler
|
||||
{
|
||||
ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => true
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// 用Post方式请求string数据
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="content"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="Exception"></exception>
|
||||
public static async FTask<string> 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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 用Get方式请求string数据
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="Exception"></exception>
|
||||
public static async FTask<string> 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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 用Post方式请求JSON数据,并自动把JSON转换为对象。
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="content"></param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
public static async FTask<T> CallByPost<T>(string url, HttpContent content)
|
||||
{
|
||||
return await Deserialize<T>(url, await Client.PostAsync(url, content));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 用Post方式请求JSON数据,并自动把JSON转换为对象。
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="method"></param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
public static async FTask<T> CallByPost<T>(string url, HttpMethod method)
|
||||
{
|
||||
return await Deserialize<T>(url, await Client.SendAsync(new HttpRequestMessage(method, url)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 用Get方式请求JSON数据,并自动把JSON转换为对象。
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
public static async FTask<T> CallByGet<T>(string url)
|
||||
{
|
||||
return await Deserialize<T>(url, await Client.GetAsync(url));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 用Post方式请求JSON数据,并自动把JSON转换为对象。
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="id"></param>
|
||||
/// <param name="authentication"></param>
|
||||
/// <param name="method"></param>
|
||||
/// <param name="params"></param>
|
||||
/// <typeparam name="TRequest"></typeparam>
|
||||
/// <typeparam name="TResponse"></typeparam>
|
||||
/// <returns></returns>
|
||||
public static async FTask<TResponse> Call<TRequest, TResponse>(string url, int id, AuthenticationHeaderValue authentication, string method, params object[] @params) where TRequest : class, IJsonRpcRequest, new()
|
||||
{
|
||||
var request = Pool<TRequest>.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<TResponse>(url, await client.PostAsync(url, content));
|
||||
return response;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Pool<TRequest>.Return(request);
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
private static async FTask<T> Deserialize<T>(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<T>();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f8005f3a1a1945a2929442f82832e765
|
||||
timeCreated: 1726023741
|
||||
@@ -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<HttpClientPool> Pools = new Queue<HttpClientPool>();
|
||||
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
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user