From 68eeeeb7a6f2b701fa65f81b5c07d68a566bf5bb Mon Sep 17 00:00:00 2001 From: hypercross Date: Tue, 21 Jul 2026 22:46:32 +0800 Subject: [PATCH] feat(oecs): add support for reordering relationship sources Introduces `RelationshipReordered` change kind and `ReorderSources` method to allow reordering the source set of a relationship without removing and re-adding components. This optimizes shuffling operations like deck setup in Blackjack. --- Game.Blackjack/Systems/DeckSetupSystem.cs | 21 ++------------------- OECS.PlayTest/ObservableCapture.cs | 1 + OECS/ChangeKind.cs | 9 ++++++++- OECS/ChangeSet.cs | 9 +++++++++ OECS/OrderedEntitySet.cs | 15 +++++++++++++++ OECS/RelationshipIndex.cs | 17 +++++++++++++++++ OECS/World.cs | 16 ++++++++++++++++ 7 files changed, 68 insertions(+), 20 deletions(-) diff --git a/Game.Blackjack/Systems/DeckSetupSystem.cs b/Game.Blackjack/Systems/DeckSetupSystem.cs index c76a73c..77f026b 100644 --- a/Game.Blackjack/Systems/DeckSetupSystem.cs +++ b/Game.Blackjack/Systems/DeckSetupSystem.cs @@ -60,27 +60,10 @@ public class DeckSetupSystem : ISystem for (int i = cardEntities.Length - 1; i > 0; i--) { int j = Mulberry32.NextInt(ref seed, 0, i); - - // Swap the InDeck relationship targets (cards are always in the deck, - // so there's nothing to swap except the cards themselves — but we - // shuffle the card order conceptually by removing and re-adding - // InDeck components in shuffled order). Actually, since InDeck - // is just a tag, we just re-shuffle the order in the source collection. - // The simplest approach: no need to swap component data; we just - // need to ensure the cards are iterated in shuffled order. - // We'll swap the card entities in the array. (cardEntities[i], cardEntities[j]) = (cardEntities[j], cardEntities[i]); } - // Now remove all InDeck and re-add in shuffled order so GetSources - // returns them in shuffled order. - for (int i = cardEntities.Length - 1; i >= 0; i--) - { - world.RemoveComponent(cardEntities[i]); - } - for (int i = 0; i < cardEntities.Length; i++) - { - world.AddComponent(cardEntities[i], new InDeck { Target = deckEntity }); - } + // Reorder the source set in-place — no component add/remove needed. + world.ReorderSources(deckEntity, cardEntities); } } \ No newline at end of file diff --git a/OECS.PlayTest/ObservableCapture.cs b/OECS.PlayTest/ObservableCapture.cs index 61e341c..3fd21c6 100644 --- a/OECS.PlayTest/ObservableCapture.cs +++ b/OECS.PlayTest/ObservableCapture.cs @@ -74,6 +74,7 @@ public sealed class ObservableCapture : IDisposable if (change.ComponentType != null && change.Kind != ChangeKind.ComponentRemoved && change.Kind != ChangeKind.EntityRemoved + && change.Kind != ChangeKind.RelationshipReordered && _formatters.TryGetValue(change.ComponentType, out var formatter)) { formattedValue = formatter(_world, change.Entity); diff --git a/OECS/ChangeKind.cs b/OECS/ChangeKind.cs index 1b7f9ea..8e5f4b5 100644 --- a/OECS/ChangeKind.cs +++ b/OECS/ChangeKind.cs @@ -29,5 +29,12 @@ public enum ChangeKind /// A component's value was modified. Must be explicitly marked /// via . /// - ComponentModified + ComponentModified, + + /// + /// The source set for a relationship type on a target entity was + /// reordered. The is the target + /// and is the relationship type. + /// + RelationshipReordered } diff --git a/OECS/ChangeSet.cs b/OECS/ChangeSet.cs index a1795bd..c78b653 100644 --- a/OECS/ChangeSet.cs +++ b/OECS/ChangeSet.cs @@ -64,6 +64,15 @@ internal class ChangeSet TryAdd(new EntityChange(entity, ChangeKind.ComponentModified, componentType)); } + /// + /// Records that the source set for a relationship type on a target entity + /// was reordered. + /// + public void MarkRelationshipReordered(Entity target, Type relationshipType) + { + TryAdd(new EntityChange(target, ChangeKind.RelationshipReordered, relationshipType)); + } + /// /// Clears all accumulated changes. /// diff --git a/OECS/OrderedEntitySet.cs b/OECS/OrderedEntitySet.cs index 9e0d5a8..199e91f 100644 --- a/OECS/OrderedEntitySet.cs +++ b/OECS/OrderedEntitySet.cs @@ -44,6 +44,21 @@ internal sealed class OrderedEntitySet : IReadOnlyCollection return true; } + /// + /// Reorders the set to match the given entity order. + /// The provided list must contain exactly the same entities as the set. + /// + public void Reorder(IReadOnlyList ordered) + { + _order.Clear(); + _index.Clear(); + for (int i = 0; i < ordered.Count; i++) + { + _order.Add(ordered[i]); + _index[ordered[i]] = i; + } + } + 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 f5d1572..3644d5f 100644 --- a/OECS/RelationshipIndex.cs +++ b/OECS/RelationshipIndex.cs @@ -119,6 +119,23 @@ internal class RelationshipIndex } } + /// + /// Reorders the source set for the given relationship type and target + /// to match the order of entities in . + /// + public void ReorderSources(Entity target, IReadOnlyList ordered) + where T : struct, IRelationship + { + var type = typeof(T); + if (!_index.TryGetValue(type, out var targetMap)) + return; + + if (!targetMap.TryGetValue(target, out var sources)) + return; + + sources.Reorder(ordered); + } + /// /// Returns true if the index has any entries for the given relationship type. /// diff --git a/OECS/World.cs b/OECS/World.cs index fce3b76..5f5e661 100644 --- a/OECS/World.cs +++ b/OECS/World.cs @@ -449,6 +449,7 @@ public class World : IDisposable /// /// Returns all source entities that have a relationship of type /// pointing to the given target entity. + /// Sources are iterated in insertion order. /// public IReadOnlyCollection GetSources(Entity target) where T : struct, IRelationship @@ -456,6 +457,21 @@ public class World : IDisposable return _relationships.GetSources(target); } + /// + /// Reorders the source set for the given relationship type and target + /// to match the order of entities in . + /// All entities currently in the set must appear exactly once in the + /// provided collection; no entities are added or removed. + /// Fires a change event + /// on the target entity. + /// + public void ReorderSources(Entity target, IReadOnlyList ordered) + where T : struct, IRelationship + { + _relationships.ReorderSources(target, ordered); + _changes.Pending.MarkRelationshipReordered(target, typeof(T)); + } + // ── Commands ────────────────────────────────────────────────────── ///