diff --git a/OECS/OrderedEntitySet.cs b/OECS/OrderedEntitySet.cs
new file mode 100644
index 0000000..9e0d5a8
--- /dev/null
+++ b/OECS/OrderedEntitySet.cs
@@ -0,0 +1,49 @@
+using System.Collections;
+
+namespace OECS;
+
+///
+/// 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.
+///
+internal sealed class OrderedEntitySet : IReadOnlyCollection
+{
+ // 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 _order = new();
+ private readonly Dictionary _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 GetEnumerator() => _order.GetEnumerator();
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+}
\ No newline at end of file
diff --git a/OECS/RelationshipIndex.cs b/OECS/RelationshipIndex.cs
index 1436da4..f5d1572 100644
--- a/OECS/RelationshipIndex.cs
+++ b/OECS/RelationshipIndex.cs
@@ -8,13 +8,16 @@ namespace OECS;
///
/// Updated automatically by when relationship
/// 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.
///
internal class RelationshipIndex
{
///
/// Per relationship type: target entity → set of source entities.
///
- private readonly Dictionary>> _index = new();
+ private readonly Dictionary> _index = new();
///
/// 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))
{
- targetMap = new Dictionary>();
+ targetMap = new Dictionary();
_index[relationshipType] = targetMap;
}
if (!targetMap.TryGetValue(target, out var sources))
{
- sources = new HashSet();
+ sources = new OrderedEntitySet();
targetMap[target] = sources;
}
@@ -55,7 +58,7 @@ internal class RelationshipIndex
///
/// 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.
///
public IReadOnlyCollection GetSources(Entity target)
where T : struct, IRelationship
@@ -123,4 +126,4 @@ internal class RelationshipIndex
{
return _index.ContainsKey(relationshipType);
}
-}
+}
\ No newline at end of file