2026-07-18 19:50:31 +08:00
|
|
|
using System.Diagnostics;
|
|
|
|
|
using R3;
|
|
|
|
|
|
2026-07-18 19:03:37 +08:00
|
|
|
namespace OECS;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// The central container for all ECS state.
|
|
|
|
|
///
|
|
|
|
|
/// Manages entity lifecycle, component storage, and provides the primary
|
|
|
|
|
/// API for adding, removing, and querying components.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public class World : IDisposable
|
|
|
|
|
{
|
2026-07-18 19:53:58 +08:00
|
|
|
/// <summary>
|
|
|
|
|
/// The reserved entity ID for the singleton entity.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public static readonly Entity SingletonEntity = new(1, 1);
|
|
|
|
|
|
2026-07-18 19:03:37 +08:00
|
|
|
private readonly EntityAllocator _allocator;
|
|
|
|
|
private readonly ComponentStore _components;
|
2026-07-18 19:38:10 +08:00
|
|
|
private readonly CommandQueue _commands;
|
2026-07-18 19:42:59 +08:00
|
|
|
private readonly RelationshipIndex _relationships;
|
2026-07-18 19:50:31 +08:00
|
|
|
private readonly ChangeBuffer _changes;
|
2026-07-18 19:53:58 +08:00
|
|
|
private bool _singletonCreated;
|
2026-07-18 20:13:25 +08:00
|
|
|
private bool _disposed;
|
2026-07-18 19:50:31 +08:00
|
|
|
|
2026-07-18 21:22:23 +08:00
|
|
|
// Deferred structural mutation support: when iterating entities,
|
|
|
|
|
// AddComponent, RemoveComponent, and DestroyEntity are buffered and
|
|
|
|
|
// applied after the iteration completes.
|
|
|
|
|
private int _iterationDepth;
|
|
|
|
|
private readonly List<PendingMutation> _pendingMutations = new();
|
|
|
|
|
|
|
|
|
|
private enum PendingMutationKind { AddComponent, RemoveComponent, DestroyEntity }
|
|
|
|
|
|
|
|
|
|
private struct PendingMutation
|
|
|
|
|
{
|
|
|
|
|
public PendingMutationKind Kind;
|
|
|
|
|
public Entity Entity;
|
|
|
|
|
public object? Component; // boxed struct for AddComponent
|
|
|
|
|
public Type ComponentType;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Auto-dirty-marking: during iteration, component accesses via
|
|
|
|
|
// GetComponent<T> are tracked. When the iteration scope ends,
|
|
|
|
|
// all accessed components are automatically marked as modified.
|
|
|
|
|
// Outside of iteration, MarkModified<T> must be called explicitly.
|
2026-07-18 19:50:31 +08:00
|
|
|
private readonly HashSet<(Entity Entity, Type ComponentType)> _accessedComponents = new();
|
|
|
|
|
private readonly HashSet<(Entity Entity, Type ComponentType)> _markedModified = new();
|
2026-07-18 19:03:37 +08:00
|
|
|
|
|
|
|
|
public World()
|
|
|
|
|
{
|
|
|
|
|
_allocator = new EntityAllocator();
|
|
|
|
|
_components = new ComponentStore();
|
2026-07-18 19:38:10 +08:00
|
|
|
_commands = new CommandQueue();
|
2026-07-18 19:42:59 +08:00
|
|
|
_relationships = new RelationshipIndex();
|
2026-07-18 19:50:31 +08:00
|
|
|
_changes = new ChangeBuffer();
|
2026-07-18 19:03:37 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Entity Management ────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Creates a new entity and returns its handle.
|
2026-07-18 19:50:31 +08:00
|
|
|
/// Automatically marks an <see cref="ChangeKind.EntityAdded"/> change.
|
2026-07-18 19:03:37 +08:00
|
|
|
/// </summary>
|
|
|
|
|
public Entity CreateEntity()
|
|
|
|
|
{
|
2026-07-18 19:50:31 +08:00
|
|
|
var entity = _allocator.Allocate();
|
|
|
|
|
_changes.Pending.MarkEntityAdded(entity);
|
|
|
|
|
return entity;
|
2026-07-18 19:03:37 +08:00
|
|
|
}
|
|
|
|
|
|
2026-07-18 21:06:57 +08:00
|
|
|
/// <summary>
|
|
|
|
|
/// Creates an entity with a specific handle. Used during deserialization
|
|
|
|
|
/// to restore entities with their original IDs and versions.
|
|
|
|
|
/// Does NOT mark an EntityAdded change (caller is responsible for
|
|
|
|
|
/// posting changes after the full load).
|
|
|
|
|
/// </summary>
|
|
|
|
|
internal Entity CreateEntity(Entity handle)
|
|
|
|
|
{
|
|
|
|
|
_allocator.Reserve(handle);
|
|
|
|
|
return handle;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-18 19:03:37 +08:00
|
|
|
/// <summary>
|
|
|
|
|
/// Destroys an entity, removing all its components and recycling its ID.
|
2026-07-18 19:42:59 +08:00
|
|
|
///
|
|
|
|
|
/// Cascade behavior:
|
|
|
|
|
/// - All relationships where this entity is the <b>source</b> are removed.
|
|
|
|
|
/// - All relationships where this entity is the <b>target</b> are removed
|
|
|
|
|
/// from the source entities.
|
2026-07-18 19:03:37 +08:00
|
|
|
/// </summary>
|
|
|
|
|
public void DestroyEntity(Entity entity)
|
2026-07-18 21:22:23 +08:00
|
|
|
{
|
|
|
|
|
if (_iterationDepth > 0)
|
|
|
|
|
{
|
|
|
|
|
_pendingMutations.Add(new PendingMutation
|
|
|
|
|
{
|
|
|
|
|
Kind = PendingMutationKind.DestroyEntity,
|
|
|
|
|
Entity = entity
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DestroyEntityImpl(entity);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void DestroyEntityImpl(Entity entity)
|
2026-07-18 19:03:37 +08:00
|
|
|
{
|
|
|
|
|
if (!_allocator.IsAlive(entity))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
// Singleton entity is never destroyed.
|
|
|
|
|
if (entity.Id == 1)
|
|
|
|
|
return;
|
|
|
|
|
|
2026-07-18 19:42:59 +08:00
|
|
|
// Remove all relationships where this entity is the target.
|
2026-07-18 19:58:23 +08:00
|
|
|
// Materialize to avoid collection-modified-during-enumeration when
|
|
|
|
|
// OnRemoved mutates the same HashSet we're iterating.
|
|
|
|
|
var incoming = _relationships.GetIncomingRelationships(entity)
|
|
|
|
|
.Select(r => (r.RelationshipType, r.Sources.ToList()))
|
|
|
|
|
.ToList();
|
|
|
|
|
foreach (var (relType, sources) in incoming)
|
2026-07-18 19:42:59 +08:00
|
|
|
{
|
|
|
|
|
foreach (var source in sources)
|
|
|
|
|
{
|
|
|
|
|
_relationships.OnRemoved(relType, source, entity);
|
|
|
|
|
_components.Remove(source, relType);
|
2026-07-18 20:13:25 +08:00
|
|
|
_changes.Pending.MarkComponentRemoved(source, relType);
|
2026-07-18 19:42:59 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Remove all relationships where this entity is the source.
|
|
|
|
|
_relationships.RemoveAllSourcesForEntity(entity);
|
|
|
|
|
|
2026-07-18 19:58:23 +08:00
|
|
|
// Mark component removals for each component type before removing them.
|
|
|
|
|
foreach (var componentType in _components.ComponentTypes.ToList())
|
|
|
|
|
{
|
|
|
|
|
if (_components.GetSet(componentType)?.Contains(entity) == true)
|
|
|
|
|
{
|
|
|
|
|
_changes.Pending.MarkComponentRemoved(entity, componentType);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-18 19:03:37 +08:00
|
|
|
_components.RemoveAll(entity);
|
|
|
|
|
_allocator.Free(entity);
|
2026-07-18 19:50:31 +08:00
|
|
|
|
|
|
|
|
_changes.Pending.MarkEntityRemoved(entity);
|
2026-07-18 19:03:37 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Returns true if the entity is currently alive (not destroyed).
|
|
|
|
|
/// </summary>
|
|
|
|
|
public bool IsAlive(Entity entity)
|
|
|
|
|
{
|
|
|
|
|
return _allocator.IsAlive(entity);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Component Management ─────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Adds a component to the entity. Replaces the existing component if
|
|
|
|
|
/// the entity already has one of type <typeparamref name="T"/>.
|
2026-07-18 19:42:59 +08:00
|
|
|
///
|
|
|
|
|
/// If <typeparamref name="T"/> implements <see cref="IRelationship"/>,
|
|
|
|
|
/// the reverse index is updated automatically.
|
2026-07-18 19:03:37 +08:00
|
|
|
/// </summary>
|
|
|
|
|
public void AddComponent<T>(Entity entity, T component) where T : struct
|
2026-07-18 21:22:23 +08:00
|
|
|
{
|
|
|
|
|
if (_iterationDepth > 0)
|
|
|
|
|
{
|
|
|
|
|
_pendingMutations.Add(new PendingMutation
|
|
|
|
|
{
|
|
|
|
|
Kind = PendingMutationKind.AddComponent,
|
|
|
|
|
Entity = entity,
|
|
|
|
|
Component = component,
|
|
|
|
|
ComponentType = typeof(T)
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
AddComponentImpl(entity, component);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void AddComponentImpl<T>(Entity entity, T component) where T : struct
|
2026-07-18 19:03:37 +08:00
|
|
|
{
|
|
|
|
|
ThrowIfNotAlive(entity);
|
2026-07-18 19:42:59 +08:00
|
|
|
|
|
|
|
|
// If replacing an existing relationship, remove the old index entry first.
|
|
|
|
|
if (component is IRelationship newRel)
|
|
|
|
|
{
|
|
|
|
|
if (_components.TryGet<T>(entity, out var old))
|
|
|
|
|
{
|
|
|
|
|
var oldRel = (IRelationship)(object)old;
|
|
|
|
|
_relationships.OnRemoved(typeof(T), entity, oldRel.Target);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-18 19:50:31 +08:00
|
|
|
bool isNew = !_components.Has<T>(entity);
|
2026-07-18 19:03:37 +08:00
|
|
|
_components.Add(entity, component);
|
2026-07-18 19:42:59 +08:00
|
|
|
|
|
|
|
|
// Update relationship index.
|
|
|
|
|
if (component is IRelationship rel)
|
|
|
|
|
{
|
|
|
|
|
_relationships.OnAdded(typeof(T), entity, rel.Target);
|
|
|
|
|
}
|
2026-07-18 19:50:31 +08:00
|
|
|
|
|
|
|
|
// Mark change.
|
|
|
|
|
if (isNew)
|
|
|
|
|
_changes.Pending.MarkComponentAdded(entity, typeof(T));
|
2026-07-18 19:03:37 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Removes the component of type <typeparamref name="T"/> from the entity.
|
|
|
|
|
/// No-op if the entity does not have the component.
|
2026-07-18 19:42:59 +08:00
|
|
|
///
|
|
|
|
|
/// If <typeparamref name="T"/> implements <see cref="IRelationship"/>,
|
|
|
|
|
/// the reverse index is updated automatically.
|
2026-07-18 19:03:37 +08:00
|
|
|
/// </summary>
|
|
|
|
|
public void RemoveComponent<T>(Entity entity) where T : struct
|
2026-07-18 21:22:23 +08:00
|
|
|
{
|
|
|
|
|
if (_iterationDepth > 0)
|
|
|
|
|
{
|
|
|
|
|
_pendingMutations.Add(new PendingMutation
|
|
|
|
|
{
|
|
|
|
|
Kind = PendingMutationKind.RemoveComponent,
|
|
|
|
|
Entity = entity,
|
|
|
|
|
ComponentType = typeof(T)
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RemoveComponentImpl<T>(entity);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void RemoveComponentImpl<T>(Entity entity) where T : struct
|
2026-07-18 19:03:37 +08:00
|
|
|
{
|
2026-07-18 19:42:59 +08:00
|
|
|
// Update relationship index before removal.
|
|
|
|
|
if (typeof(IRelationship).IsAssignableFrom(typeof(T)))
|
|
|
|
|
{
|
|
|
|
|
if (_components.TryGet<T>(entity, out var existing))
|
|
|
|
|
{
|
|
|
|
|
var rel = (IRelationship)(object)existing;
|
|
|
|
|
_relationships.OnRemoved(typeof(T), entity, rel.Target);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-18 19:50:31 +08:00
|
|
|
bool existed = _components.Has<T>(entity);
|
2026-07-18 19:03:37 +08:00
|
|
|
_components.Remove<T>(entity);
|
2026-07-18 19:50:31 +08:00
|
|
|
|
|
|
|
|
if (existed)
|
|
|
|
|
_changes.Pending.MarkComponentRemoved(entity, typeof(T));
|
2026-07-18 19:03:37 +08:00
|
|
|
}
|
|
|
|
|
|
2026-07-18 21:24:03 +08:00
|
|
|
/// <summary>
|
2026-07-18 19:03:37 +08:00
|
|
|
/// <summary>
|
|
|
|
|
/// Returns a reference to the component of type <typeparamref name="T"/>
|
|
|
|
|
/// for the given entity. Throws if the entity does not have the component.
|
2026-07-18 19:50:31 +08:00
|
|
|
///
|
2026-07-18 21:24:03 +08:00
|
|
|
/// During iteration, this access is auto-marked as modified since it
|
|
|
|
|
/// returns a mutable ref. Use <see cref="ReadComponent{T}"/> if you
|
|
|
|
|
/// only need to read the value.
|
2026-07-18 19:03:37 +08:00
|
|
|
/// </summary>
|
|
|
|
|
public ref T GetComponent<T>(Entity entity) where T : struct
|
|
|
|
|
{
|
2026-07-18 21:22:23 +08:00
|
|
|
if (_iterationDepth > 0)
|
|
|
|
|
_accessedComponents.Add((entity, typeof(T)));
|
2026-07-18 19:03:37 +08:00
|
|
|
return ref _components.Get<T>(entity);
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-18 21:24:03 +08:00
|
|
|
/// <summary>
|
|
|
|
|
/// Returns a copy of the component of type <typeparamref name="T"/>
|
|
|
|
|
/// for the given entity. Never auto-marks as modified — use this when
|
|
|
|
|
/// you only need to read the value.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public T ReadComponent<T>(Entity entity) where T : struct
|
|
|
|
|
{
|
|
|
|
|
return _components.Get<T>(entity);
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-18 20:07:43 +08:00
|
|
|
/// <summary>
|
|
|
|
|
/// Tries to get the component of type <typeparamref name="T"/> for the
|
|
|
|
|
/// given entity. Returns true and copies the value to
|
|
|
|
|
/// <paramref name="value"/> if the component exists; otherwise returns false.
|
2026-07-18 21:24:03 +08:00
|
|
|
/// Never auto-marks as modified.
|
2026-07-18 20:07:43 +08:00
|
|
|
/// </summary>
|
|
|
|
|
public bool TryGetComponent<T>(Entity entity, out T value) where T : struct
|
|
|
|
|
{
|
|
|
|
|
return _components.TryGet<T>(entity, out value);
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-18 19:03:37 +08:00
|
|
|
/// <summary>
|
|
|
|
|
/// Returns true if the entity has a component of type <typeparamref name="T"/>.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public bool HasComponent<T>(Entity entity) where T : struct
|
|
|
|
|
{
|
|
|
|
|
return _components.Has<T>(entity);
|
|
|
|
|
}
|
2026-07-18 19:33:05 +08:00
|
|
|
|
2026-07-18 19:50:31 +08:00
|
|
|
// ── Change Tracking ──────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Marks a component of type <typeparamref name="T"/> on the given entity
|
2026-07-18 21:22:23 +08:00
|
|
|
/// as modified. Only needed outside of iteration scopes — during
|
|
|
|
|
/// <see cref="ForEach"/> or a <c>Select</c> loop, components accessed
|
|
|
|
|
/// via <see cref="GetComponent{T}"/> are auto-marked.
|
2026-07-18 19:50:31 +08:00
|
|
|
/// </summary>
|
|
|
|
|
public void MarkModified<T>(Entity entity) where T : struct
|
|
|
|
|
{
|
|
|
|
|
_changes.Pending.MarkComponentModified(entity, typeof(T));
|
|
|
|
|
_markedModified.Add((entity, typeof(T)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Posts all pending changes to R3 subscribers, then clears the pending set.
|
|
|
|
|
/// Called automatically by <see cref="SystemGroup"/> after each system
|
|
|
|
|
/// and after the full tick.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public void PostChanges()
|
|
|
|
|
{
|
|
|
|
|
_changes.Post();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Returns an observable that emits all entity changes (adds, removes,
|
|
|
|
|
/// component adds, component removes, component modifications).
|
|
|
|
|
/// </summary>
|
|
|
|
|
public Observable<EntityChange> ObserveEntityChanges()
|
|
|
|
|
{
|
|
|
|
|
return _changes.ObserveEntityChanges();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Returns an observable that emits changes for a specific component type
|
|
|
|
|
/// <typeparamref name="T"/> (added, removed, or modified).
|
|
|
|
|
/// </summary>
|
|
|
|
|
public Observable<EntityChange> ObserveComponentChanges<T>() where T : struct
|
|
|
|
|
{
|
|
|
|
|
return _changes.ObserveComponentChanges(typeof(T));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Returns an observable that emits changes matching the given query.
|
|
|
|
|
/// Only component-level changes whose component type is in the query's
|
|
|
|
|
/// "with" set and not in the "without" set are emitted.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public Observable<EntityChange> ObserveQuery(QueryDescriptor query)
|
|
|
|
|
{
|
|
|
|
|
return _changes.ObserveQuery(query);
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-18 19:53:58 +08:00
|
|
|
// ── Singletons ───────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Sets (adds or replaces) a singleton component of type <typeparamref name="T"/>.
|
|
|
|
|
/// Singletons are stored on a reserved entity that is never destroyed
|
|
|
|
|
/// and excluded from normal queries.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public void SetSingleton<T>(T component) where T : struct
|
|
|
|
|
{
|
|
|
|
|
EnsureSingleton();
|
|
|
|
|
AddComponent(SingletonEntity, component);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Returns a reference to the singleton component of type <typeparamref name="T"/>.
|
2026-07-18 21:24:03 +08:00
|
|
|
/// Throws if the singleton has not been set. Auto-marks as modified during iteration.
|
2026-07-18 19:53:58 +08:00
|
|
|
/// </summary>
|
|
|
|
|
public ref T GetSingleton<T>() where T : struct
|
|
|
|
|
{
|
|
|
|
|
EnsureSingleton();
|
|
|
|
|
return ref GetComponent<T>(SingletonEntity);
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-18 21:24:03 +08:00
|
|
|
/// <summary>
|
|
|
|
|
/// Returns a copy of the singleton component of type <typeparamref name="T"/>.
|
|
|
|
|
/// Never auto-marks as modified — use this when you only need to read the singleton.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public T ReadSingleton<T>() where T : struct
|
|
|
|
|
{
|
|
|
|
|
EnsureSingleton();
|
|
|
|
|
return ReadComponent<T>(SingletonEntity);
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-18 19:53:58 +08:00
|
|
|
/// <summary>
|
|
|
|
|
/// Returns true if a singleton component of type <typeparamref name="T"/> exists.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public bool HasSingleton<T>() where T : struct
|
|
|
|
|
{
|
|
|
|
|
return HasComponent<T>(SingletonEntity);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Removes the singleton component of type <typeparamref name="T"/>.
|
|
|
|
|
/// No-op if the singleton does not have the component.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public void RemoveSingleton<T>() where T : struct
|
|
|
|
|
{
|
|
|
|
|
RemoveComponent<T>(SingletonEntity);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void EnsureSingleton()
|
|
|
|
|
{
|
|
|
|
|
if (_singletonCreated)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
_singletonCreated = true;
|
|
|
|
|
|
|
|
|
|
// Manually register the singleton entity in the allocator so
|
|
|
|
|
// IsAlive returns true for it. We bypass Allocate() because
|
|
|
|
|
// the singleton has a fixed ID and version.
|
|
|
|
|
_allocator.RegisterSingleton(SingletonEntity);
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-18 19:42:59 +08:00
|
|
|
// ── Relationships ────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Returns all source entities that have a relationship of type
|
|
|
|
|
/// <typeparamref name="T"/> pointing to the given target entity.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public IReadOnlyCollection<Entity> GetSources<T>(Entity target)
|
|
|
|
|
where T : struct, IRelationship
|
|
|
|
|
{
|
|
|
|
|
return _relationships.GetSources<T>(target);
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-18 19:38:10 +08:00
|
|
|
// ── Commands ──────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// The command queue for deferred, serializable commands.
|
|
|
|
|
/// Systems enqueue commands via <c>world.Commands.Enqueue(...)</c>.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public CommandQueue Commands => _commands;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Manually drains the command queue, executing all pending commands.
|
|
|
|
|
/// Called automatically by <see cref="SystemGroup"/> after each system
|
|
|
|
|
/// and after the full tick.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public void ExecuteCommands()
|
|
|
|
|
{
|
|
|
|
|
_commands.ExecuteAll(this);
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-18 19:33:05 +08:00
|
|
|
// ── Queries ──────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Returns a new <see cref="QueryBuilder"/> for constructing queries.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public QueryBuilder Query() => new();
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Iterates all entities matching the query, providing ref access to
|
2026-07-18 21:22:23 +08:00
|
|
|
/// one component type. Structural mutations (AddComponent, RemoveComponent,
|
|
|
|
|
/// DestroyEntity) made during iteration are deferred and applied after
|
|
|
|
|
/// the iteration completes.
|
2026-07-18 19:33:05 +08:00
|
|
|
/// </summary>
|
|
|
|
|
public void ForEach<T1>(
|
|
|
|
|
QueryDescriptor query,
|
|
|
|
|
ForEachAction<T1> action)
|
|
|
|
|
where T1 : struct
|
|
|
|
|
{
|
2026-07-18 20:07:43 +08:00
|
|
|
ValidateQueryTypes(query, typeof(T1));
|
2026-07-18 21:22:23 +08:00
|
|
|
BeginIteration();
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
QueryExecutor.ForEach(_components, query, action);
|
|
|
|
|
}
|
|
|
|
|
finally
|
|
|
|
|
{
|
|
|
|
|
EndIteration();
|
|
|
|
|
}
|
2026-07-18 19:33:05 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Iterates all entities matching the query, providing ref access to
|
2026-07-18 21:22:23 +08:00
|
|
|
/// two component types. Structural mutations are deferred.
|
2026-07-18 19:33:05 +08:00
|
|
|
/// </summary>
|
|
|
|
|
public void ForEach<T1, T2>(
|
|
|
|
|
QueryDescriptor query,
|
|
|
|
|
ForEachAction<T1, T2> action)
|
|
|
|
|
where T1 : struct where T2 : struct
|
|
|
|
|
{
|
2026-07-18 20:07:43 +08:00
|
|
|
ValidateQueryTypes(query, typeof(T1), typeof(T2));
|
2026-07-18 21:22:23 +08:00
|
|
|
BeginIteration();
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
QueryExecutor.ForEach(_components, query, action);
|
|
|
|
|
}
|
|
|
|
|
finally
|
|
|
|
|
{
|
|
|
|
|
EndIteration();
|
|
|
|
|
}
|
2026-07-18 19:33:05 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Iterates all entities matching the query, providing ref access to
|
2026-07-18 21:22:23 +08:00
|
|
|
/// three component types. Structural mutations are deferred.
|
2026-07-18 19:33:05 +08:00
|
|
|
/// </summary>
|
|
|
|
|
public void ForEach<T1, T2, T3>(
|
|
|
|
|
QueryDescriptor query,
|
|
|
|
|
ForEachAction<T1, T2, T3> action)
|
|
|
|
|
where T1 : struct where T2 : struct where T3 : struct
|
|
|
|
|
{
|
2026-07-18 20:07:43 +08:00
|
|
|
ValidateQueryTypes(query, typeof(T1), typeof(T2), typeof(T3));
|
2026-07-18 21:22:23 +08:00
|
|
|
BeginIteration();
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
QueryExecutor.ForEach(_components, query, action);
|
|
|
|
|
}
|
|
|
|
|
finally
|
|
|
|
|
{
|
|
|
|
|
EndIteration();
|
|
|
|
|
}
|
2026-07-18 19:33:05 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Iterates all entities matching the query, providing ref access to
|
2026-07-18 21:22:23 +08:00
|
|
|
/// four component types. Structural mutations are deferred.
|
2026-07-18 19:33:05 +08:00
|
|
|
/// </summary>
|
|
|
|
|
public void ForEach<T1, T2, T3, T4>(
|
|
|
|
|
QueryDescriptor query,
|
|
|
|
|
ForEachAction<T1, T2, T3, T4> action)
|
|
|
|
|
where T1 : struct where T2 : struct where T3 : struct where T4 : struct
|
|
|
|
|
{
|
2026-07-18 20:07:43 +08:00
|
|
|
ValidateQueryTypes(query, typeof(T1), typeof(T2), typeof(T3), typeof(T4));
|
2026-07-18 21:22:23 +08:00
|
|
|
BeginIteration();
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
QueryExecutor.ForEach(_components, query, action);
|
|
|
|
|
}
|
|
|
|
|
finally
|
|
|
|
|
{
|
|
|
|
|
EndIteration();
|
|
|
|
|
}
|
2026-07-18 19:33:05 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Iterates all entities matching the query, providing ref access to
|
2026-07-18 21:22:23 +08:00
|
|
|
/// five component types. Structural mutations are deferred.
|
2026-07-18 19:33:05 +08:00
|
|
|
/// </summary>
|
|
|
|
|
public void ForEach<T1, T2, T3, T4, T5>(
|
|
|
|
|
QueryDescriptor query,
|
|
|
|
|
ForEachAction<T1, T2, T3, T4, T5> action)
|
|
|
|
|
where T1 : struct where T2 : struct where T3 : struct where T4 : struct where T5 : struct
|
|
|
|
|
{
|
2026-07-18 20:07:43 +08:00
|
|
|
ValidateQueryTypes(query, typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5));
|
2026-07-18 21:22:23 +08:00
|
|
|
BeginIteration();
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
QueryExecutor.ForEach(_components, query, action);
|
|
|
|
|
}
|
|
|
|
|
finally
|
|
|
|
|
{
|
|
|
|
|
EndIteration();
|
|
|
|
|
}
|
2026-07-18 19:33:05 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Iterates all entities matching the query, providing ref access to
|
2026-07-18 21:22:23 +08:00
|
|
|
/// six component types. Structural mutations are deferred.
|
2026-07-18 19:33:05 +08:00
|
|
|
/// </summary>
|
|
|
|
|
public void ForEach<T1, T2, T3, T4, T5, T6>(
|
|
|
|
|
QueryDescriptor query,
|
|
|
|
|
ForEachAction<T1, T2, T3, T4, T5, T6> action)
|
|
|
|
|
where T1 : struct where T2 : struct where T3 : struct where T4 : struct where T5 : struct where T6 : struct
|
|
|
|
|
{
|
2026-07-18 20:07:43 +08:00
|
|
|
ValidateQueryTypes(query, typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6));
|
2026-07-18 21:22:23 +08:00
|
|
|
BeginIteration();
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
QueryExecutor.ForEach(_components, query, action);
|
|
|
|
|
}
|
|
|
|
|
finally
|
|
|
|
|
{
|
|
|
|
|
EndIteration();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Begins an iteration scope. Structural mutations are buffered
|
|
|
|
|
/// until <see cref="EndIteration"/> is called.
|
|
|
|
|
/// </summary>
|
|
|
|
|
internal void BeginIteration()
|
|
|
|
|
{
|
|
|
|
|
_iterationDepth++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Ends an iteration scope. When the outermost scope ends, all
|
|
|
|
|
/// buffered structural mutations are applied and auto-tracked
|
|
|
|
|
/// component modifications are posted.
|
|
|
|
|
/// </summary>
|
|
|
|
|
internal void EndIteration()
|
|
|
|
|
{
|
|
|
|
|
_iterationDepth--;
|
|
|
|
|
if (_iterationDepth == 0)
|
|
|
|
|
{
|
|
|
|
|
// Auto-mark all components accessed via GetComponent<T>
|
|
|
|
|
// during the iteration as modified.
|
|
|
|
|
foreach (var (entity, componentType) in _accessedComponents)
|
|
|
|
|
{
|
|
|
|
|
if (!_markedModified.Contains((entity, componentType)))
|
|
|
|
|
{
|
|
|
|
|
_changes.Pending.MarkComponentModified(entity, componentType);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
_accessedComponents.Clear();
|
|
|
|
|
_markedModified.Clear();
|
|
|
|
|
|
|
|
|
|
FlushPendingMutations();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void FlushPendingMutations()
|
|
|
|
|
{
|
|
|
|
|
if (_pendingMutations.Count == 0)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
foreach (var m in _pendingMutations)
|
|
|
|
|
{
|
|
|
|
|
switch (m.Kind)
|
|
|
|
|
{
|
|
|
|
|
case PendingMutationKind.AddComponent:
|
|
|
|
|
// Use reflection to call AddComponentImpl<T>.
|
|
|
|
|
var addMethod = typeof(World).GetMethod(
|
|
|
|
|
nameof(AddComponentImpl),
|
|
|
|
|
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)!;
|
|
|
|
|
var genericAdd = addMethod.MakeGenericMethod(m.ComponentType);
|
|
|
|
|
genericAdd.Invoke(this, [m.Entity, m.Component]);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case PendingMutationKind.RemoveComponent:
|
|
|
|
|
var removeMethod = typeof(World).GetMethod(
|
|
|
|
|
nameof(RemoveComponentImpl),
|
|
|
|
|
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)!;
|
|
|
|
|
var genericRemove = removeMethod.MakeGenericMethod(m.ComponentType);
|
|
|
|
|
genericRemove.Invoke(this, [m.Entity]);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case PendingMutationKind.DestroyEntity:
|
|
|
|
|
DestroyEntityImpl(m.Entity);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_pendingMutations.Clear();
|
2026-07-18 19:33:05 +08:00
|
|
|
}
|
2026-07-18 19:03:37 +08:00
|
|
|
|
|
|
|
|
// ── Internal Access ───────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// The component store. Exposed internally for query execution and
|
|
|
|
|
/// system infrastructure.
|
|
|
|
|
/// </summary>
|
|
|
|
|
internal ComponentStore Components => _components;
|
|
|
|
|
|
|
|
|
|
// ── Helpers ───────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
private void ThrowIfNotAlive(Entity entity)
|
|
|
|
|
{
|
|
|
|
|
if (!_allocator.IsAlive(entity))
|
|
|
|
|
throw new InvalidOperationException($"Entity {entity} is not alive.");
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-18 20:07:43 +08:00
|
|
|
/// <summary>
|
|
|
|
|
/// Validates that the ForEach type parameters match the query's
|
|
|
|
|
/// With set. When the With set is empty (only Without filters are
|
|
|
|
|
/// specified), any ForEach type parameters are accepted.
|
|
|
|
|
/// </summary>
|
|
|
|
|
private static void ValidateQueryTypes(QueryDescriptor query, params Type[] forEachTypes)
|
|
|
|
|
{
|
|
|
|
|
// When With is empty, the query is using only Without filters.
|
|
|
|
|
// The ForEach type parameters are the sole source of truth.
|
|
|
|
|
if (query.With.Count == 0)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (query.With.Count != forEachTypes.Length)
|
|
|
|
|
ThrowMismatch(query, forEachTypes);
|
|
|
|
|
|
|
|
|
|
foreach (var t in forEachTypes)
|
|
|
|
|
{
|
|
|
|
|
if (!query.With.Contains(t))
|
|
|
|
|
ThrowMismatch(query, forEachTypes);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static void ThrowMismatch(QueryDescriptor query, Type[] forEachTypes)
|
|
|
|
|
{
|
|
|
|
|
var queryTypes = string.Join(", ", query.With.Select(t => t.Name));
|
|
|
|
|
var forEachTypeNames = string.Join(", ", forEachTypes.Select(t => t.Name));
|
|
|
|
|
throw new InvalidOperationException(
|
|
|
|
|
$"ForEach type parameters [{forEachTypeNames}] do not match " +
|
|
|
|
|
$"the query's With types [{queryTypes}]. " +
|
|
|
|
|
$"Ensure the ForEach type parameters exactly match the types " +
|
|
|
|
|
$"passed to QueryBuilder.With<T>().");
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-18 19:03:37 +08:00
|
|
|
// ── Cleanup ───────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
public void Dispose()
|
|
|
|
|
{
|
2026-07-18 20:13:25 +08:00
|
|
|
if (_disposed)
|
|
|
|
|
return;
|
|
|
|
|
_disposed = true;
|
2026-07-18 19:50:31 +08:00
|
|
|
_changes.Dispose();
|
|
|
|
|
}
|
2026-07-18 19:03:37 +08:00
|
|
|
}
|