当前代码实现了一个增强型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
}