feat: use OrderedEntitySet in RelationshipIndex
Replace HashSet with OrderedEntitySet in RelationshipIndex to preserve the insertion order of source entities during iteration.
This commit is contained in:
parent
535c8d948e
commit
b0bf10f286
|
|
@ -0,0 +1,49 @@
|
||||||
|
using System.Collections;
|
||||||
|
|
||||||
|
namespace OECS;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An insertion-ordered set of entities that supports O(1) Add, Remove, and Contains
|
||||||
|
/// while iterating in insertion order. Drop-in for cases where both
|
||||||
|
/// stable ordering and fast membership checks are needed.
|
||||||
|
///
|
||||||
|
/// Not thread-safe.
|
||||||
|
/// </summary>
|
||||||
|
internal sealed class OrderedEntitySet : IReadOnlyCollection<Entity>
|
||||||
|
{
|
||||||
|
// Entities are stored in a packed list; a secondary dictionary maps
|
||||||
|
// each entity to its index for O(1) removal via swap-with-last.
|
||||||
|
private readonly List<Entity> _order = new();
|
||||||
|
private readonly Dictionary<Entity, int> _index = new();
|
||||||
|
|
||||||
|
public int Count => _order.Count;
|
||||||
|
|
||||||
|
public bool Add(Entity entity)
|
||||||
|
{
|
||||||
|
if (_index.ContainsKey(entity))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
_index[entity] = _order.Count;
|
||||||
|
_order.Add(entity);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Remove(Entity entity)
|
||||||
|
{
|
||||||
|
if (!_index.Remove(entity, out var idx))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
int lastIdx = _order.Count - 1;
|
||||||
|
if (idx < lastIdx)
|
||||||
|
{
|
||||||
|
var last = _order[lastIdx];
|
||||||
|
_order[idx] = last;
|
||||||
|
_index[last] = idx;
|
||||||
|
}
|
||||||
|
_order.RemoveAt(lastIdx);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerator<Entity> GetEnumerator() => _order.GetEnumerator();
|
||||||
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||||
|
}
|
||||||
|
|
@ -8,13 +8,16 @@ namespace OECS;
|
||||||
///
|
///
|
||||||
/// Updated automatically by <see cref="World"/> when relationship
|
/// Updated automatically by <see cref="World"/> when relationship
|
||||||
/// components are added, removed, or when entities are destroyed.
|
/// components are added, removed, or when entities are destroyed.
|
||||||
|
///
|
||||||
|
/// Source sets are insertion-ordered — the order in which relationships
|
||||||
|
/// are added is preserved during iteration.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal class RelationshipIndex
|
internal class RelationshipIndex
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Per relationship type: target entity → set of source entities.
|
/// Per relationship type: target entity → set of source entities.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly Dictionary<Type, Dictionary<Entity, HashSet<Entity>>> _index = new();
|
private readonly Dictionary<Type, Dictionary<Entity, OrderedEntitySet>> _index = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Registers that a source entity now has a relationship pointing to a target.
|
/// Registers that a source entity now has a relationship pointing to a target.
|
||||||
|
|
@ -23,13 +26,13 @@ internal class RelationshipIndex
|
||||||
{
|
{
|
||||||
if (!_index.TryGetValue(relationshipType, out var targetMap))
|
if (!_index.TryGetValue(relationshipType, out var targetMap))
|
||||||
{
|
{
|
||||||
targetMap = new Dictionary<Entity, HashSet<Entity>>();
|
targetMap = new Dictionary<Entity, OrderedEntitySet>();
|
||||||
_index[relationshipType] = targetMap;
|
_index[relationshipType] = targetMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!targetMap.TryGetValue(target, out var sources))
|
if (!targetMap.TryGetValue(target, out var sources))
|
||||||
{
|
{
|
||||||
sources = new HashSet<Entity>();
|
sources = new OrderedEntitySet();
|
||||||
targetMap[target] = sources;
|
targetMap[target] = sources;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -55,7 +58,7 @@ internal class RelationshipIndex
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns all source entities that have a relationship of the given type
|
/// Returns all source entities that have a relationship of the given type
|
||||||
/// pointing to the specified target entity.
|
/// pointing to the specified target entity, in insertion order.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IReadOnlyCollection<Entity> GetSources<T>(Entity target)
|
public IReadOnlyCollection<Entity> GetSources<T>(Entity target)
|
||||||
where T : struct, IRelationship
|
where T : struct, IRelationship
|
||||||
|
|
@ -123,4 +126,4 @@ internal class RelationshipIndex
|
||||||
{
|
{
|
||||||
return _index.ContainsKey(relationshipType);
|
return _index.ContainsKey(relationshipType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue