Before 优化 机场

This commit is contained in:
CortexCore
2025-03-10 18:06:44 +08:00
parent 350e6d67b2
commit 1f4e20f512
178 changed files with 17534 additions and 821 deletions

View File

@@ -1,8 +1,10 @@
using System;
using System.Buffers;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security.AccessControl;
using System.Threading;
using Microsoft.Extensions.DependencyInjection;
@@ -10,51 +12,77 @@ using Microsoft.Extensions.Logging;
namespace BITKit.Entities
{
public class EntitiesService:IEntitiesService,IDisposable
public class EntitiesService : IEntitiesService, IDisposable
{
private static int _count;
private readonly ILogger<EntitiesService> _logger;
private readonly IFixedTicker _ticker;
private static int _count;
private static readonly ConcurrentQueue<IEntity> OnAddQueue = new();
private readonly ConcurrentQueue<IEntity> _onAddQueue = new();
private readonly ConcurrentDictionary<int, HashSet<int>> _typeCaches = new();
public EntitiesService(ILogger<EntitiesService> logger, IFixedTicker ticker)
{
_count++;
_logger = logger;
_ticker = ticker;
_count++;
logger.LogInformation($"已创建EntitiesService,当前有:{_count}个实例");
_ticker.Add(OnTick);
}
private void OnTick(float obj)
{
while (OnAddQueue.TryDequeue(out var entity))
while (_onAddQueue.TryDequeue(out var entity))
{
OnAdd?.Invoke(entity);
foreach (var serviceDescriptor in entity.ServiceCollection)
{
var typeHash = serviceDescriptor.ServiceType.GetHashCode();
var hashSet = _typeCaches.GetOrCreate(typeHash);
hashSet.Add(entity.Id);
}
}
}
private static readonly ConcurrentDictionary<int, IEntity> Entities = new();
public event Action<IEntity> OnAdd;
public event Action<IEntity> OnRemove;
IReadOnlyDictionary<int, IEntity> IEntitiesService.Entities => Entities;
public bool Register(IEntity entity)
{
if (!Entities.TryAdd(entity.Id, entity)) return false;
OnAddQueue.Enqueue(entity);
_onAddQueue.Enqueue(entity);
return true;
}
public bool UnRegister(IEntity entity)
{
if (!Entities.TryRemove(entity.Id, out _)) return false;
OnRemove?.Invoke(entity);
OnRemove?.Invoke(entity);
foreach (var serviceDescriptor in entity.ServiceCollection)
{
var typeHash = serviceDescriptor.ServiceType.GetHashCode();
var hashSet = _typeCaches.GetOrCreate(typeHash);
hashSet.Remove(entity.Id);
}
return true;
}
public CancellationToken CancellationToken => _cancellationTokenSource.Token;
private readonly CancellationTokenSource _cancellationTokenSource = new();
public IEntity Get(int id)
{
return Entities[id];
@@ -70,194 +98,247 @@ namespace BITKit.Entities
return Entities.GetOrAdd(id, factory);
}
public IEntity[] Query<T>()
public Span<T> QueryComponents<T>()
{
throw new NotImplementedException("Obsoleted");
}
var pool = ArrayPool<T>.Shared;
var hashset = _typeCaches.GetOrCreate(typeof(T).GetHashCode());
public T[] QueryComponents<T>()
{
var list = new List<T>();
foreach (var entity in Entities.Values)
var count = hashset.Count;
var array = pool.Rent(count); // ✅ 从池中获取数组,避免 GC
var i = 0;
foreach (var id in hashset)
{
if (entity.ServiceProvider.GetService<T>() is { } t1)
{
list.Add(t1);
}
array[i] = Entities[id].ServiceProvider.GetRequiredService<T>();
i++;
}
return list.ToArray();
// return _queryCache.GetOrAdd(typeof(T), type =>
// {
// return _entities.Values.Where(entity => entity.TryGetComponent(out T component)).ToArray();
// }).Cast<T>().ToArray();
// 🚀 使用 `GCHandle` 固定数组,防止被 GC 移动
var handle = GCHandle.Alloc(array, GCHandleType.Pinned);
try
{
return new Span<T>(array, 0, count); // ✅ 返回 `Span<T>`,无额外拷贝
}
finally
{
handle.Free(); // ✅ 释放 `GCHandle`,防止内存泄漏
pool.Return(array); // ✅ 归还 ArrayPool
}
}
public (T, T1)[] QueryComponents<T, T1>()
{
var list = new List<(T, T1)>();
foreach (var entity in Entities.Values)
{
if (entity.ServiceProvider.GetService<T>() is { } t1 &&
entity.ServiceProvider.GetService<T1>() is { } t2)
{
list.Add((t1, t2));
}
}
return list.ToArray();
// List<(T, T1)> list = new();
// foreach (var entity in _entities.Values)
// {
// if (entity.TryGetComponent(out T t) && entity.TryGetComponent(out T1 t1))
// list.Add((t, t1));
// }
// return list.ToArray();
}
public (T, T1, T2)[] QueryComponents<T, T1, T2>()
{
var list = new List<(T, T1, T2)>();
foreach (var entity in Entities.Values)
{
if (entity.ServiceProvider.GetService<T>() is { } t1 &&
entity.ServiceProvider.GetService<T1>() is { } t2 &&
entity.ServiceProvider.GetService<T2>() is { } t3)
{
list.Add((t1, t2, t3));
}
}
return list.ToArray();
// List<(T, T1, T2)> list = new();
// foreach (var entity in _entities.Values)
// {
// if (entity.TryGetComponent(out T t) && entity.TryGetComponent(out T1 t1) && entity.TryGetComponent(out T2 t2))
// list.Add((t, t1, t2));
// }
// return list.ToArray();
}
public (T, T1, T2, T3)[] QueryComponents<T, T1, T2, T3>()
public Span<(T, T1)> QueryComponents<T, T1>()
{
var list = new List<(T, T1, T2, T3)>();
foreach (var entity in Entities.Values)
{
if (entity.ServiceProvider.GetService<T>() is { } t1 &&
entity.ServiceProvider.GetService<T1>() is { } t2 &&
entity.ServiceProvider.GetService<T2>() is { } t3 &&
entity.ServiceProvider.GetService<T3>() is { } t4)
{
list.Add((t1, t2, t3, t4));
}
}
return list.ToArray();
// List<(T, T1, T2, T3)> list = new();
// foreach (var entity in _entities.Values)
// {
// if (entity.TryGetComponent(out T t) && entity.TryGetComponent(out T1 t1) && entity.TryGetComponent(out T2 t2) && entity.TryGetComponent(out T3 t3))
// list.Add((t, t1, t2, t3));
// }
// return list.ToArray();
}
var pool = ArrayPool<(T, T1)>.Shared;
var hashset = _typeCaches.GetOrCreate(typeof(T).GetHashCode());
var t1Set = _typeCaches.GetOrCreate(typeof(T1).GetHashCode());
public (T, T1, T2, T3, T4)[] QueryComponents<T, T1, T2, T3, T4>()
{
var list = new List<(T, T1, T2, T3, T4)>();
foreach (var entity in Entities.Values)
{
if (entity.ServiceProvider.GetService<T>() is { } t1 &&
entity.ServiceProvider.GetService<T1>() is { } t2 &&
entity.ServiceProvider.GetService<T2>() is { } t3 &&
entity.ServiceProvider.GetService<T3>() is { } t4 &&
entity.ServiceProvider.GetService<T4>() is { } t5)
{
list.Add((t1, t2, t3, t4, t5));
}
}
return list.ToArray();
// List<(T, T1, T2, T3, T4)> list = new();
// foreach (var entity in _entities.Values)
// {
// if (entity.TryGetComponent(out T t) && entity.TryGetComponent(out T1 t1) && entity.TryGetComponent(out T2 t2) && entity.TryGetComponent(out T3 t3) && entity.TryGetComponent(out T4 t4))
// list.Add((t, t1, t2, t3, t4));
// }
// return list.ToArray();
}
var count = hashset.Count;
var array = pool.Rent(count);
public (T, T1, T2, T3, T4, T5)[] QueryComponents<T, T1, T2, T3, T4, T5>()
{
var list = new List<(T, T1, T2, T3, T4, T5)>();
foreach (var entity in Entities.Values)
var i = 0;
foreach (var id in hashset.Intersect(t1Set))
{
if (entity.ServiceProvider.GetService<T>() is { } t1 &&
entity.ServiceProvider.GetService<T1>() is { } t2 &&
entity.ServiceProvider.GetService<T2>() is { } t3 &&
entity.ServiceProvider.GetService<T3>() is { } t4 &&
entity.ServiceProvider.GetService<T4>() is { } t5 &&
entity.ServiceProvider.GetService<T5>() is { } t6)
{
list.Add((t1, t2, t3, t4, t5, t6));
}
}
return list.ToArray();
// List<(T, T1, T2, T3, T4, T5)> list = new();
// foreach (var entity in _entities.Values)
// {
// if (entity.TryGetComponent(out T t) && entity.TryGetComponent(out T1 t1) && entity.TryGetComponent(out T2 t2) && entity.TryGetComponent(out T3 t3) && entity.TryGetComponent(out T4 t4) && entity.TryGetComponent(out T5 t5))
// list.Add((t, t1, t2, t3, t4, t5));
// }
// return list.ToArray();
}
public (T, T1, T2, T3, T4, T5, T6)[] QueryComponents<T, T1, T2, T3, T4, T5, T6>()
{
var list = new List<(T, T1, T2, T3, T4, T5, T6)>();
foreach (var entity in Entities.Values)
{
if (entity.ServiceProvider.GetService<T>() is { } t1 &&
entity.ServiceProvider.GetService<T1>() is { } t2 &&
entity.ServiceProvider.GetService<T2>() is { } t3 &&
entity.ServiceProvider.GetService<T3>() is { } t4 &&
entity.ServiceProvider.GetService<T4>() is { } t5 &&
entity.ServiceProvider.GetService<T5>() is { } t6 &&
entity.ServiceProvider.GetService<T6>() is { } t7)
{
list.Add((t1, t2, t3, t4, t5, t6, t7));
}
var entity = Entities[id];
array[i] = (entity.ServiceProvider.GetRequiredService<T>(),
entity.ServiceProvider.GetRequiredService<T1>());
i++;
}
return list.ToArray();
// List<(T, T1, T2, T3, T4, T5, T6)> list = new();
// foreach (var entity in _entities.Values)
// {
// if (entity.TryGetComponent(out T t) && entity.TryGetComponent(out T1 t1) && entity.TryGetComponent(out T2 t2) && entity.TryGetComponent(out T3 t3) && entity.TryGetComponent(out T4 t4) && entity.TryGetComponent(out T5 t5) && entity.TryGetComponent(out T6 t6))
// list.Add((t, t1, t2, t3, t4, t5, t6));
// }
// return list.ToArray();
var handle = GCHandle.Alloc(array, GCHandleType.Pinned);
try
{
return new Span<(T, T1)>(array, 0, count);
}
finally
{
handle.Free();
pool.Return(array);
}
}
public ValueTuple<T, T1, T2, T3, T4, T5, T6, TRest>[] QueryComponents<T, T1, T2, T3, T4, T5, T6, TRest>()
where TRest : struct
public Span<(T, T1, T2)> QueryComponents<T, T1, T2>()
{
var list = new List<ValueTuple<T, T1, T2, T3, T4, T5, T6, TRest>>();
foreach (var entity in Entities.Values)
var pool = ArrayPool<(T, T1, T2)>.Shared;
var hashset = _typeCaches.GetOrCreate(typeof(T).GetHashCode());
var t1Set = _typeCaches.GetOrCreate(typeof(T1).GetHashCode());
var t2Set = _typeCaches.GetOrCreate(typeof(T2).GetHashCode());
var count = hashset.Count;
var array = pool.Rent(count);
var i = 0;
foreach (var id in hashset.Intersect(t1Set).Intersect(t2Set))
{
if (entity.ServiceProvider.GetService<T>() is { } t1 &&
entity.ServiceProvider.GetService<T1>() is { } t2 &&
entity.ServiceProvider.GetService<T2>() is { } t3 &&
entity.ServiceProvider.GetService<T3>() is { } t4 &&
entity.ServiceProvider.GetService<T4>() is { } t5 &&
entity.ServiceProvider.GetService<T5>() is { } t6 &&
entity.ServiceProvider.GetService<T6>() is { } t7 &&
entity.ServiceProvider.GetService<TRest>() is { } t8)
{
list.Add(new ValueTuple<T, T1, T2, T3, T4, T5, T6, TRest>(t1, t2, t3, t4, t5, t6, t7, t8));
}
var entity = Entities[id];
array[i] = (entity.ServiceProvider.GetRequiredService<T>(),
entity.ServiceProvider.GetRequiredService<T1>(),
entity.ServiceProvider.GetRequiredService<T2>());
i++;
}
var handle = GCHandle.Alloc(array, GCHandleType.Pinned);
try
{
return new Span<(T, T1, T2)>(array, 0, count);
}
finally
{
handle.Free();
pool.Return(array);
}
}
public Span<(T, T1, T2, T3)> QueryComponents<T, T1, T2, T3>()
{
var pool = ArrayPool<(T, T1, T2, T3)>.Shared;
var hashset = _typeCaches.GetOrCreate(typeof(T).GetHashCode());
var t1Set = _typeCaches.GetOrCreate(typeof(T1).GetHashCode());
var t2Set = _typeCaches.GetOrCreate(typeof(T2).GetHashCode());
var t3Set = _typeCaches.GetOrCreate(typeof(T3).GetHashCode());
var count = hashset.Count;
var array = pool.Rent(count);
var i = 0;
foreach (var id in hashset.Intersect(t1Set).Intersect(t2Set).Intersect(t3Set))
{
var entity = Entities[id];
array[i] = (entity.ServiceProvider.GetRequiredService<T>(),
entity.ServiceProvider.GetRequiredService<T1>(),
entity.ServiceProvider.GetRequiredService<T2>(),
entity.ServiceProvider.GetRequiredService<T3>());
i++;
}
var handle = GCHandle.Alloc(array, GCHandleType.Pinned);
try
{
return new Span<(T, T1, T2, T3)>(array, 0, count);
}
finally
{
handle.Free();
pool.Return(array);
}
}
public Span<(T, T1, T2, T3, T4)> QueryComponents<T, T1, T2, T3, T4>()
{
var pool = ArrayPool<(T, T1, T2, T3, T4)>.Shared;
var hashset = _typeCaches.GetOrCreate(typeof(T).GetHashCode());
var t1Set = _typeCaches.GetOrCreate(typeof(T1).GetHashCode());
var t2Set = _typeCaches.GetOrCreate(typeof(T2).GetHashCode());
var t3Set = _typeCaches.GetOrCreate(typeof(T3).GetHashCode());
var t4Set = _typeCaches.GetOrCreate(typeof(T4).GetHashCode());
var count = hashset.Count;
var array = pool.Rent(count);
var i = 0;
foreach (var id in hashset.Intersect(t1Set).Intersect(t2Set).Intersect(t3Set).Intersect(t4Set))
{
var entity = Entities[id];
array[i] = (entity.ServiceProvider.GetRequiredService<T>(),
entity.ServiceProvider.GetRequiredService<T1>(),
entity.ServiceProvider.GetRequiredService<T2>(),
entity.ServiceProvider.GetRequiredService<T3>(),
entity.ServiceProvider.GetRequiredService<T4>());
i++;
}
var handle = GCHandle.Alloc(array, GCHandleType.Pinned);
try
{
return new Span<(T, T1, T2, T3, T4)>(array, 0, count);
}
finally
{
handle.Free();
pool.Return(array);
}
}
public Span<(T, T1, T2, T3, T4, T5)> QueryComponents<T, T1, T2, T3, T4, T5>()
{
var pool = ArrayPool<(T, T1, T2, T3, T4, T5)>.Shared;
var hashset = _typeCaches.GetOrCreate(typeof(T).GetHashCode());
var t1Set = _typeCaches.GetOrCreate(typeof(T1).GetHashCode());
var t2Set = _typeCaches.GetOrCreate(typeof(T2).GetHashCode());
var t3Set = _typeCaches.GetOrCreate(typeof(T3).GetHashCode());
var t4Set = _typeCaches.GetOrCreate(typeof(T4).GetHashCode());
var t5Set = _typeCaches.GetOrCreate(typeof(T5).GetHashCode());
var count = hashset.Count;
var array = pool.Rent(count);
var i = 0;
foreach (var id in hashset.Intersect(t1Set).Intersect(t2Set).Intersect(t3Set).Intersect(t4Set)
.Intersect(t5Set))
{
var entity = Entities[id];
array[i] = (entity.ServiceProvider.GetRequiredService<T>(),
entity.ServiceProvider.GetRequiredService<T1>(),
entity.ServiceProvider.GetRequiredService<T2>(),
entity.ServiceProvider.GetRequiredService<T3>(),
entity.ServiceProvider.GetRequiredService<T4>(),
entity.ServiceProvider.GetRequiredService<T5>()
);
i++;
}
var handle = GCHandle.Alloc(array, GCHandleType.Pinned);
try
{
return new Span<(T, T1, T2, T3, T4, T5)>(array, 0, count);
}
finally
{
handle.Free();
pool.Return(array);
}
}
public Span<(T, T1, T2, T3, T4, T5, T6)> QueryComponents<T, T1, T2, T3, T4, T5, T6>()
{
var pool = ArrayPool<(T, T1, T2, T3, T4, T5, T6)>.Shared;
var hashset = _typeCaches.GetOrCreate(typeof(T).GetHashCode());
var t1Set = _typeCaches.GetOrCreate(typeof(T1).GetHashCode());
var t2Set = _typeCaches.GetOrCreate(typeof(T2).GetHashCode());
var t3Set = _typeCaches.GetOrCreate(typeof(T3).GetHashCode());
var t4Set = _typeCaches.GetOrCreate(typeof(T4).GetHashCode());
var t5Set = _typeCaches.GetOrCreate(typeof(T5).GetHashCode());
var t6Set = _typeCaches.GetOrCreate(typeof(T6).GetHashCode());
var count = hashset.Count;
var array = pool.Rent(count);
var i = 0;
foreach (var id in hashset.Intersect(t1Set).Intersect(t2Set).Intersect(t3Set).Intersect(t4Set)
.Intersect(t5Set).Intersect(t6Set))
{
var entity = Entities[id];
array[i] = (entity.ServiceProvider.GetRequiredService<T>(),
entity.ServiceProvider.GetRequiredService<T1>(),
entity.ServiceProvider.GetRequiredService<T2>(),
entity.ServiceProvider.GetRequiredService<T3>(),
entity.ServiceProvider.GetRequiredService<T4>(),
entity.ServiceProvider.GetRequiredService<T5>(),
entity.ServiceProvider.GetRequiredService<T6>()
);
i++;
}
var handle = GCHandle.Alloc(array, GCHandleType.Pinned);
try
{
return new Span<(T, T1, T2, T3, T4, T5, T6)>(array, 0, count);
}
finally
{
handle.Free();
pool.Return(array);
}
return list.ToArray();
}
public void Dispose()
@@ -267,11 +348,11 @@ namespace BITKit.Entities
{
Entities.Clear();
}
_logger.LogInformation($"已释放,还剩{_count}个实例");
_cancellationTokenSource?.Dispose();
_ticker.Remove(OnTick);
}
}

View File

@@ -8,23 +8,24 @@ namespace BITKit.Entities
{
public class Entity : IEntity, IDisposable
{
public Entity()
{
ServiceCollection.AddSingleton<IEntity>(this);
}
public void WaitForInitializationComplete()
{
throw new NotImplementedException();
}
public int Id { get; set; } = Guid.NewGuid().GetHashCode();
public CancellationToken CancellationToken { get; set; }
public IServiceProvider ServiceProvider => _serviceProvider ??= ServiceCollection.BuildServiceProvider();
private ServiceProvider _serviceProvider;
public IServiceCollection ServiceCollection { get; } = new ServiceCollection();
public IServiceCollection ServiceCollection
{
get
{
if (_serviceCollection is not null) return _serviceCollection;
_serviceCollection = new ServiceCollection();
_serviceCollection.AddSingleton<IEntity>(this);
return _serviceCollection;
}
}
private IServiceCollection _serviceCollection;
public object[] GetServices() => ServiceCollection.ToArray()
.Select(x => _serviceProvider.GetService(x.ServiceType)).ToArray();
public void Inject(object obj)
{
foreach (var fieldInfo in obj.GetType().GetFields(ReflectionHelper.Flags))

View File

@@ -66,19 +66,13 @@ namespace BITKit.Entities
/// 通过Id获取或添加Entity
/// </summary>
IEntity GetOrAdd(int id,Func<int,IEntity> factory);
/// <summary>
/// 查询Entity,例如
/// </summary>
/// <para>var rotationEntities=EntitiesService.Query&lt;RotationComponent&gt;</para>
IEntity[] Query<T>();
/// <summary>
/// 查询1个组件
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
T[] QueryComponents<T>();
Span<T> QueryComponents<T>();
/// <summary>
/// 查询2个组件
@@ -86,7 +80,7 @@ namespace BITKit.Entities
/// <typeparam name="T"></typeparam>
/// <typeparam name="T1"></typeparam>
/// <returns></returns>
ValueTuple<T, T1>[] QueryComponents<T, T1>();
Span<ValueTuple<T, T1>> QueryComponents<T, T1>();
/// <summary>
/// 查询3个组件
@@ -95,7 +89,7 @@ namespace BITKit.Entities
/// <typeparam name="T1"></typeparam>
/// <typeparam name="T2"></typeparam>
/// <returns></returns>
ValueTuple<T, T1, T2>[] QueryComponents<T, T1, T2>();
Span<ValueTuple<T, T1, T2>> QueryComponents<T, T1, T2>();
/// <summary>
/// 查询4个组件
@@ -105,7 +99,7 @@ namespace BITKit.Entities
/// <typeparam name="T2"></typeparam>
/// <typeparam name="T3"></typeparam>
/// <returns></returns>
ValueTuple<T, T1, T2, T3>[] QueryComponents<T, T1, T2, T3>();
Span<ValueTuple<T, T1, T2, T3>> QueryComponents<T, T1, T2, T3>();
/// <summary>
/// 查询5个组件
@@ -116,7 +110,7 @@ namespace BITKit.Entities
/// <typeparam name="T3"></typeparam>
/// <typeparam name="T4"></typeparam>
/// <returns></returns>
ValueTuple<T, T1, T2, T3, T4>[] QueryComponents<T, T1, T2, T3, T4>();
Span<ValueTuple<T, T1, T2, T3, T4>> QueryComponents<T, T1, T2, T3, T4>();
/// <summary>
/// 查询6个组件
/// </summary>
@@ -127,7 +121,7 @@ namespace BITKit.Entities
/// <typeparam name="T4"></typeparam>
/// <typeparam name="T5"></typeparam>
/// <returns></returns>
ValueTuple<T, T1, T2, T3, T4, T5>[] QueryComponents<T, T1, T2, T3, T4, T5>();
Span<ValueTuple<T, T1, T2, T3, T4, T5>> QueryComponents<T, T1, T2, T3, T4, T5>();
/// <summary>
/// 查询7个组件
@@ -140,7 +134,7 @@ namespace BITKit.Entities
/// <typeparam name="T5"></typeparam>
/// <typeparam name="T6"></typeparam>
/// <returns></returns>
ValueTuple<T, T1, T2, T3, T4, T5, T6>[] QueryComponents<T, T1, T2, T3, T4, T5, T6>();
Span<ValueTuple<T, T1, T2, T3, T4, T5, T6>> QueryComponents<T, T1, T2, T3, T4, T5, T6>();
/// <summary>
/// 查询8个组件
@@ -154,6 +148,6 @@ namespace BITKit.Entities
/// <typeparam name="T6"></typeparam>
/// <typeparam name="TRest">剩余实例</typeparam>
/// <returns></returns>
ValueTuple<T, T1, T2, T3, T4, T5, T6, TRest>[] QueryComponents<T, T1, T2, T3, T4, T5, T6, TRest>() where TRest : struct;
//Span<ValueTuple<T, T1, T2, T3, T4, T5, T6, TRest>> QueryComponents<T, T1, T2, T3, T4, T5, T6, TRest>() where TRest : struct;
}
}

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: c9f4b5601bd64124a9efdee9cf9c7226
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,79 @@
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using BITKit;
using Cysharp.Threading.Tasks;
using Microsoft.Extensions.Logging;
namespace Net.BITKit.Localization
{
public class CsvLocalizationService
{
public string Url { get; set; } =
@"http://server.bitfall.icu:21982/com.project.b/net.project.b.localization.csv";
private readonly ILogger<CsvLocalizationService> _logger;
private readonly ILocalizationService _localizationService;
public CsvLocalizationService(ILocalizationService localizationService, ILogger<CsvLocalizationService> logger)
{
_localizationService = localizationService;
_logger = logger;
_localizationService.OnLanguageChangeAsync += OnLanguageChangeAsync;
}
public static Dictionary<string, Dictionary<string, string>> ParseCsvToDictionary(string csvData)
{
var dict = new Dictionary<string, Dictionary<string, string>>();
using var reader = new StringReader(csvData);
var headerLine = reader.ReadLine();
if (headerLine == null) return dict;
var headers = headerLine.Split(',');
for (var i = 1; i < headers.Length; i++) // 跳过 "Key" 列
{
dict[headers[i]] = new Dictionary<string, string>();
}
string? line;
while ((line = reader.ReadLine()) != null)
{
var columns = line.Split(',');
if (columns.Length < 2) continue;
var key = columns[0]; // 取 Key 值
for (var i = 1; i < columns.Length; i++)
{
dict[headers[i]][key] = columns[i]; // 填充语言数据
}
}
return dict;
}
private async UniTask OnLanguageChangeAsync(string arg1, string arg2)
{
var csv = await new HttpClient().GetStringAsync(Url);
_logger.LogInformation($"下载完成:\n{csv}");
foreach (var (lang, dictionary) in ParseCsvToDictionary(csv))
{
if (_localizationService.LocalizedStrings.TryGetValue(lang, out var currentDictionary) is false)
{
currentDictionary = new Dictionary<string, string>();
_localizationService.LocalizedStrings.Add(lang,currentDictionary);
}
foreach (var (key, value) in dictionary)
{
currentDictionary.Set(key, value);
}
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0a0edc8f5d75b3646a7b405ef830a52d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,111 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using BITKit;
using Cysharp.Threading.Tasks;
using Microsoft.Extensions.Logging;
namespace Net.BITKit.Localization
{
/// <summary>
/// 本地化
/// </summary>
public interface ILocalizationService
{
public string Prefix { get; }
/// <summary>
/// 当前语言
/// </summary>
public string CurrentLanguage { get; }
/// <summary>
/// 更改回调,通常在此检查并下载语言包
/// </summary>
public event Func<string,string,UniTask> OnLanguageChangeAsync;
/// <summary>
/// 语言更改完成回调,UI和World执行更新
/// </summary>
public event Action<string,string> OnLanguageChanged;
/// <summary>
/// 获取翻译文本,返回无复制的引用
/// </summary>
/// <param name="key"></param>
/// <param name="language">语言,默认为当前CultureInfo.Name</param>
/// <returns></returns>
public string GetLocalizedString(string key,string language=null);
public UniTask ChangeLanguageAsync(string newLanguage);
public IDictionary<string, IDictionary<string, string>> LocalizedStrings { get; }
}
public class LocalizationService : ILocalizationService,IDisposable
{
private static LocalizationService _singleton;
public string Prefix => "#";
private char _prefix = '#';
public string CurrentLanguage { get; private set; }
public event Func<string, string, UniTask> OnLanguageChangeAsync;
public event Action<string, string> OnLanguageChanged;
private readonly ILogger<LocalizationService> _logger;
public IDictionary<string, IDictionary<string, string>> LocalizedStrings { get; } =
new Dictionary<string, IDictionary<string, string>>();
private readonly ValidHandle _isBusy = new();
private readonly HashSet<string> _untranslatedKeys = new();
public LocalizationService(ILogger<LocalizationService> logger)
{
if (_singleton is not null)
{
logger.LogError("LocalizationService can only be one singleton");
return;
}
_logger = logger;
}
public string GetLocalizedString(string key, string language = null)
{
language ??= CurrentLanguage; // 默认使用当前语言
if (key[0] != _prefix)
{
key = _prefix + key;
}
if (LocalizedStrings.TryGetValue(language, out var langDict) && langDict.TryGetValue(key, out var value))
{
return value;
}
_untranslatedKeys.Add(key);
return key.Replace(Prefix,string.Empty).Replace("_"," "); // 如果找不到翻译,就返回 key 本身(常见策略)
}
public async UniTask ChangeLanguageAsync(string newLanguage)
{
if (CurrentLanguage == newLanguage) return;
var oldLanguage = CurrentLanguage;
CurrentLanguage = newLanguage;
await _isBusy;
using var _ = _isBusy.GetHandle();
await UniTask.SwitchToMainThread();
await OnLanguageChangeAsync.UniTaskFunc(oldLanguage, newLanguage);
// 触发同步事件(例如更新 UI
OnLanguageChanged?.Invoke(oldLanguage, newLanguage);
}
public void Dispose()
{
_isBusy?.Dispose();
_singleton = null;
_logger.LogInformation("Untranslated keys:\n"+string.Join("\n", _untranslatedKeys));
_untranslatedKeys.Clear();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: baab5c8590ad6ee44acbc258eeacdc8b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -9,13 +9,16 @@ namespace BITKit.Pool
/// </summary>
public interface IPoolService
{
public int DefaultCapacity { get; set; }
/// <summary>
/// 生成对象
/// </summary>
/// <param name="path">可寻址路径</param>
/// <param name="prefab">直接提供的预制体</param>
/// <typeparam name="T">类型</typeparam>
/// <returns></returns>
UniTask<T> Spawn<T>(string path) where T : class;
UniTask<T> Spawn<T>(string path,object prefab=null) where T : class;
/// <summary>
/// 回收对象
/// </summary>

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Cysharp.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
@@ -24,7 +25,6 @@ namespace BITKit.StateMachine
public bool Enabled { get; set; } = true;
public T CurrentState { get;private set; }
public T NextOrCurrentState => _nextTargetState.IfNotAllow(CurrentState);
public event Action<T, T> OnStateChanging;
public event Func<T, T, UniTask> OnStateChangeAsync;
public event Action<T, T> OnStateChanged;
@@ -35,7 +35,13 @@ namespace BITKit.StateMachine
public readonly ValidHandle IsBusy=new();
private readonly CancellationTokenSource _cancellationTokenSource=new();
private readonly Dictionary<int, T> _dictionary = new();
private readonly Optional<T> _nextTargetState = new();
private readonly HashSet<int> _isRegistered = new();
private CancellationTokenSource _transitionCts;
private T _entryCompletedState;
public async void Initialize()
{
await IsBusy;
@@ -43,33 +49,27 @@ namespace BITKit.StateMachine
using var _ = IsBusy.GetHandle();
foreach (var (_,value) in StateDictionary)
{
await value.InitializeAsync();
value.Initialize();
if (_isRegistered.Add(value.Identifier))
{
await value.InitializeAsync();
value.Initialize();
}
}
}
public async void UpdateState(float deltaTime)
{
if (CurrentState is null) return;
if (_entryCompletedState is null) return;
using var _ = IsBusy.GetHandle();
CurrentState.OnStateUpdate(deltaTime);
await CurrentState.OnStateUpdateAsync(deltaTime);
_entryCompletedState.OnStateUpdate(deltaTime);
await _entryCompletedState.OnStateUpdateAsync(deltaTime);
}
public async void DisposeState()
public void DisposeState()
{
await IsBusy;
if (_cancellationTokenSource.IsCancellationRequested) return;
if (CurrentState is null) return;
using var _ = IsBusy.GetHandle();
CurrentState.Enabled = false;
await CurrentState.OnStateExitAsync(CurrentState, null);
CurrentState.OnStateExit(CurrentState, null);
CurrentState = null;
TransitionState(null);
}
public T TransitionState<TState>() where TState : T
{
T nextState;
@@ -96,50 +96,60 @@ namespace BITKit.StateMachine
nextState.Identifier = nextState.GetHashCode();
}
}
if (Equals(nextState, CurrentState)) return;
if(_nextTargetState.Allow && Equals(_nextTargetState.Value,nextState))return;
if (_nextTargetState.Allow)
{
_nextTargetState.Value = nextState;
return;
}
_nextTargetState.SetValueThenAllow(nextState);
await IsBusy;
if(CurrentState==nextState)return;
if(_cancellationTokenSource.IsCancellationRequested)return;
using var _ = IsBusy.GetHandle();
OnStateChanging?.Invoke(CurrentState,nextState);
await OnStateChangeAsync.UniTaskFunc(CurrentState,nextState);
if (nextState is not null && _dictionary.TryAdd(nextState.Identifier, nextState))
{
await nextState.InitializeAsync();
if(_cancellationTokenSource.IsCancellationRequested)return;
nextState.Initialize();
}
if (CurrentState is not null)
{
CurrentState.Enabled = false;
await CurrentState.OnStateExitAsync(CurrentState, nextState);
if(_cancellationTokenSource.IsCancellationRequested)return;
CurrentState.OnStateExit(CurrentState,nextState);
}
if (_nextTargetState.Allow && _nextTargetState.Value != nextState)
{
return;
}
var tempState = CurrentState;
CurrentState = _nextTargetState.Value;
_nextTargetState.Clear();
nextState.Enabled = true;
await nextState.OnStateEntryAsync(tempState);
if(_cancellationTokenSource.IsCancellationRequested)return;
nextState.OnStateEntry(tempState);
OnStateChanged?.Invoke(tempState,nextState);
CurrentState = nextState;
_transitionCts?.Cancel();
_transitionCts = new CancellationTokenSource();
var ct = _transitionCts.Token;
await IsBusy;
using var _ = IsBusy.GetHandle();
if(ct.IsCancellationRequested||_cancellationTokenSource.IsCancellationRequested)return;
OnStateChanging?.Invoke(tempState,nextState);
if (tempState is not null)
{
if (_entryCompletedState == tempState)
{
_entryCompletedState = null;
}
tempState.Enabled = false;
await tempState.OnStateExitAsync(tempState, nextState);
tempState.OnStateExit(tempState,nextState);
if(_cancellationTokenSource.IsCancellationRequested)return;
}
if(ct.IsCancellationRequested)return;
await OnStateChangeAsync.UniTaskFunc(CurrentState,nextState);
if(ct.IsCancellationRequested)return;
if (nextState is not null)
{
if (_isRegistered.Add(nextState.Identifier))
{
await RegisterAsync(nextState);
if(ct.IsCancellationRequested || _cancellationTokenSource.IsCancellationRequested)return;
}
nextState.Enabled = true;
await nextState.OnStateEntryAsync(CurrentState);
nextState.OnStateEntry(CurrentState);
if(ct.IsCancellationRequested || _cancellationTokenSource.IsCancellationRequested)return;
_entryCompletedState = nextState;
}
OnStateChanged?.Invoke(tempState, nextState);
}
public T TransitionState(T nextState)
@@ -148,27 +158,49 @@ namespace BITKit.StateMachine
return nextState;
}
private async UniTask RegisterAsync(T newState)
{
StateDictionary.TryAdd(newState.GetType(),newState);
_dictionary.TryAdd(newState.Identifier, newState);
newState.Initialize();
await newState.InitializeAsync();
}
public async void Register(T newState)
{
await IsBusy;
using var _ = IsBusy.GetHandle();
await RegisterAsync(newState);
}
public async void UnRegister(T newState)
{
if (newState is null) return;
if (Dictionary.ContainsKey(newState.Identifier) is false) return;
_dictionary.Remove(newState.Identifier);
await IsBusy;
using var _ = IsBusy.GetHandle();
if (Equals(CurrentState, newState))
{
await CurrentState.OnStateExitAsync(CurrentState, null);
CurrentState.OnStateExit(CurrentState, null);
if (CurrentState is IAsyncDisposable asyncDisposable)
{
await asyncDisposable.DisposeAsync();
}
if (CurrentState is IDisposable disposable)
{
disposable.Dispose();
}
CurrentState = null;
}
if (newState is IAsyncDisposable asyncDisposable)
{
await asyncDisposable.DisposeAsync();
}
if (newState is IDisposable disposable)
{
disposable.Dispose();
}
OnStateUnRegistered?.Invoke(newState);
}
public void Dispose()

View File

@@ -7,7 +7,6 @@ namespace BITKit.UX
public interface IUXDialogue
{
void Show(string content,string title = "Alert",Action confirmAction=null,Action<bool> onChoose=null);
}
}