diff --git a/BITKit.csproj b/BITKit.csproj index 1743760..db5ee62 100644 --- a/BITKit.csproj +++ b/BITKit.csproj @@ -5,6 +5,7 @@ disable BITKit net7.0 + true @@ -51,6 +52,7 @@ + diff --git a/Src/Core/ECS/EntitiesService.cs b/Src/Core/ECS/EntitiesService.cs index 1325d65..7c059df 100644 --- a/Src/Core/ECS/EntitiesService.cs +++ b/Src/Core/ECS/EntitiesService.cs @@ -1,284 +1,623 @@ 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 kcp2k; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Unity.Mathematics; namespace BITKit.Entities { - public class EntitiesService:IEntitiesService,IDisposable + public static class EntitiesServiceExtensions { - private readonly ILogger _logger; - private readonly IFixedTicker _ticker; - private static int _count; - private static readonly ConcurrentQueue OnAddQueue = new(); - private static ConcurrentDictionary> TypeCaches = new(); - public EntitiesService(ILogger logger, IFixedTicker ticker) + internal static T GetRequiredServiceWithId(this IServiceProvider serviceProvider, int id) { - if (_count > 0) - { - throw new MulticastNotSupportedException(); - } - _count++; + return serviceProvider.GetRequiredService(); + } + } + public unsafe class EntitiesService : IEntitiesService, IDisposable + { + private static int _count; + + + private readonly ILogger _logger; + private readonly ITicker _ticker; + + private readonly ConcurrentQueue _onAddQueue = new(); + private readonly Dictionary> _typeCaches = new(); + private readonly Dictionary> _typeInstances = new(); + + private readonly ConcurrentDictionary _staticCaches = new(); + + public EntitiesService(ILogger logger, ITicker ticker) + { _logger = logger; _ticker = ticker; - - _ticker.Add(OnTick); + + _pool = new(ObjectGenerator, null, 64); + + _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); - } + _staticCaches.Clear(); + MakeCache(entity); } } - - - private static readonly ConcurrentDictionary Entities = new(); + private void MakeCache(IEntity entity) + { + foreach (var serviceDescriptor in entity.ServiceCollection) + { + var typeHash = serviceDescriptor.ServiceType.GetHashCode(); + var hashSet = _typeCaches.GetOrCreate(typeHash); + hashSet.Add(entity.Id); + } + } + + private readonly Dictionary _entitiesInternal = new(); public event Action OnAdd; public event Action OnRemove; - IReadOnlyDictionary IEntitiesService.Entities => Entities; + IReadOnlyDictionary IEntitiesService.Entities => _entitiesInternal; + private readonly Pool> _pool; + + private HashSet ObjectGenerator() + { + return new HashSet(math.max(8192, _typeCaches.Count * 2)); + } + public bool Register(IEntity entity) { - if (!Entities.TryAdd(entity.Id, entity)) return false; - OnAddQueue.Enqueue(entity); + if (!_entitiesInternal.TryAdd(entity.Id, entity)) return false; + if (_ticker is not null) + { + _onAddQueue.Enqueue(entity); + } + else + { + _staticCaches.Clear(); + OnAdd?.Invoke(entity); + MakeCache(entity); + } + + + return true; } public bool UnRegister(IEntity entity) { - if (!Entities.TryRemove(entity.Id, out _)) return false; + if (!_entitiesInternal.TryRemove(entity.Id)) return false; OnRemove?.Invoke(entity); foreach (var serviceDescriptor in entity.ServiceCollection) { var typeHash = serviceDescriptor.ServiceType.GetHashCode(); - var hashSet = TypeCaches.GetOrCreate(typeHash); + var hashSet = _typeCaches.GetOrCreate(typeHash); hashSet.Remove(entity.Id); } + _typeInstances.TryRemove(entity.Id); + + _staticCaches.Clear(); + return true; } public CancellationToken CancellationToken => _cancellationTokenSource.Token; private readonly CancellationTokenSource _cancellationTokenSource = new(); + public IEntity Get(int id) { - return Entities[id]; + return _entitiesInternal[id]; } public bool TryGetEntity(int id, out IEntity entity) { - return Entities.TryGetValue(id, out entity); + return _entitiesInternal.TryGetValue(id, out entity); } public IEntity GetOrAdd(int id, Func factory) { - return Entities.GetOrAdd(id, factory); - } - - public IEntity[] Query() - { - throw new NotImplementedException("Obsoleted"); - } - - public T[] QueryComponents() - { - var list = new List(); - foreach (var entity in Entities.Values) + if (_entitiesInternal.TryGetValue(id, out var current)) { - if (entity.ServiceProvider.GetService() is { } t1) - { - list.Add(t1); - } + _entitiesInternal.TryAdd(id, current = factory.Invoke(id)); } - return list.ToArray(); - // return _queryCache.GetOrAdd(typeof(T), type => - // { - // return _entities.Values.Where(entity => entity.TryGetComponent(out T component)).ToArray(); - // }).Cast().ToArray(); + return current; } - public (T, T1)[] QueryComponents() + public Span QueryComponents() where T : class { - var list = new List<(T, T1)>(); - foreach (var entity in Entities.Values) - { - if (entity.ServiceProvider.GetService() is { } t1 && - entity.ServiceProvider.GetService() 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() - { - var list = new List<(T, T1, T2)>(); - foreach (var entity in Entities.Values) - { - if (entity.ServiceProvider.GetService() is { } t1 && - entity.ServiceProvider.GetService() is { } t2 && - entity.ServiceProvider.GetService() 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(); - } + var pool = ArrayPool.Shared; - public (T, T1, T2, T3)[] QueryComponents() - { - var list = new List<(T, T1, T2, T3)>(); - foreach (var entity in Entities.Values) - { - if (entity.ServiceProvider.GetService() is { } t1 && - entity.ServiceProvider.GetService() is { } t2 && - entity.ServiceProvider.GetService() is { } t3 && - entity.ServiceProvider.GetService() 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 hashset = _typeCaches.GetOrCreate(typeof(T).GetHashCode()); - public (T, T1, T2, T3, T4)[] QueryComponents() - { - var list = new List<(T, T1, T2, T3, T4)>(); - foreach (var entity in Entities.Values) - { - if (entity.ServiceProvider.GetService() is { } t1 && - entity.ServiceProvider.GetService() is { } t2 && - entity.ServiceProvider.GetService() is { } t3 && - entity.ServiceProvider.GetService() is { } t4 && - entity.ServiceProvider.GetService() 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 array = pool.Rent(hashset.Count); + var collection = _pool.Take(); + collection.Clear(); - public (T, T1, T2, T3, T4, T5)[] QueryComponents() - { - var list = new List<(T, T1, T2, T3, T4, T5)>(); - foreach (var entity in Entities.Values) - { - if (entity.ServiceProvider.GetService() is { } t1 && - entity.ServiceProvider.GetService() is { } t2 && - entity.ServiceProvider.GetService() is { } t3 && - entity.ServiceProvider.GetService() is { } t4 && - entity.ServiceProvider.GetService() is { } t5 && - entity.ServiceProvider.GetService() 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(); - } + collection.UnionWith(hashset); - public (T, T1, T2, T3, T4, T5, T6)[] QueryComponents() - { - var list = new List<(T, T1, T2, T3, T4, T5, T6)>(); - foreach (var entity in Entities.Values) + var i = 0; + foreach (var id in collection) { - if (entity.ServiceProvider.GetService() is { } t1 && - entity.ServiceProvider.GetService() is { } t2 && - entity.ServiceProvider.GetService() is { } t3 && - entity.ServiceProvider.GetService() is { } t4 && - entity.ServiceProvider.GetService() is { } t5 && - entity.ServiceProvider.GetService() is { } t6 && - entity.ServiceProvider.GetService() is { } t7) + var instances = _typeInstances.GetOrCreate(id); + + var h0 = typeof(T).GetHashCode(); + + if (instances.TryGetValue(h0, out var v0) is false) { - list.Add((t1, t2, t3, t4, t5, t6, t7)); + instances[h0] = v0 = _entitiesInternal[id].ServiceProvider.GetRequiredService(); } + + array[i] = Unsafe.As(v0); + 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(); + try + { + return new Span(array, 0, i); + } + finally + { + pool.Return(array); + _pool.Return(collection); + } } - public ValueTuple[] QueryComponents() - where TRest : struct + public Span<(T, T1)> QueryComponents() where T : class where T1 : class { - var list = new List>(); - foreach (var entity in Entities.Values) + var pool = ArrayPool<(T, T1)>.Shared; + + var hashset = _typeCaches.GetOrCreate(typeof(T).GetHashCode()); + var t1Set = _typeCaches.GetOrCreate(typeof(T1).GetHashCode()); + + var count = math.max(hashset.Count, t1Set.Count); + + var array = pool.Rent(count); + var collection = _pool.Take(); + collection.Clear(); + + collection.UnionWith(hashset); + collection.IntersectWith(t1Set); + + var i = 0; + foreach (var id in collection) { - if (entity.ServiceProvider.GetService() is { } t1 && - entity.ServiceProvider.GetService() is { } t2 && - entity.ServiceProvider.GetService() is { } t3 && - entity.ServiceProvider.GetService() is { } t4 && - entity.ServiceProvider.GetService() is { } t5 && - entity.ServiceProvider.GetService() is { } t6 && - entity.ServiceProvider.GetService() is { } t7 && - entity.ServiceProvider.GetService() is { } t8) + var instances = _typeInstances.GetOrCreate(id); + + var h0 = typeof(T).GetHashCode(); + var h1 = typeof(T1).GetHashCode(); + + if (instances.TryGetValue(h0, out var v0) is false) { - list.Add(new ValueTuple(t1, t2, t3, t4, t5, t6, t7, t8)); + instances[h0] = v0 = _entitiesInternal[id].ServiceProvider.GetRequiredService(); } + + if (instances.TryGetValue(h1, out var v1) is false) + { + instances[h1] = v1 = _entitiesInternal[id].ServiceProvider.GetRequiredService(); + } + + array[i] = (Unsafe.As(v0), Unsafe.As(v1)); + i++; + } + + try + { + return new Span<(T, T1)>(array, 0, i); + } + finally + { + pool.Return(array); + _pool.Return(collection); + } + } + + public Span<(T, T1, T2)> QueryComponents() where T : class where T1 : class where T2 : class + { + 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 = math.max(hashset.Count, math.max(t1Set.Count, t2Set.Count)); + + var array = pool.Rent(count); + var collection = _pool.Take(); + collection.Clear(); + + collection.UnionWith(hashset); + collection.IntersectWith(t1Set); + collection.IntersectWith(t2Set); + + var i = 0; + foreach (var id in collection) + { + var instances = _typeInstances.GetOrCreate(id); + + var h0 = typeof(T).GetHashCode(); + var h1 = typeof(T1).GetHashCode(); + var h2 = typeof(T2).GetHashCode(); + + if (instances.TryGetValue(h0, out var v0) is false) + { + instances[h0] = v0 = _entitiesInternal[id].ServiceProvider.GetRequiredService(); + } + + if (instances.TryGetValue(h1, out var v1) is false) + { + instances[h1] = v1 = _entitiesInternal[id].ServiceProvider.GetRequiredService(); + } + + if (instances.TryGetValue(h2, out var v2) is false) + { + instances[h2] = v2 = _entitiesInternal[id].ServiceProvider.GetRequiredService(); + } + + array[i] = (Unsafe.As(v0), Unsafe.As(v1), Unsafe.As(v2)); + i++; + } + + try + { + return new Span<(T, T1, T2)>(array, 0, i); + } + finally + { + pool.Return(array); + _pool.Return(collection); + } + } + + public Span<(T, T1, T2, T3)> QueryComponents() + where T : class where T1 : class where T2 : class where T3 : class + { + 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 = math.max(hashset.Count, math.max(t1Set.Count, math.max(t2Set.Count, t3Set.Count))); + + var array = pool.Rent(count); + var collection = _pool.Take(); + collection.Clear(); + + collection.UnionWith(hashset); + collection.IntersectWith(t1Set); + collection.IntersectWith(t2Set); + collection.IntersectWith(t3Set); + + var i = 0; + foreach (var id in collection) + { + var instances = _typeInstances.GetOrCreate(id); + + var h0 = typeof(T).GetHashCode(); + var h1 = typeof(T1).GetHashCode(); + var h2 = typeof(T2).GetHashCode(); + var h3 = typeof(T3).GetHashCode(); + + if (instances.TryGetValue(h0, out var v0) is false) + { + instances[h0] = v0 = _entitiesInternal[id].ServiceProvider.GetRequiredService(); + } + + if (instances.TryGetValue(h1, out var v1) is false) + { + instances[h1] = v1 = _entitiesInternal[id].ServiceProvider.GetRequiredService(); + } + + if (instances.TryGetValue(h2, out var v2) is false) + { + instances[h2] = v2 = _entitiesInternal[id].ServiceProvider.GetRequiredService(); + } + + if (instances.TryGetValue(h3, out var v3) is false) + { + instances[h3] = v3 = _entitiesInternal[id].ServiceProvider.GetRequiredService(); + } + + array[i] = (Unsafe.As(v0), Unsafe.As(v1), Unsafe.As(v2), Unsafe.As(v3)); + i++; + } + + try + { + return new Span<(T, T1, T2, T3)>(array, 0, i); + } + finally + { + pool.Return(array); + _pool.Return(collection); + } + } + + public Span<(T, T1, T2, T3, T4)> QueryComponents() where T : class + where T1 : class + where T2 : class + where T3 : class + where T4 : class + { + 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 = math.max(hashset.Count, + math.max(t1Set.Count, math.max(t2Set.Count, math.max(t3Set.Count, t4Set.Count)))); + + var array = pool.Rent(count); + var collection = _pool.Take(); + collection.Clear(); + + collection.UnionWith(hashset); + collection.IntersectWith(t1Set); + collection.IntersectWith(t2Set); + collection.IntersectWith(t3Set); + collection.IntersectWith(t4Set); + + var i = 0; + foreach (var id in collection) + { + var instances = _typeInstances.GetOrCreate(id); + + var h0 = typeof(T).GetHashCode(); + var h1 = typeof(T1).GetHashCode(); + var h2 = typeof(T2).GetHashCode(); + var h3 = typeof(T3).GetHashCode(); + var h4 = typeof(T4).GetHashCode(); + + if (instances.TryGetValue(h0, out var v0) is false) + { + instances[h0] = v0 = _entitiesInternal[id].ServiceProvider.GetRequiredService(); + } + + if (instances.TryGetValue(h1, out var v1) is false) + { + instances[h1] = v1 = _entitiesInternal[id].ServiceProvider.GetRequiredService(); + } + + if (instances.TryGetValue(h2, out var v2) is false) + { + instances[h2] = v2 = _entitiesInternal[id].ServiceProvider.GetRequiredService(); + } + + if (instances.TryGetValue(h3, out var v3) is false) + { + instances[h3] = v3 = _entitiesInternal[id].ServiceProvider.GetRequiredService(); + } + + if (instances.TryGetValue(h4, out var v4) is false) + { + instances[h4] = v4 = _entitiesInternal[id].ServiceProvider.GetRequiredService(); + } + + array[i] = (Unsafe.As(v0), Unsafe.As(v1), Unsafe.As(v2), Unsafe.As(v3), + Unsafe.As(v4)); + i++; + } + + try + { + return new Span<(T, T1, T2, T3, T4)>(array, 0, i); + } + finally + { + pool.Return(array); + _pool.Return(collection); + } + } + + public Span<(T, T1, T2, T3, T4, T5)> QueryComponents() where T : class + where T1 : class + where T2 : class + where T3 : class + where T4 : class + where T5 : class + { + 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 = math.max(hashset.Count, + math.max(t1Set.Count, + math.max(t2Set.Count, math.max(t3Set.Count, math.max(t4Set.Count, t5Set.Count))))); + + var array = pool.Rent(count); + var collection = _pool.Take(); + collection.Clear(); + + collection.UnionWith(hashset); + collection.IntersectWith(t1Set); + collection.IntersectWith(t2Set); + collection.IntersectWith(t3Set); + collection.IntersectWith(t4Set); + collection.IntersectWith(t5Set); + + var i = 0; + foreach (var id in collection) + { + var instances = _typeInstances.GetOrCreate(id); + + var h0 = typeof(T).GetHashCode(); + var h1 = typeof(T1).GetHashCode(); + var h2 = typeof(T2).GetHashCode(); + var h3 = typeof(T3).GetHashCode(); + var h4 = typeof(T4).GetHashCode(); + var h5 = typeof(T5).GetHashCode(); + + if (instances.TryGetValue(h0, out var v0) is false) + { + instances[h0] = v0 = _entitiesInternal[id].ServiceProvider.GetRequiredService(); + } + + if (instances.TryGetValue(h1, out var v1) is false) + { + instances[h1] = v1 = _entitiesInternal[id].ServiceProvider.GetRequiredService(); + } + + if (instances.TryGetValue(h2, out var v2) is false) + { + instances[h2] = v2 = _entitiesInternal[id].ServiceProvider.GetRequiredService(); + } + + if (instances.TryGetValue(h3, out var v3) is false) + { + instances[h3] = v3 = _entitiesInternal[id].ServiceProvider.GetRequiredService(); + } + + if (instances.TryGetValue(h4, out var v4) is false) + { + instances[h4] = v4 = _entitiesInternal[id].ServiceProvider.GetRequiredService(); + } + + if (instances.TryGetValue(h5, out var v5) is false) + { + instances[h5] = v5 = _entitiesInternal[id].ServiceProvider.GetRequiredService(); + } + + array[i] = (Unsafe.As(v0), Unsafe.As(v1), Unsafe.As(v2), Unsafe.As(v3), + Unsafe.As(v4), Unsafe.As(v5)); + i++; + } + + try + { + return new Span<(T, T1, T2, T3, T4, T5)>(array, 0, i); + } + finally + { + pool.Return(array); + _pool.Return(collection); + } + } + + public Span<(T, T1, T2, T3, T4, T5, T6)> QueryComponents() where T : class + where T1 : class + where T2 : class + where T3 : class + where T4 : class + where T5 : class + where T6 : class + { + 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 = math.max(hashset.Count, + math.max(t1Set.Count, + math.max(t2Set.Count, + math.max(t3Set.Count, math.max(t4Set.Count, math.max(t5Set.Count, t6Set.Count)))))); + + var array = pool.Rent(count); + var collection = _pool.Take(); + collection.Clear(); + + collection.UnionWith(hashset); + collection.IntersectWith(t1Set); + collection.IntersectWith(t2Set); + collection.IntersectWith(t3Set); + collection.IntersectWith(t4Set); + collection.IntersectWith(t5Set); + collection.IntersectWith(t6Set); + + var i = 0; + foreach (var id in collection) + { + var instances = _typeInstances.GetOrCreate(id); + + var h0 = typeof(T).GetHashCode(); + var h1 = typeof(T1).GetHashCode(); + var h2 = typeof(T2).GetHashCode(); + var h3 = typeof(T3).GetHashCode(); + var h4 = typeof(T4).GetHashCode(); + var h5 = typeof(T5).GetHashCode(); + var h6 = typeof(T6).GetHashCode(); + + if (instances.TryGetValue(h0, out var v0) is false) + { + instances[h0] = v0 = _entitiesInternal[id].ServiceProvider.GetRequiredService(); + } + + if (instances.TryGetValue(h1, out var v1) is false) + { + instances[h1] = v1 = _entitiesInternal[id].ServiceProvider.GetRequiredService(); + } + + if (instances.TryGetValue(h2, out var v2) is false) + { + instances[h2] = v2 = _entitiesInternal[id].ServiceProvider.GetRequiredService(); + } + + if (instances.TryGetValue(h3, out var v3) is false) + { + instances[h3] = v3 = _entitiesInternal[id].ServiceProvider.GetRequiredService(); + } + + if (instances.TryGetValue(h4, out var v4) is false) + { + instances[h4] = v4 = _entitiesInternal[id].ServiceProvider.GetRequiredService(); + } + + if (instances.TryGetValue(h5, out var v5) is false) + { + instances[h5] = v5 = _entitiesInternal[id].ServiceProvider.GetRequiredService(); + } + + if (instances.TryGetValue(h6, out var v6) is false) + { + instances[h6] = v6 = _entitiesInternal[id].ServiceProvider.GetRequiredService(); + } + + array[i] = (Unsafe.As(v0), Unsafe.As(v1), Unsafe.As(v2), Unsafe.As(v3), + Unsafe.As(v4), Unsafe.As(v5), Unsafe.As(v6)); + i++; + } + + try + { + return new Span<(T, T1, T2, T3, T4, T5, T6)>(array, 0, i); + } + finally + { + pool.Return(array); + _pool.Return(collection); } - return list.ToArray(); } public void Dispose() @@ -286,14 +625,14 @@ namespace BITKit.Entities _count--; if (_count <= 0) { - Entities.Clear(); + _entitiesInternal.Clear(); } - + _logger.LogInformation($"已释放,还剩{_count}个实例"); - + _cancellationTokenSource?.Dispose(); - - _ticker.Remove(OnTick); + + _ticker?.Remove(OnTick); } } } diff --git a/Src/Core/ECS/EntityComponentSystem.cs b/Src/Core/ECS/EntityComponentSystem.cs index 81c0344..6fe951b 100644 --- a/Src/Core/ECS/EntityComponentSystem.cs +++ b/Src/Core/ECS/EntityComponentSystem.cs @@ -18,6 +18,7 @@ namespace BITKit.Entities IServiceProvider ServiceProvider { get; } IServiceCollection ServiceCollection { get; } void Inject(object obj); + } /// /// 基本实体服务 @@ -66,19 +67,13 @@ namespace BITKit.Entities /// 通过Id获取或添加Entity /// IEntity GetOrAdd(int id,Func factory); - - /// - /// 查询Entity,例如 - /// - /// var rotationEntities=EntitiesService.Query<RotationComponent> - IEntity[] Query(); - + /// /// 查询1个组件 /// /// /// - T[] QueryComponents(); + Span QueryComponents() where T : class; /// /// 查询2个组件 @@ -86,7 +81,7 @@ namespace BITKit.Entities /// /// /// - ValueTuple[] QueryComponents(); + Span> QueryComponents() where T : class where T1 : class; /// /// 查询3个组件 @@ -95,7 +90,7 @@ namespace BITKit.Entities /// /// /// - ValueTuple[] QueryComponents(); + Span> QueryComponents() where T : class where T1 : class where T2 : class; /// /// 查询4个组件 @@ -105,7 +100,7 @@ namespace BITKit.Entities /// /// /// - ValueTuple[] QueryComponents(); + Span> QueryComponents() where T : class where T1 : class where T2 : class where T3:class; /// /// 查询5个组件 @@ -116,7 +111,7 @@ namespace BITKit.Entities /// /// /// - ValueTuple[] QueryComponents(); + Span> QueryComponents()where T : class where T1 : class where T2 : class where T3:class where T4:class; /// /// 查询6个组件 /// @@ -127,7 +122,7 @@ namespace BITKit.Entities /// /// /// - ValueTuple[] QueryComponents(); + Span> QueryComponents()where T : class where T1 : class where T2 : class where T3:class where T4:class where T5 : class; /// /// 查询7个组件 @@ -140,7 +135,7 @@ namespace BITKit.Entities /// /// /// - ValueTuple[] QueryComponents(); + Span> QueryComponents()where T : class where T1 : class where T2 : class where T3:class where T4:class where T5 : class where T6 :class; /// /// 查询8个组件 @@ -154,6 +149,6 @@ namespace BITKit.Entities /// /// 剩余实例 /// - ValueTuple[] QueryComponents() where TRest : struct; + //Span> QueryComponents() where TRest : struct; } } \ No newline at end of file diff --git a/Src/Core/ECS/UnityEntity.cs b/Src/Core/ECS/UnityEntity.cs index e959b66..4eef9a3 100644 --- a/Src/Core/ECS/UnityEntity.cs +++ b/Src/Core/ECS/UnityEntity.cs @@ -13,6 +13,7 @@ namespace BITKit.Entities [DefaultExecutionOrder(-1)] public class UnityEntity : MonoBehaviour,IEntity { + [SerializeField] private bool debug; private IEntitiesService _entitiesService; private IEntity _entity; @@ -22,6 +23,11 @@ namespace BITKit.Entities if (_entitiesService.Entities.ContainsKey(gameObject.GetInstanceID())) return; + if (debug) + { + + } + var entity = new Entity() { Id = gameObject.GetInstanceID(), @@ -38,7 +44,10 @@ namespace BITKit.Entities foreach (var component in GetComponents()) { + if(!component)continue; var type = component.GetType(); + + entity.ServiceCollection.AddSingleton(type, component); foreach (var x in type.GetInterfaces()) { diff --git a/Src/Core/Extensions/IEnumerable.cs b/Src/Core/Extensions/IEnumerable.cs index 670fa6f..58cc287 100644 --- a/Src/Core/Extensions/IEnumerable.cs +++ b/Src/Core/Extensions/IEnumerable.cs @@ -122,44 +122,20 @@ namespace BITKit value = default; return default; } - [Obsolete("Use TryGetElementAt instead")] - public static bool TryGet(this IEnumerable self, int index, out T value)=>TryGetElementAt(self, index, out value); - - public static bool TryInsert(this IDictionary self, TKey key, TValue value) + public static TValue GetOrCreate(this IDictionary self, TKey t) where TValue : new() { - lock (self) + if (self.TryGetValue(t, out var value)) { - if (self.ContainsKey(key)) - { - self[key] = value; - } - else - { - self.Add(key, value); - } - return true; - } - } - public static void Insert(this IDictionary self, TKey key, TValue value) - { - TryInsert(self, key, value); - } - public static TValue GetOrCreate(this IDictionary self, TKey t) - { - lock (self) - { - if (self.TryGetValue(t, out TValue value)) - { - } - else - { - value = Activator.CreateInstance(); - self.Add(t, value); - } - return value; } + else + { + value = new TValue(); + self.TryAdd(t, value); + } + return value; } + public static bool TryRemove(this IList self, T t) { if (self.Contains(t)) diff --git a/Src/Core/Extensions/Object.cs b/Src/Core/Extensions/Object.cs index b385165..68bc0b4 100644 --- a/Src/Core/Extensions/Object.cs +++ b/Src/Core/Extensions/Object.cs @@ -1,9 +1,64 @@ using System; +using System.Collections.Concurrent; +using System.Runtime.CompilerServices; +using Microsoft.Extensions.DependencyInjection; +using Unity.Mathematics; namespace BITKit { public static partial class Extensions { + private static readonly ConcurrentDictionary> Services = new(); + public static bool QueryComponents(this IServiceProvider self,out T t1) where T : class + { + var id = self.GetHashCode(); + var typeId = typeof(T).GetHashCode(); + if (Services.GetOrCreate(id).TryGetValue(typeId, out var value) is false) + { + value = self.GetService(); + Services.GetOrCreate(id).TryAdd(typeId,value); + } + if (value is null) + { + t1 = null; + return false; + } + t1 = Unsafe.As(value); + return true; + } + public static bool QueryComponents (this IServiceProvider self,out T t1,out T1 t2) where T : class where T1 : class + { + t1 = null; + t2 = null; + if (QueryComponents(self,out t1) is false) return false; + if (QueryComponents(self,out t2) is false) return false; + return true; + } + + public static bool QueryComponents (this IServiceProvider self,out T t1,out T1 t2,out T2 t3) where T : class where T1 : class where T2 : class + { + t1 = null; + t2 = null; + t3 = null; + if (QueryComponents(self,out t1) is false) return false; + if (QueryComponents(self,out t2) is false) return false; + if (QueryComponents(self,out t3) is false) return false; + return true; + } + + public static bool QueryComponents (this IServiceProvider self,out T t1,out T1 t2,out T2 t3,out T3 t4) where T : class where T1 : class where T2 : class where T3 : class + { + t1 = null; + t2 = null; + t3 = null; + t4 = null; + if (QueryComponents(self,out t1) is false) return false; + if (QueryComponents(self,out t2) is false) return false; + if (QueryComponents(self,out t3) is false) return false; + if (QueryComponents(self,out t4) is false) return false; + return true; + } + public static T IsNull(this T t, Action action) { if (t is null) diff --git a/Src/Core/Group/ActivableGroup.cs b/Src/Core/Group/ActivableGroup.cs deleted file mode 100644 index e01105b..0000000 --- a/Src/Core/Group/ActivableGroup.cs +++ /dev/null @@ -1,139 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq.Expressions; -using Cysharp.Threading.Tasks; -// ReSharper disable MethodHasAsyncOverload - -namespace BITKit -{ - public interface IEntryGroup - { - - } - public interface IEntryElement - { - bool IsEntered { get; set; } - void Entry(); - UniTask EntryAsync(); - void Entered(); - void Exit(); - UniTask ExitAsync(); - void Exited(); - } - - [System.Serializable] - public class EntryGroup : IEntryGroup where T : IEntryElement - { - public int index = -1; - public List list = new(); - private int m_index = -1; - private bool completed=true; - - public event Action OnEntry; - public event Action OnExit; - public void Entry(T t) - { - if (t is not null) - { - list.TryAdd(t); - - } - index = list.IndexOf(t); - EnsureConfiguration(); - } - public void Entry(int index) - { - if (index < list.Count) - { - this.index = index; - } - } - public void Entry(Func entryFactory) - { - var index = list.FindIndex(x => entryFactory.Invoke(x)); - Entry(index); - } - public void Entry() - { - index = 0; - EnsureConfiguration(); - } - - public void Exit() - { - index = -1; - EnsureConfiguration(); - } - public bool TryGetEntried(out T value) - { - EnsureConfiguration(); - if (m_index is not -1) - { - value = list[m_index]; - return true; - } - value = default; - return false; - } - private async void EnsureConfiguration() - { - try - { - if(completed is false) return; - completed = false; - if (index == m_index) - { - - } - else - { - var currentIndex = m_index; - if (currentIndex is not -1 && list.TryGetElementAt(currentIndex, out var currentElement)) - { - currentElement.Exit(); - try - { - await currentElement.ExitAsync(); - } - catch (OperationCanceledException) - { - return; - } - catch (Exception e) - { - BIT4Log.LogException(e); - } - currentElement.Exited(); - currentElement.IsEntered = false; - OnExit?.Invoke(currentElement); - } - m_index = index; - if (index is not -1 && list.TryGetElementAt(index, out var nextElement)) - { - nextElement.IsEntered = true; - OnEntry?.Invoke(nextElement); - nextElement.Entry(); - try - { - await nextElement.EntryAsync(); - nextElement.Entered(); - } - catch (OperationCanceledException){} - - } - } - completed = true; - } - catch (Exception e) - { - #if UNITY_EDITOR - UnityEngine.Debug.LogException(e); - #else - BIT4Log.LogException(e); - #endif - - } - - } - } -} \ No newline at end of file diff --git a/Src/Unity/Scripts/Quadtree/Items.meta b/Src/Core/Localization.meta similarity index 77% rename from Src/Unity/Scripts/Quadtree/Items.meta rename to Src/Core/Localization.meta index c0a6232..9fc096d 100644 --- a/Src/Unity/Scripts/Quadtree/Items.meta +++ b/Src/Core/Localization.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: f97e02abd61aa274683a80be45fec110 +guid: c9f4b5601bd64124a9efdee9cf9c7226 folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/Src/Core/Localization/CsvLocalizationService.cs b/Src/Core/Localization/CsvLocalizationService.cs new file mode 100644 index 0000000..70270d5 --- /dev/null +++ b/Src/Core/Localization/CsvLocalizationService.cs @@ -0,0 +1,92 @@ +using System; +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 _logger; + private readonly ILocalizationService _localizationService; + + public CsvLocalizationService(ILocalizationService localizationService, ILogger logger) + { + _localizationService = localizationService; + _logger = logger; + + _localizationService.OnLanguageChangeAsync += OnLanguageChangeAsync; + } + + public static Dictionary> ParseCsvToDictionary(string csvData) + { + var dict = new Dictionary>(); + 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" 列 + { + var header = headers[i]; + if (dict.ContainsKey(header) is false) + { + dict.Add(header, new Dictionary()); + } + } + + 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++) + { + if(i>headers.Length-1)continue; + var header = headers[i]; + if (dict.TryGetValue(header, out var d) is false) + { + d = new Dictionary(); + dict.Add(header,d); + } + d.Set(key,columns[i]); + //dict[headers[i]][key] = columns[i]; // 填充语言数据 + } + } + + return dict; + } + + private async UniTask OnLanguageChangeAsync(string arg1, string arg2) + { + var csv = await new HttpClient().GetStringAsync(Url+$"?{DateTime.Now.Ticks}"); + + _logger.LogInformation($"下载完成:\n{csv}"); + + foreach (var (lang, dictionary) in ParseCsvToDictionary(csv)) + { + if (_localizationService.LocalizedStrings.TryGetValue(lang, out var currentDictionary) is false) + { + currentDictionary = new Dictionary(); + _localizationService.LocalizedStrings.Add(lang,currentDictionary); + } + foreach (var (key, value) in dictionary) + { + currentDictionary.Set(key, value); + } + } + } + } + +} diff --git a/Src/Unity/Scripts/Quadtree/GameObjectQuadtreeRoot.cs.meta b/Src/Core/Localization/CsvLocalizationService.cs.meta similarity index 83% rename from Src/Unity/Scripts/Quadtree/GameObjectQuadtreeRoot.cs.meta rename to Src/Core/Localization/CsvLocalizationService.cs.meta index 7017687..f836012 100644 --- a/Src/Unity/Scripts/Quadtree/GameObjectQuadtreeRoot.cs.meta +++ b/Src/Core/Localization/CsvLocalizationService.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 2bc2bf5dce5a1fa418a53e9616109677 +guid: 0a0edc8f5d75b3646a7b405ef830a52d MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Src/Core/Localization/ILocalizationService.cs b/Src/Core/Localization/ILocalizationService.cs new file mode 100644 index 0000000..3a2355e --- /dev/null +++ b/Src/Core/Localization/ILocalizationService.cs @@ -0,0 +1,113 @@ +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 +{ + /// + /// 本地化 + /// + public interface ILocalizationService + { + public string Prefix { get; } + /// + /// 当前语言 + /// + public string CurrentLanguage { get; } + /// + /// 更改回调,通常在此检查并下载语言包 + /// + public event Func OnLanguageChangeAsync; + /// + /// 语言更改完成回调,UI和World执行更新 + /// + public event Action OnLanguageChanged; + /// + /// 获取翻译文本,返回无复制的引用 + /// + /// + /// 语言,默认为当前CultureInfo.Name + /// + public string GetLocalizedString(string key,string language=null); + public UniTask ChangeLanguageAsync(string newLanguage); + public IDictionary> 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 OnLanguageChangeAsync; + public event Action OnLanguageChanged; + + private readonly ILogger _logger; + + public IDictionary> LocalizedStrings { get; } = + new Dictionary>(); + + private readonly ValidHandle _isBusy = new(); + + private readonly HashSet _untranslatedKeys = new(); + + public LocalizationService(ILogger 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(); + + #if UNITY_5_3_OR_NEWER + await UniTask.SwitchToMainThread(); +#endif + + 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(); + } + } + +} diff --git a/Src/Core/Group/ActivableGroup.cs.meta b/Src/Core/Localization/ILocalizationService.cs.meta similarity index 83% rename from Src/Core/Group/ActivableGroup.cs.meta rename to Src/Core/Localization/ILocalizationService.cs.meta index c8d6dd1..923b23f 100644 --- a/Src/Core/Group/ActivableGroup.cs.meta +++ b/Src/Core/Localization/ILocalizationService.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 407c6cf54ae8abc42b77a090f9fa7616 +guid: baab5c8590ad6ee44acbc258eeacdc8b MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Src/Core/Mod/ModService_IO.cs b/Src/Core/Mod/ModService_IO.cs index c3b81cb..5d50316 100644 --- a/Src/Core/Mod/ModService_IO.cs +++ b/Src/Core/Mod/ModService_IO.cs @@ -19,6 +19,7 @@ namespace BITKit.Mod { list.Add((T)obj); } + } return list; } diff --git a/Src/Core/Quadtree.meta b/Src/Core/Quadtree.meta new file mode 100644 index 0000000..3d76858 --- /dev/null +++ b/Src/Core/Quadtree.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c15a9900918e4324991bcf53015b006e +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Src/Core/Quadtree/Net.BITKit.Quadtree.asmdef b/Src/Core/Quadtree/Net.BITKit.Quadtree.asmdef new file mode 100644 index 0000000..0e53721 --- /dev/null +++ b/Src/Core/Quadtree/Net.BITKit.Quadtree.asmdef @@ -0,0 +1,20 @@ +{ + "name": "Net.BITKit.Quadtree", + "rootNamespace": "", + "references": [ + "GUID:14fe60d984bf9f84eac55c6ea033a8f4", + "GUID:d8b63aba1907145bea998dd612889d6b", + "GUID:f51ebe6a0ceec4240a699833d6309b23", + "GUID:e0cd26848372d4e5c891c569017e11f1", + "GUID:2665a8d13d1b3f18800f46e256720795" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": true, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": true +} \ No newline at end of file diff --git a/Src/Unity/Scripts/Quadtree/BITKit.Quadtree.asmdef.meta b/Src/Core/Quadtree/Net.BITKit.Quadtree.asmdef.meta similarity index 100% rename from Src/Unity/Scripts/Quadtree/BITKit.Quadtree.asmdef.meta rename to Src/Core/Quadtree/Net.BITKit.Quadtree.asmdef.meta diff --git a/Src/Core/Quadtree/Quadtree.cs b/Src/Core/Quadtree/Quadtree.cs new file mode 100644 index 0000000..82e32ba --- /dev/null +++ b/Src/Core/Quadtree/Quadtree.cs @@ -0,0 +1,305 @@ +using System; +using System.Buffers; +using System.Collections; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http.Headers; +using System.Security.Policy; +using System.Threading; +using System.Threading.Tasks; +using BITKit; +#if UNITY_5_3_OR_NEWER +using Unity.Burst; +#endif +using Unity.Mathematics; + +namespace Net.BITKit.Quadtree +{ + public class Quadtree + { + public QuadtreeNode Root { get; private set; } + + public IDictionary Positions + { + get + { + Expansion(); + return _positions; + } + } + + public IDictionary Sizes => _sizes; + private readonly Dictionary _positions; + private readonly ConcurrentQueue<(int, float2)> _queue; + private readonly Dictionary _sizes; + + public Quadtree(float2 center, float2 size) + { + _sizes = new Dictionary(); + _positions = new Dictionary(); + _queue = new ConcurrentQueue<(int, float2)>(); + Root = new QuadtreeNode(this, center, size); + } + + public void Insert(in int objectId, in float2 position, in float2 size = default) + { + _queue.Enqueue((objectId, position)); + + if (size.x is not 0) + { + _sizes.TryAdd(objectId, size); + } + else + { + var root = Root; + + InsertRecursive(ref root, in objectId, in position); + } + } + + private void InsertRecursive(ref QuadtreeNode node, in int objectId, in float2 position) + { + if (!node.Contains(position)) + return; + if (node.Objects.Count < 4 || node.Size.x <= 1f || node.Depth > Root.Size.x / 8) // 假设最小节点大小为1 + { + node.Objects.Add(objectId); + } + else + { + if (node.Children[0].Size.x == 0) // 如果子节点未初始化 + { + node.Split(); + } + + for (var i = 0; i < 4; i++) + { + InsertRecursive(ref node.Children[i], objectId, position); + } + } + } + + public Span Query(float2 position, float radius) + { + Expansion(); + + var index = 0; + + var root = Root; + + var pool = ArrayPool.Shared; + + var array = pool.Rent(math.ceilpow2(_positions.Count * 2)); + + pool.Return(array); + + QueryRecursive(in root, in position, in radius, array.AsSpan(), ref index); + foreach (var (key, size) in _sizes) + { + var pos = _positions[key]; + + if (IsCircleInRect(pos, radius, position, size)) + { + array[index] = key; + index++; + } + } + + try + { + return array.AsSpan()[..index]; + } + finally + { + pool.Return(array); + } + } +#if UNITY_5_3_OR_NEWER + [BurstCompile] +#endif + private static bool IsCircleInRect(float2 point, float radius, float2 pos, float2 size) + { + var halfSize = size * 0.5f; + var min = pos - halfSize; // 矩形左下角 + var max = pos + halfSize; // 矩形右上角 + + // 计算扩展后的包围盒 + var expandedMin = min - radius; + var expandedMax = max + radius; + + // 检查点是否在扩展的矩形内 + return point.x >= expandedMin.x && point.x <= expandedMax.x && + point.y >= expandedMin.y && point.y <= expandedMax.y; + } + + private void Expansion() + { + while (_queue.TryDequeue(out var item)) + { + _positions.TryAdd(item.Item1, item.Item2); + } + } +#if UNITY_5_3_OR_NEWER + [BurstCompile] +#endif + private void QueryRecursive(in QuadtreeNode node, in float2 position, in float radius, Span result, + ref int index) + { + if (!Intersects(node.Center, node.Size, position, radius)) + return; + + + + foreach (var obj in node.Objects) + { + // 直接 TryGetValue,避免 `Positions[obj]` 可能的重复哈希查找 + if (!_positions.TryGetValue(obj, out var objPos)) continue; + + // 计算平方距离,避免额外变量 + if (math.dot(objPos - position, objPos - position) > radius * radius) continue; + + // 直接写入 result + try + { + result[index] = obj; + } + catch (Exception) + { + #if UNITY_EDITOR + var unions = result.ToArray().Distinct(); + var hashSet = new HashSet(); + For(Root); + void For(in QuadtreeNode node) + { + foreach (var child in node.Children) + { + For(child); + } + foreach (var x in node.Objects) + { + if (hashSet.Add(x) is false) + { + + } + } + } + #endif + throw; + } + + index++; + } + + + for (var i = 0; i < 4; i++) + { + if (node.Children[i].Size.x > 0) + { + QueryRecursive(in node.Children[i], in position, in radius, result, ref index); + } + } + } +#if UNITY_5_3_OR_NEWER + [BurstCompile] +#endif + private static bool Intersects(in float2 center, in float2 size, in float2 position, in float radius) + { + // 计算 AABB 的最小/最大点 + var min = center - size * 0.5f; + var max = center + size * 0.5f; + + // 找到圆心到 AABB 的最近点 + var closest = math.clamp(position, min, max); + + // 计算圆心到最近点的距离 + var delta = position - closest; + var distanceSq = math.dot(delta, delta); + + // 相交条件:如果距离平方 <= 半径平方 + return distanceSq <= (radius * radius); + } + + + // 删除对象 + public bool Remove(int objectId) + { + Expansion(); + + if (_positions.TryGetValue(objectId, out var position) is false) return false; + + _sizes.TryRemove(objectId); + + var root = Root; + + if (RemoveRecursive(ref root, objectId, position) is false) return false; + _positions.Remove(objectId); + + + return true; + } + private static bool RemoveRecursive(ref QuadtreeNode node, int objectId, float2 position) + { + if (!node.Contains(position)) + return false; + + // 尝试从当前节点删除 + bool removed = node.Objects.Remove(objectId); + + // 如果当前节点是叶子节点,返回是否成功删除 + if (node.IsLeaf()) + { + return removed; + } + + // 递归从子节点删除 + for (var i = 0; i < 4; i++) + { + if (RemoveRecursive(ref node.Children[i], objectId, position)) + { + removed = true; + } + } + + // 如果对象被移除,尝试合并子节点 + if (removed) + { + TryMerge(ref node); + } + + return removed; + } + + private static void TryMerge(ref QuadtreeNode node) + { + if (node.IsLeaf()) return; + + // 检查是否可以合并子节点 + var totalObjects = node.Objects.Count; + for (var i = 0; i < 4; i++) + { + totalObjects += node.Children[i].Objects.Count; + } + + // 如果当前节点和所有子节点的对象数量小于等于阈值,则合并 + if (totalObjects <= 4) + { + // 把所有子节点的对象都合并到当前节点 + foreach (var child in node.Children) + { + node.Objects.UnionWith(child.Objects); + child.Objects.Clear(); // 清空子节点的对象集合 + } + + // 清空子节点,重新初始化 + for (var i = 0; i < 4; i++) + { + node.Children[i] = new QuadtreeNode() + { + Depth = node.Depth + 1 + }; + } + } + } + } +} diff --git a/Src/Unity/Scripts/Quadtree/INode.cs.meta b/Src/Core/Quadtree/Quadtree.cs.meta similarity index 83% rename from Src/Unity/Scripts/Quadtree/INode.cs.meta rename to Src/Core/Quadtree/Quadtree.cs.meta index ad87d95..3ad1d45 100644 --- a/Src/Unity/Scripts/Quadtree/INode.cs.meta +++ b/Src/Core/Quadtree/Quadtree.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 097957ac1375f8141a80aa94f32030db +guid: b421b3302ecaaab4bab9eebf970e2bb6 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Src/Core/Quadtree/QuadtreeNode.cs b/Src/Core/Quadtree/QuadtreeNode.cs new file mode 100644 index 0000000..ef7d1e9 --- /dev/null +++ b/Src/Core/Quadtree/QuadtreeNode.cs @@ -0,0 +1,84 @@ +using System; +using System.Buffers; +using System.Collections.Concurrent; +using Unity.Mathematics; +using System.Collections.Generic; + +namespace Net.BITKit.Quadtree +{ + public struct QuadtreeNode + { + public readonly Quadtree Root; + + public int Depth; + + public float2 Center; // 节点中心 + public float2 Size; // 节点大小 + + public readonly QuadtreeNode[] Children; // 子节点 + public readonly HashSet Objects; // 存储的对象ID + + public QuadtreeNode(Quadtree root,float2 center, float2 size) + { + Root = root; + Center = center; + Size = size; + + Children = new QuadtreeNode[4]; + + Objects = new HashSet(); + + Depth = 0; + } + + public bool Contains(float2 point) + { + var min = Center - Size * 0.5f; + var max = Center + Size * 0.5f; + + return math.all(point >= min & point <= max); + } + + public void Split() + { + var childSize = Size / 2; + var offset = childSize / 2; + + foreach (var node in Children) + { + foreach (var id in Objects) + { + var pos = Root.Positions[id]; + if (node.Contains(pos)) + { + node.Objects.Add(id); + } + } + } + + Objects.Clear(); + + Children[0] = new QuadtreeNode(Root, Center + new float2(-offset.x, offset.y), childSize) + { + Depth = Depth + 1, + }; + Children[1] = new QuadtreeNode(Root, Center + new float2(offset.x, offset.y), childSize) + { + Depth = Depth + 1, + }; + Children[2] = new QuadtreeNode(Root, Center + new float2(-offset.x, -offset.y), childSize) + { + Depth = Depth + 1, + }; + Children[3] = new QuadtreeNode(Root, Center + new float2(offset.x, -offset.y), childSize) + { + Depth = Depth + 1, + }; + } + + public bool IsLeaf() + { + return Children[0].Size.x == 0; // 如果子节点未初始化,说明是叶子节点 + } + } +} diff --git a/Src/Unity/Scripts/Quadtree/IQuadtreeRoot.cs.meta b/Src/Core/Quadtree/QuadtreeNode.cs.meta similarity index 83% rename from Src/Unity/Scripts/Quadtree/IQuadtreeRoot.cs.meta rename to Src/Core/Quadtree/QuadtreeNode.cs.meta index a1cc8f6..f38829c 100644 --- a/Src/Unity/Scripts/Quadtree/IQuadtreeRoot.cs.meta +++ b/Src/Core/Quadtree/QuadtreeNode.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 625588063be34ab419d783b87c474a44 +guid: 837e58f0e4d63c2438b1037a76a5a6ee MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Src/Core/Sharp/BITSharp.cs b/Src/Core/Sharp/BITSharp.cs index 46cef9f..f670868 100644 --- a/Src/Core/Sharp/BITSharp.cs +++ b/Src/Core/Sharp/BITSharp.cs @@ -6,6 +6,7 @@ using System.IO; using System.Linq; using System.Reflection; using System.Text; +using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using BITKit.Mod; @@ -37,6 +38,7 @@ namespace BITKit } public class CodeGenerator:ICodeGenerator { + public virtual string Generate(Type type, string className = null) { if (type.IsInterface is false) throw new InvalidDataException("The type have to be interface"); diff --git a/Src/Core/Tween/BITween.cs b/Src/Core/Tween/BITween.cs index ce9c997..c2bcfda 100644 --- a/Src/Core/Tween/BITween.cs +++ b/Src/Core/Tween/BITween.cs @@ -35,7 +35,25 @@ namespace BITKit.Tween return new TweenSequence(); } - public static async UniTask Lerp(Action setter,T from,T to,float duration, Func lerp,CancellationToken cancellationToken = default) + public static async UniTask MoveToForward(Action setter, T from, T to, float delta, + Func func, CancellationToken cancellationToken = default) + { + setter(from); + while (Equals(from,to) is false && cancellationToken.IsCancellationRequested is false) + { + from = func(from, to, delta*BITApp.Time.DeltaTime); +#if UNITY_5_3_OR_NEWER + await UniTask.NextFrame(cancellationToken); +#else + + await UniTask.Yield(); +#endif + setter(from); + } + if (cancellationToken.IsCancellationRequested is false) + setter(to); + } + public static async UniTask Lerp(Action setter,T from,T to,float duration, Func func,CancellationToken cancellationToken = default) { var t = 0f; var delta = 1f / duration; @@ -44,7 +62,7 @@ namespace BITKit.Tween while (t < 1 && cancellationToken.IsCancellationRequested is false) { t = math.clamp(t + delta*BITApp.Time.DeltaTime, 0, 1); - var next = lerp(from, to, t); + var next = func(from, to, t); #if UNITY_5_3_OR_NEWER await UniTask.NextFrame(cancellationToken); #else diff --git a/Src/Core/UX/IUXDialogue.cs b/Src/Core/UX/IUXDialogue.cs index 1323284..990f9c7 100644 --- a/Src/Core/UX/IUXDialogue.cs +++ b/Src/Core/UX/IUXDialogue.cs @@ -7,7 +7,6 @@ namespace BITKit.UX public interface IUXDialogue { void Show(string content,string title = "Alert",Action confirmAction=null,Action onChoose=null); - - + } } diff --git a/Src/Core/UX/IUXPanel.cs b/Src/Core/UX/IUXPanel.cs index 07eec72..97b6292 100644 --- a/Src/Core/UX/IUXPanel.cs +++ b/Src/Core/UX/IUXPanel.cs @@ -1,4 +1,6 @@ using System; +using System.Runtime.CompilerServices; +using BITKit.StateMachine; using Cysharp.Threading.Tasks; // ReSharper disable UnassignedGetOnlyAutoProperty @@ -12,7 +14,7 @@ namespace BITKit.UX /// ⭐异步打开与关闭 /// ⭐当前可见状态 /// ⭐基本UI导航回调 - public interface IUXPanel:IEntryElement + public interface IUXPanel:IStateAsync { /// /// 是否为窗口,用于覆盖其他面板 diff --git a/Src/Core/Utils/Data.cs b/Src/Core/Utils/Data.cs index 865efb5..50f005a 100644 --- a/Src/Core/Utils/Data.cs +++ b/Src/Core/Utils/Data.cs @@ -79,9 +79,6 @@ namespace BITKit var action = x as Action; action.Invoke(value); }); - if (value is not JToken) - Objects.TryInsert(key, value); - } public static bool TryGetValue(string key, out T value) { diff --git a/Src/Core/Wrapper/IWrapper.cs b/Src/Core/Wrapper/IWrapper.cs index e782676..86cc7de 100644 --- a/Src/Core/Wrapper/IWrapper.cs +++ b/Src/Core/Wrapper/IWrapper.cs @@ -19,7 +19,10 @@ namespace BITKit var type = typeof(T); if (type == typeof(string)) { - _value = string.Empty.As(); + if (string.Empty is T t) + { + _value = t; + } return; } if(type.IsAbstract || type.IsInterface)return; diff --git a/Src/Core/kcp2k/kcp/Pool.cs b/Src/Core/kcp2k/kcp/Pool.cs index 81b5289..717a0ef 100644 --- a/Src/Core/kcp2k/kcp/Pool.cs +++ b/Src/Core/kcp2k/kcp/Pool.cs @@ -33,7 +33,7 @@ namespace kcp2k // return an element to the pool public void Return(T item) { - objectResetter(item); + objectResetter?.Invoke(item); objects.Push(item); } diff --git a/Src/Unity/BitMask.meta b/Src/Unity/BitMask.meta new file mode 100644 index 0000000..667aa9f --- /dev/null +++ b/Src/Unity/BitMask.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c72b222167e45bb4b91034232c2164b5 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Src/Unity/BitMask/Net.Project.B.BitMask.Unity.asmdef b/Src/Unity/BitMask/Net.Project.B.BitMask.Unity.asmdef new file mode 100644 index 0000000..724e3fd --- /dev/null +++ b/Src/Unity/BitMask/Net.Project.B.BitMask.Unity.asmdef @@ -0,0 +1,19 @@ +{ + "name": "Net.Project.B.BitMask.Unity", + "rootNamespace": "", + "references": [ + "GUID:14fe60d984bf9f84eac55c6ea033a8f4", + "GUID:6ef4ed8ff60a7aa4bb60a8030e6f4008", + "GUID:d525ad6bd40672747bde77962f1c401e", + "GUID:49b49c76ee64f6b41bf28ef951cb0e50" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Src/UnityPluginsSupport/Polaris/BITKit.Extensions.Polaris.asmdef.meta b/Src/Unity/BitMask/Net.Project.B.BitMask.Unity.asmdef.meta similarity index 76% rename from Src/UnityPluginsSupport/Polaris/BITKit.Extensions.Polaris.asmdef.meta rename to Src/Unity/BitMask/Net.Project.B.BitMask.Unity.asmdef.meta index a8f6fb9..8f499bd 100644 --- a/Src/UnityPluginsSupport/Polaris/BITKit.Extensions.Polaris.asmdef.meta +++ b/Src/Unity/BitMask/Net.Project.B.BitMask.Unity.asmdef.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 4f23b1d01b5fa044784cf0a3dd9794eb +guid: 1f7ecae3bbd1640418dd25dd62e6a9d3 AssemblyDefinitionImporter: externalObjects: {} userData: diff --git a/Src/Unity/BitMask/NewScriptableBitMask.asset b/Src/Unity/BitMask/NewScriptableBitMask.asset new file mode 100644 index 0000000..eedb884 --- /dev/null +++ b/Src/Unity/BitMask/NewScriptableBitMask.asset @@ -0,0 +1,17 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 4bc860277386c244aac5bc8ec6cbb54b, type: 3} + m_Name: NewScriptableBitMask + m_EditorClassIdentifier: + implements: {fileID: 11400000, guid: 1e642bf42ffa8bd429cbfdeeefabaecd, type: 2} + dictionary: + _serializedList: [] diff --git a/Src/Unity/BitMask/NewScriptableBitMask.asset.meta b/Src/Unity/BitMask/NewScriptableBitMask.asset.meta new file mode 100644 index 0000000..168183d --- /dev/null +++ b/Src/Unity/BitMask/NewScriptableBitMask.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 8411467f1adb3754ba46f890d7be5ff4 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Src/Unity/BitMask/NewScriptableBitMaskSample.asset b/Src/Unity/BitMask/NewScriptableBitMaskSample.asset new file mode 100644 index 0000000..b510c21 --- /dev/null +++ b/Src/Unity/BitMask/NewScriptableBitMaskSample.asset @@ -0,0 +1,38 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 9fab0368c5976f24dafd43f829e91aab, type: 3} + m_Name: NewScriptableBitMaskSample + m_EditorClassIdentifier: + implements: + - rid: 7572707602352832517 + - rid: 7572707602352832518 + - rid: 7572707602352832519 + - rid: 7572707602352832520 + - rid: 7572707602352832521 + references: + version: 2 + RefIds: + - rid: 7572707602352832517 + type: {class: SampleInterface1, ns: Net.BITKit.BitMask, asm: Net.Project.B.BitMask.Unity} + data: + - rid: 7572707602352832518 + type: {class: SampleInterface2, ns: Net.BITKit.BitMask, asm: Net.Project.B.BitMask.Unity} + data: + - rid: 7572707602352832519 + type: {class: SampleInterface3, ns: Net.BITKit.BitMask, asm: Net.Project.B.BitMask.Unity} + data: + - rid: 7572707602352832520 + type: {class: SampleInterface4, ns: Net.BITKit.BitMask, asm: Net.Project.B.BitMask.Unity} + data: + - rid: 7572707602352832521 + type: {class: SampleInterface5, ns: Net.BITKit.BitMask, asm: Net.Project.B.BitMask.Unity} + data: diff --git a/Src/Unity/BitMask/NewScriptableBitMaskSample.asset.meta b/Src/Unity/BitMask/NewScriptableBitMaskSample.asset.meta new file mode 100644 index 0000000..2aaea86 --- /dev/null +++ b/Src/Unity/BitMask/NewScriptableBitMaskSample.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 1e642bf42ffa8bd429cbfdeeefabaecd +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Src/Unity/BitMask/ScriptableBitMask.cs b/Src/Unity/BitMask/ScriptableBitMask.cs new file mode 100644 index 0000000..e73feb6 --- /dev/null +++ b/Src/Unity/BitMask/ScriptableBitMask.cs @@ -0,0 +1,192 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using AYellowpaper.SerializedCollections; +using BITKit; +using NUnit.Framework; +using UnityEngine; +using UnityEngine.Pool; +using UnityEngine.UIElements; +using YamlDotNet.Serialization; +using YamlDotNet.Serialization.NamingConventions; +#if UNITY_EDITOR + using UnityEditor; +#endif +namespace Net.BITKit.BitMask +{ + + public class ScriptableBitMask : ScriptableObject,ISerializationCallbackReceiver + { + [SerializeField] public ScriptableImplements implements; + + internal readonly Dictionary> Dictionary=new(); + [SerializeField] public string yaml; + + public Type Type => implements.Type; + + public Type[] Types => implements ? implements.Types : Array.Empty(); + + private void OnEnable() + { + OnBeforeSerialize(); + } + + public void OnBeforeSerialize() + { + if(string.IsNullOrEmpty(yaml))return; + var deserializer = new DeserializerBuilder() + .WithNamingConvention(CamelCaseNamingConvention.Instance) // 驼峰命名 + .Build(); + Dictionary.Clear(); + var newDictionary = deserializer.Deserialize>>(yaml); + foreach (var pair in newDictionary) + { + Dictionary[pair.Key] = pair.Value; + } + } + + public void OnAfterDeserialize() + { + if(Dictionary.Count is 0)return; + var serializer = new SerializerBuilder() + .WithNamingConvention(CamelCaseNamingConvention.Instance) + .Build(); + yaml = serializer.Serialize(Dictionary); + } + public IDictionary> FlagMask + { + get + { + var typeDictionary = implements.Types.ToDictionary(x => x.Name, x => x); + + var nextDictionary = new Dictionary>(); + + foreach (var (key, d) in Dictionary) + { + if (typeDictionary.TryGetValue(key, out var type) is false) continue; + var hashSet = new HashSet(); + foreach (var x in d) + { + if(typeDictionary.TryGetValue(x,out var t1) is false)continue; + hashSet.Add(t1); + } + nextDictionary[type]=hashSet; + } + + foreach (var type in Types) + { + if (nextDictionary.TryAdd(type, new HashSet())) ; + } + + return nextDictionary; + } + } + + + } +#if UNITY_EDITOR + [CustomEditor(typeof(ScriptableBitMask),true)] + public sealed class ScriptableBitMaskEditor:Editor + { + private const int CellSize = 24; + public override VisualElement CreateInspectorGUI() + { + if (serializedObject.targetObject is not ScriptableBitMask scriptableBitMask) + return base.CreateInspectorGUI(); + + var inspector = new VisualElement + { + style = + { + flexDirection = FlexDirection.Column + } + }; + + var defaultPropertyFields = inspector.Create(); + + BITInspectorExtensions.FillDefaultInspector(defaultPropertyFields.Create(),serializedObject,false); + + var root = inspector.Create(); + + root.style.flexDirection = new StyleEnum(FlexDirection.Row); + + + var allFlags = scriptableBitMask.Types; + + var labelRow = root.Create(); + var toggleRot = root.Create(); + + labelRow.style.alignSelf = new StyleEnum(Align.FlexEnd); + + for (var x = -1; x < allFlags.Length; x++) + { + if (x is -1) + { + var container = toggleRot.Create(); + container.style.flexDirection = FlexDirection.Row; + foreach (var type in scriptableBitMask.Types.Reverse()) + { + var text = container.Create