增强型ECS(Entity-Component-System)框架

当前代码实现了一个增强型ECS(Entity-Component-System)框架的辅助模块,主要提供以下核心功能:

关系管理 (RelationManager)

自动维护实体之间的双向关系(如父子、引用等),支持通过枚举类型定义关系种类。

每条关系本身作为一个实体存在,便于附加额外数据。

当源或目标实体销毁时,自动清理所有相关关系和对应关系实体,保证一致性。

组件字段索引 (IndexManager)

支持为特定组件的字段建立唯一或非唯一索引,通过IndexedAttribute标记需要索引的字段。

监听组件添加事件自动更新索引,查询时可通过键快速定位实体。

由于ECS核心可能不提供组件移除时的旧值,索引更新需手动处理组件移除场景。

事件通知中心 (NotificationCenter)

提供组件添加和移除的订阅机制,允许处理程序在指定执行上下文(同步、线程池、UI线程)中执行。

移除通知仅传递实体,因为底层事件不包含被移除的组件值。

内部缓存组件获取委托,避免反射开销。

命令系统 (CommandBase 和 CommandSystem<,>)

提供异步命令模式:命令继承CommandBase并实现Execute,提交后通过系统排队执行,结果通过Task返回。

支持异常传播,命令执行过程中的异常可通过任务的SetException传递。

ECS上下文 (EcsContext)

整合以上所有功能,包含一个EcsWorld(核心ECS世界),并提供统一入口访问关系管理器、索引管理器和通知中心。

支持可选的后台更新线程,通过定时器或信号触发系统更新,适合独立于主循环运行的场景。

实现了IDisposable,用于正确停止后台线程并释放资源。

这些功能共同扩展了基础ECS框架的能力,使其更适合复杂游戏或应用开发中的关系追踪、数据快速检索、事件驱动和异步命令处理等需求。

csharp 复制代码
namespace ECSEnhanced
{
    #region 特性与枚举

    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
    public sealed class IndexedAttribute : Attribute
    {
        public bool Unique { get; set; }
    }

    public enum ExecutionContext
    {
        Sync,
        ThreadPool,
        UI
    }

    #endregion

    #region 关系管理器

    /// <summary>
    /// 管理实体间关系,自动维护双向索引,并确保关系实体正确销毁。
    /// </summary>
    public class RelationManager
    {
        private readonly EntityManager _entityManager;
        private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();

        // (source, relationType) -> targets
        private readonly Dictionary<(Entity, object), HashSet<Entity>> _forward = new();
        // (target, relationType) -> sources
        private readonly Dictionary<(Entity, object), HashSet<Entity>> _reverse = new();
        // (source, target, relationType) -> relationEntity
        private readonly Dictionary<(Entity, Entity, object), Entity> _relationEntityMap = new();

        public RelationManager(EntityManager entityManager)
        {
            _entityManager = entityManager ?? throw new ArgumentNullException(nameof(entityManager));
            _entityManager.EntityDestroyed += OnEntityDestroyed;
        }

        private void OnEntityDestroyed(Entity entity) => RemoveAllRelations(entity);

        public void AddRelation<TRelation>(Entity source, Entity target, TRelation relationType) where TRelation : Enum
        {
            if (!_entityManager.IsEntityValid(source)) throw new ArgumentException("Invalid source", nameof(source));
            if (!_entityManager.IsEntityValid(target)) throw new ArgumentException("Invalid target", nameof(target));

            // 创建关系实体并附加组件
            var relEntity = _entityManager.CreateEntity();
            _entityManager.AddComponent(relEntity, new RelationComponent<TRelation>
            {
                Source = source,
                Target = target,
                RelationType = relationType
            });

            _lock.EnterWriteLock();
            try
            {
                var fwdKey = (source, (object)relationType);
                if (!_forward.TryGetValue(fwdKey, out var fwdSet))
                    _forward[fwdKey] = fwdSet = new HashSet<Entity>();
                fwdSet.Add(target);

                var revKey = (target, (object)relationType);
                if (!_reverse.TryGetValue(revKey, out var revSet))
                    _reverse[revKey] = revSet = new HashSet<Entity>();
                revSet.Add(source);

                var relKey = (source, target, (object)relationType);
                _relationEntityMap[relKey] = relEntity;
            }
            finally { _lock.ExitWriteLock(); }
        }

        public bool RemoveRelation<TRelation>(Entity source, Entity target, TRelation relationType) where TRelation : Enum
        {
            Entity relEntity;
            _lock.EnterWriteLock();
            try
            {
                var fwdKey = (source, (object)relationType);
                if (!_forward.TryGetValue(fwdKey, out var fwdSet) || !fwdSet.Remove(target))
                    return false;

                if (fwdSet.Count == 0) _forward.Remove(fwdKey);

                var revKey = (target, (object)relationType);
                if (_reverse.TryGetValue(revKey, out var revSet) && revSet.Remove(source) && revSet.Count == 0)
                    _reverse.Remove(revKey);

                var relKey = (source, target, (object)relationType);
                if (!_relationEntityMap.TryGetValue(relKey, out relEntity))
                    return false; // 映射丢失,视为删除失败

                _relationEntityMap.Remove(relKey);
            }
            finally { _lock.ExitWriteLock(); }

            // 释放锁后销毁关系实体,避免递归锁
            if (relEntity.IsValid)
                _entityManager.DestroyEntity(relEntity);

            return true;
        }

        public void RemoveAllRelations(Entity entity)
        {
            var entitiesToDestroy = new List<Entity>();

            _lock.EnterWriteLock();
            try
            {
                // 收集所有受影响的元组,避免在迭代中修改集合
                var affectedRelations = new List<(Entity, Entity, object)>();

                // 移除所有以 entity 为源的关系
                var fwdKeys = _forward.Keys.Where(k => k.Item1 == entity).ToList();
                foreach (var key in fwdKeys)
                {
                    if (_forward.TryGetValue(key, out var targets))
                    {
                        foreach (var target in targets)
                            affectedRelations.Add((entity, target, key.Item2));
                    }
                    _forward.Remove(key);
                }

                // 移除所有以 entity 为目标的关系
                var revKeys = _reverse.Keys.Where(k => k.Item1 == entity).ToList();
                foreach (var key in revKeys)
                {
                    if (_reverse.TryGetValue(key, out var sources))
                    {
                        foreach (var source in sources)
                            affectedRelations.Add((source, entity, key.Item2));
                    }
                    _reverse.Remove(key);
                }

                // 从映射中取出关系实体并准备销毁
                foreach (var (src, tgt, relType) in affectedRelations)
                {
                    var relKey = (src, tgt, relType);
                    if (_relationEntityMap.TryGetValue(relKey, out var relEntity))
                    {
                        entitiesToDestroy.Add(relEntity);
                        _relationEntityMap.Remove(relKey);
                    }
                }
            }
            finally { _lock.ExitWriteLock(); }

            // 释放锁后销毁所有关系实体
            foreach (var relEntity in entitiesToDestroy)
            {
                if (relEntity.IsValid)
                    _entityManager.DestroyEntity(relEntity);
            }
        }

        public IReadOnlyList<Entity> GetRelatedTargets<TRelation>(Entity source, TRelation relationType) where TRelation : Enum
        {
            _lock.EnterReadLock();
            try
            {
                var key = (source, (object)relationType);
                return _forward.TryGetValue(key, out var set) ? set.ToList() : Array.Empty<Entity>();
            }
            finally { _lock.ExitReadLock(); }
        }

        public IReadOnlyList<Entity> GetRelatedSources<TRelation>(Entity target, TRelation relationType) where TRelation : Enum
        {
            _lock.EnterReadLock();
            try
            {
                var key = (target, (object)relationType);
                return _reverse.TryGetValue(key, out var set) ? set.ToList() : Array.Empty<Entity>();
            }
            finally { _lock.ExitReadLock(); }
        }

        public bool HasRelation<TRelation>(Entity source, Entity target, TRelation relationType) where TRelation : Enum
        {
            _lock.EnterReadLock();
            try
            {
                var key = (source, (object)relationType);
                return _forward.TryGetValue(key, out var set) && set.Contains(target);
            }
            finally { _lock.ExitReadLock(); }
        }

        private struct RelationComponent<TRelation> : IComponent where TRelation : Enum
        {
            public Entity Source;
            public Entity Target;
            public TRelation RelationType;
        }
    }

    #endregion

    #region 索引管理器(适配ECS.Core)

    /// <summary>
    /// 组件字段索引管理器,支持唯一/非唯一索引,自动响应组件添加和实体销毁。
    /// 注意:由于ECS.Core的ComponentRemoved事件不提供旧组件值,索引无法自动处理组件移除。
    ///      若需单独移除组件,请手动调用索引的Remove方法或重建索引。
    /// </summary>
    public class IndexManager
    {
        private readonly EntityManager _entityManager;
        private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();
        private readonly Dictionary<Type, IIndex> _indexes = new();

        public IndexManager(EntityManager entityManager)
        {
            _entityManager = entityManager ?? throw new ArgumentNullException(nameof(entityManager));
            _entityManager.ComponentAdded += OnComponentAdded;
            _entityManager.EntityDestroyed += OnEntityDestroyed;
            // ComponentRemoved 事件无法利用,因为无法获得组件值
        }

        public void RegisterIndex<TComponent>(Func<TComponent, object> keySelector, bool isUnique = false)
            where TComponent : struct, IComponent
        {
            _lock.EnterWriteLock();
            try
            {
                var type = typeof(TComponent);
                if (_indexes.ContainsKey(type))
                    throw new InvalidOperationException($"Index already registered for {type.Name}");

                var index = new Index<TComponent>(keySelector, isUnique);
                _indexes[type] = index;
                index.Rebuild(_entityManager);
            }
            finally { _lock.ExitWriteLock(); }
        }

        public Entity FindEntity<TComponent>(object key) where TComponent : struct, IComponent
        {
            _lock.EnterReadLock();
            try
            {
                if (!_indexes.TryGetValue(typeof(TComponent), out var idx))
                    throw new KeyNotFoundException($"No index for {typeof(TComponent).Name}");
                return ((Index<TComponent>)idx).FindEntity(key);
            }
            finally { _lock.ExitReadLock(); }
        }

        public IReadOnlyList<Entity> FindEntities<TComponent>(object key) where TComponent : struct, IComponent
        {
            _lock.EnterReadLock();
            try
            {
                if (!_indexes.TryGetValue(typeof(TComponent), out var idx))
                    throw new KeyNotFoundException($"No index for {typeof(TComponent).Name}");
                return ((Index<TComponent>)idx).FindEntities(key);
            }
            finally { _lock.ExitReadLock(); }
        }

        public bool Contains<TComponent>(object key) where TComponent : struct, IComponent
        {
            _lock.EnterReadLock();
            try
            {
                if (!_indexes.TryGetValue(typeof(TComponent), out var idx))
                    return false;
                return ((Index<TComponent>)idx).Contains(key);
            }
            finally { _lock.ExitReadLock(); }
        }

        private void OnComponentAdded(Entity entity, Type componentType)
        {
            _lock.EnterWriteLock();
            try
            {
                if (_indexes.TryGetValue(componentType, out var idx))
                {
                    idx.AddFromEntity(entity, _entityManager);
                }
            }
            finally { _lock.ExitWriteLock(); }
        }

        private void OnEntityDestroyed(Entity entity)
        {
            _lock.EnterWriteLock();
            try
            {
                foreach (var idx in _indexes.Values)
                    idx.RemoveEntity(entity);
            }
            finally { _lock.ExitWriteLock(); }
        }

        #region Index internal classes
        private interface IIndex
        {
            void AddFromEntity(Entity entity, EntityManager em);
            void RemoveEntity(Entity entity);
            void Clear();
            void Rebuild(EntityManager em);
        }

        private class Index<TComponent> : IIndex where TComponent : struct, IComponent
        {
            private readonly Func<TComponent, object> _keySelector;
            public bool IsUnique { get; }
            private readonly Dictionary<object, Entity> _unique = new();
            private readonly Dictionary<object, HashSet<Entity>> _nonUnique = new();
            private readonly Dictionary<Entity, object> _entityToKey = new();

            public Index(Func<TComponent, object> keySelector, bool isUnique)
            {
                _keySelector = keySelector;
                IsUnique = isUnique;
            }

            public void AddFromEntity(Entity entity, EntityManager em)
            {
                var component = em.GetComponent<TComponent>(entity);
                Add(entity, component);
            }

            private void Add(Entity entity, TComponent component)
            {
                var key = _keySelector(component);
                if (IsUnique)
                {
                    if (_unique.ContainsKey(key))
                        throw new InvalidOperationException($"Duplicate key '{key}' for unique index on {typeof(TComponent).Name}");
                    _unique[key] = entity;
                    _entityToKey[entity] = key;
                }
                else
                {
                    if (!_nonUnique.TryGetValue(key, out var set))
                        _nonUnique[key] = set = new HashSet<Entity>();
                    set.Add(entity);
                    _entityToKey[entity] = key;
                }
            }

            public void RemoveEntity(Entity entity)
            {
                if (_entityToKey.TryGetValue(entity, out var key))
                {
                    if (IsUnique)
                        _unique.Remove(key);
                    else if (_nonUnique.TryGetValue(key, out var set))
                    {
                        set.Remove(entity);
                        if (set.Count == 0)
                            _nonUnique.Remove(key);
                    }
                    _entityToKey.Remove(entity);
                }
            }

            public Entity FindEntity(object key)
            {
                if (!IsUnique) throw new InvalidOperationException("Index is not unique");
                return _unique.TryGetValue(key, out var e) ? e : Entity.Invalid;
            }

            public IReadOnlyList<Entity> FindEntities(object key)
            {
                if (IsUnique)
                    return _unique.TryGetValue(key, out var e) ? new[] { e } : Array.Empty<Entity>();
                return _nonUnique.TryGetValue(key, out var set) ? set.ToList() : Array.Empty<Entity>();
            }

            public bool Contains(object key)
            {
                return IsUnique ? _unique.ContainsKey(key) : _nonUnique.ContainsKey(key);
            }

            public void Clear()
            {
                _unique.Clear();
                _nonUnique.Clear();
                _entityToKey.Clear();
            }

            public void Rebuild(EntityManager em)
            {
                Clear();
                var query = new EntityQuery(em).WithComponent<TComponent>();
                foreach (var entity in query.Execute())
                {
                    var comp = em.GetComponent<TComponent>(entity);
                    Add(entity, comp);
                }
            }

            void IIndex.AddFromEntity(Entity entity, EntityManager em) => AddFromEntity(entity, em);
            void IIndex.RemoveEntity(Entity entity) => RemoveEntity(entity);
            void IIndex.Rebuild(EntityManager em) => Rebuild(em);
        }
        #endregion
    }

    #endregion

    #region 通知中心(适配ECS.Core)

    /// <summary>
    /// 组件事件通知中心,支持不同执行上下文。
    /// 基于ECS.Core提供的事件:ComponentAdded (Entity, Type) 和 ComponentRemoved (Entity, Type)。
    /// 注意:ComponentRemoved不提供被移除的组件值,故移除订阅仅传递实体。
    /// </summary>
    public class NotificationCenter
    {
        private readonly EntityManager _entityManager;
        private readonly SynchronizationContext? _uiContext;
        private readonly ConcurrentDictionary<Type, List<Subscription>> _subscriptions = new();

        // 缓存组件获取器,避免重复反射
        private static readonly ConcurrentDictionary<Type, Func<EntityManager, Entity, object>> _componentGetterCache = new();

        public NotificationCenter(EntityManager entityManager, SynchronizationContext? uiContext = null)
        {
            _entityManager = entityManager ?? throw new ArgumentNullException(nameof(entityManager));
            _uiContext = uiContext ?? SynchronizationContext.Current;
            _entityManager.ComponentAdded += OnComponentAdded;
            _entityManager.ComponentRemoved += OnComponentRemoved;
        }

        public void SubscribeAdded<TComponent>(Action<Entity, TComponent> handler, ExecutionContext context = ExecutionContext.Sync) where TComponent : struct, IComponent
            => AddSubscription(typeof(TComponent), EventType.Added, handler, context);

        public void SubscribeRemoved<TComponent>(Action<Entity> handler, ExecutionContext context = ExecutionContext.Sync) where TComponent : struct, IComponent
            => AddSubscription(typeof(TComponent), EventType.Removed, handler, context);

        public void UnsubscribeAdded<TComponent>(Action<Entity, TComponent> handler) where TComponent : struct, IComponent
            => RemoveSubscription(typeof(TComponent), EventType.Added, handler);
        public void UnsubscribeRemoved<TComponent>(Action<Entity> handler) where TComponent : struct, IComponent
            => RemoveSubscription(typeof(TComponent), EventType.Removed, handler);

        private void AddSubscription(Type type, EventType ev, Delegate handler, ExecutionContext ctx)
        {
            var list = _subscriptions.GetOrAdd(type, _ => new List<Subscription>());
            lock (list)
            {
                // 创建强类型调用包装器,避免 DynamicInvoke
                Action<object> invokeAction = ev switch
                {
                    EventType.Added => args =>
                    {
                        var (entity, component) = ((Entity, object))args;
                        ((Action<Entity, object>)handler)(entity, component);
                    }
                    ,
                    EventType.Removed => args =>
                    {
                        var entity = (Entity)args;
                        ((Action<Entity>)handler)(entity);
                    }
                    ,
                    _ => throw new InvalidOperationException()
                };
                list.Add(new Subscription { Type = ev, Handler = handler, Context = ctx, InvokeAction = invokeAction });
            }
        }

        private void RemoveSubscription(Type type, EventType ev, Delegate handler)
        {
            if (_subscriptions.TryGetValue(type, out var list))
            {
                lock (list)
                {
                    list.RemoveAll(s => s.Type == ev && s.Handler == handler);
                }
            }
        }

        private void OnComponentAdded(Entity entity, Type type)
        {
            if (!_subscriptions.TryGetValue(type, out var list)) return;

            object component;
            try
            {
                // 获取或创建组件获取器委托
                var getter = _componentGetterCache.GetOrAdd(type, t =>
                {
                    var method = typeof(EntityManager).GetMethod("GetComponent", new[] { typeof(Entity) })!
                        .MakeGenericMethod(t);
                    return (em, e) => method.Invoke(em, new object[] { e })!;
                });
                component = getter(_entityManager, entity);
            }
            catch (Exception)
            {
                // 理论上不会发生,若发生则跳过通知
                // TODO: 记录日志
                return;
            }

            var subs = list.Where(s => s.Type == EventType.Added).ToList();
            Invoke(subs, (entity, component));
        }

        private void OnComponentRemoved(Entity entity, Type type)
        {
            if (!_subscriptions.TryGetValue(type, out var list)) return;
            var subs = list.Where(s => s.Type == EventType.Removed).ToList();
            Invoke(subs, entity);
        }

        private void Invoke(IEnumerable<Subscription> subs, object args)
        {
            foreach (var sub in subs)
            {
                switch (sub.Context)
                {
                    case ExecutionContext.Sync:
                        sub.InvokeAction(args);
                        break;
                    case ExecutionContext.ThreadPool:
                        ThreadPool.QueueUserWorkItem(_ => sub.InvokeAction(args));
                        break;
                    case ExecutionContext.UI:
                        _uiContext?.Post(_ => sub.InvokeAction(args), null);
                        break;
                }
            }
        }

        private enum EventType { Added, Removed }
        private class Subscription
        {
            public EventType Type { get; set; }
            public Delegate Handler { get; set; } = null!;
            public ExecutionContext Context { get; set; }
            public Action<object> InvokeAction { get; set; } = null!;
        }
    }

    #endregion

    #region 命令系统

    public abstract class CommandBase<TResult>
    {
        private readonly TaskCompletionSource<TResult> _tcs = new();
        public Task<TResult> Task => _tcs.Task;
        protected void SetResult(TResult result) => _tcs.TrySetResult(result);
        public void SetException(Exception ex) => _tcs.TrySetException(ex);
        public abstract void Execute();
    }

    public class CommandSystem<TCommand, TResult> : SystemBase where TCommand : CommandBase<TResult>
    {
        private readonly ConcurrentQueue<TCommand> _queue = new();
        public void Submit(TCommand cmd) => _queue.Enqueue(cmd);
        public sealed override void Update(float deltaTime)
        {
            while (_queue.TryDequeue(out var cmd))
            {
                try
                {
                    cmd.Execute();
                }
                catch (Exception ex)
                {
                    // 如果 Execute 内部未处理异常,则通过 SetException 传播
                    cmd.SetException(ex);
                }
            }
        }
    }

    #endregion

    #region ECS上下文

    public class EcsContext : IDisposable
    {
        private readonly CancellationTokenSource _cts = new();
        private readonly AutoResetEvent _updateSignal = new(false);
        private Thread? _backgroundThread;
        private bool _disposed;

        public EcsWorld World { get; }
        public RelationManager Relations { get; }
        public IndexManager Indexes { get; }
        public NotificationCenter Notifications { get; }

        public EcsContext(bool enableBackgroundUpdate = false, SynchronizationContext? uiContext = null)
        {
            World = new EcsWorld();
            Relations = new RelationManager(World.EntityManager);
            Indexes = new IndexManager(World.EntityManager);
            Notifications = new NotificationCenter(World.EntityManager, uiContext);

            if (enableBackgroundUpdate)
                StartBackgroundUpdate();
        }

        public void StartBackgroundUpdate(int intervalMs = 0)
        {
            if (_backgroundThread != null)
                throw new InvalidOperationException("Background thread already running.");

            _backgroundThread = new Thread(() => BackgroundLoop(intervalMs))
            {
                IsBackground = true,
                Name = "EcsContext_UpdateLoop"
            };
            _backgroundThread.Start();
        }

        private void BackgroundLoop(int intervalMs)
        {
            while (!_cts.IsCancellationRequested)
            {
                try
                {
                    World.Update(0);
                }
                catch (Exception)
                {
                    // TODO: 记录日志,但避免中断循环
                }

                if (intervalMs > 0)
                    Thread.Sleep(intervalMs);
                else
                    _updateSignal.WaitOne();
            }
        }

        public void SignalUpdate() => _updateSignal.Set();

        public void StopBackgroundUpdate()
        {
            if (_backgroundThread == null) return;
            _cts.Cancel();
            _updateSignal.Set();
            if (!_backgroundThread.Join(TimeSpan.FromSeconds(5)))
            {
                // 超时处理,避免强制终止线程
            }
            _backgroundThread = null;
        }

        public void RegisterSystem(SystemBase system) => World.SystemManager.RegisterSystem(system);

        public void Dispose()
        {
            if (_disposed) return;
            StopBackgroundUpdate();
            World.Dispose();
            _cts.Dispose();
            _updateSignal.Dispose();
            _disposed = true;
        }
    }

    #endregion
}
相关推荐
ai产品老杨2 小时前
万物互联的视频底座:基于GB28181/RTSP的多协议融合与边缘推流架构解析
架构·音视频
有个人神神叨叨3 小时前
AI Coding 时代的企业级应用架构
人工智能·架构
挨踢学霸4 小时前
技术全面重构|MsgHelper 新版深度拆解:交互、视觉与逻辑的底层优化(二)
经验分享·笔记·微信·架构·自动化
njsgcs4 小时前
solidworks导出展开 c# ExportFlatPatternView方法
c#
格林威4 小时前
工业相机图像高速存储(C#版):内存映射文件方法,附Basler相机C#实战代码!
开发语言·人工智能·数码相机·c#·机器视觉·工业相机·堡盟相机
缺点内向4 小时前
C#实战:使用Spire.Doc for .NET 获取并替换Word文档中的字体
c#·自动化·word·.net
荔枝吻4 小时前
【保姆级喂饭教程】Visual Studio 2026 中创建基于 c# 的 WinForms 入门教程
ide·c#·visual studio
浩瀚之水_csdn4 小时前
Flask 深度解析:从微内核到企业级架构
python·架构·flask
数据知道4 小时前
MongoDB复制集架构原理:Primary、Secondary 与 Arbiter 的角色分工
数据库·mongodb·架构