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.
This commit is contained in:
hypercross 2026-07-21 22:46:32 +08:00
parent b0bf10f286
commit 68eeeeb7a6
7 changed files with 68 additions and 20 deletions

View File

@ -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<InDeck>(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<InDeck>(deckEntity, cardEntities);
}
}

View File

@ -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);

View File

@ -29,5 +29,12 @@ public enum ChangeKind
/// A component's value was modified. Must be explicitly marked
/// via <see cref="World.MarkModified{T}"/>.
/// </summary>
ComponentModified
ComponentModified,
/// <summary>
/// The source set for a relationship type on a target entity was
/// reordered. The <see cref="EntityChange.Entity"/> is the target
/// and <see cref="EntityChange.ComponentType"/> is the relationship type.
/// </summary>
RelationshipReordered
}

View File

@ -64,6 +64,15 @@ internal class ChangeSet
TryAdd(new EntityChange(entity, ChangeKind.ComponentModified, componentType));
}
/// <summary>
/// Records that the source set for a relationship type on a target entity
/// was reordered.
/// </summary>
public void MarkRelationshipReordered(Entity target, Type relationshipType)
{
TryAdd(new EntityChange(target, ChangeKind.RelationshipReordered, relationshipType));
}
/// <summary>
/// Clears all accumulated changes.
/// </summary>

View File

@ -44,6 +44,21 @@ internal sealed class OrderedEntitySet : IReadOnlyCollection<Entity>
return true;
}
/// <summary>
/// Reorders the set to match the given entity order.
/// The provided list must contain exactly the same entities as the set.
/// </summary>
public void Reorder(IReadOnlyList<Entity> ordered)
{
_order.Clear();
_index.Clear();
for (int i = 0; i < ordered.Count; i++)
{
_order.Add(ordered[i]);
_index[ordered[i]] = i;
}
}
public IEnumerator<Entity> GetEnumerator() => _order.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

View File

@ -119,6 +119,23 @@ internal class RelationshipIndex
}
}
/// <summary>
/// Reorders the source set for the given relationship type and target
/// to match the order of entities in <paramref name="ordered"/>.
/// </summary>
public void ReorderSources<T>(Entity target, IReadOnlyList<Entity> 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);
}
/// <summary>
/// Returns true if the index has any entries for the given relationship type.
/// </summary>

View File

@ -449,6 +449,7 @@ public class World : IDisposable
/// <summary>
/// Returns all source entities that have a relationship of type
/// <typeparamref name="T"/> pointing to the given target entity.
/// Sources are iterated in insertion order.
/// </summary>
public IReadOnlyCollection<Entity> GetSources<T>(Entity target)
where T : struct, IRelationship
@ -456,6 +457,21 @@ public class World : IDisposable
return _relationships.GetSources<T>(target);
}
/// <summary>
/// Reorders the source set for the given relationship type and target
/// to match the order of entities in <paramref name="ordered"/>.
/// All entities currently in the set must appear exactly once in the
/// provided collection; no entities are added or removed.
/// Fires a <see cref="ChangeKind.RelationshipReordered"/> change event
/// on the target entity.
/// </summary>
public void ReorderSources<T>(Entity target, IReadOnlyList<Entity> ordered)
where T : struct, IRelationship
{
_relationships.ReorderSources<T>(target, ordered);
_changes.Pending.MarkRelationshipReordered(target, typeof(T));
}
// ── Commands ──────────────────────────────────────────────────────
/// <summary>