diff --git a/Game.Blackjack.Tests/GameFlowTests.cs b/Game.Blackjack.Tests/GameFlowTests.cs index f004a29..c53e04a 100644 --- a/Game.Blackjack.Tests/GameFlowTests.cs +++ b/Game.Blackjack.Tests/GameFlowTests.cs @@ -243,7 +243,7 @@ public class GameFlowTests private static int CountCardsInHand(World world, Entity handEntity) { - // Holds relationship: Source = card entity, Target = hand entity. + // Holds relationship: the card entity (which Holds points to the hand entity). // GetSources returns all card entities that have a Holds pointing to handEntity. return world.GetSources(handEntity).Count; } diff --git a/Game.Blackjack/Commands/HitCommand.cs b/Game.Blackjack/Commands/HitCommand.cs index 4f1677f..ebbad27 100644 --- a/Game.Blackjack/Commands/HitCommand.cs +++ b/Game.Blackjack/Commands/HitCommand.cs @@ -41,7 +41,7 @@ public struct HitCommand : ICommand // Remove from deck, add to hand. world.RemoveComponent(cardEntity); - world.AddComponent(cardEntity, new Holds { Source = cardEntity, Target = handEntity }); + world.AddComponent(cardEntity, new Holds { Target = handEntity }); } internal static Entity FindEntity(World world, Entity singletonEntity) diff --git a/Game.Blackjack/Commands/NewRoundCommand.cs b/Game.Blackjack/Commands/NewRoundCommand.cs index bd41b2b..fd705e0 100644 --- a/Game.Blackjack/Commands/NewRoundCommand.cs +++ b/Game.Blackjack/Commands/NewRoundCommand.cs @@ -42,7 +42,7 @@ public struct NewRoundCommand : ICommand foreach (var card in cards) { world.RemoveComponent(card); - world.AddComponent(card, new InDeck { Source = card, Target = deckEntity }); + world.AddComponent(card, new InDeck { Target = deckEntity }); } } diff --git a/Game.Blackjack/Components/Holds.cs b/Game.Blackjack/Components/Holds.cs index 5cad7ea..e75ba36 100644 --- a/Game.Blackjack/Components/Holds.cs +++ b/Game.Blackjack/Components/Holds.cs @@ -10,6 +10,5 @@ namespace Game.Blackjack; [MessagePackObject] public record struct Holds : IRelationship { - [Key(0)] public Entity Source { get; set; } - [Key(1)] public Entity Target { get; set; } + [Key(0)] public Entity Target { get; set; } } \ No newline at end of file diff --git a/Game.Blackjack/Components/InDeck.cs b/Game.Blackjack/Components/InDeck.cs index 6ea75be..0e47596 100644 --- a/Game.Blackjack/Components/InDeck.cs +++ b/Game.Blackjack/Components/InDeck.cs @@ -10,6 +10,5 @@ namespace Game.Blackjack; [MessagePackObject] public record struct InDeck : IRelationship { - [Key(0)] public Entity Source { get; set; } - [Key(1)] public Entity Target { get; set; } + [Key(0)] public Entity Target { get; set; } } \ No newline at end of file diff --git a/Game.Blackjack/Systems/DeckSetupSystem.cs b/Game.Blackjack/Systems/DeckSetupSystem.cs index 667a594..27229d3 100644 --- a/Game.Blackjack/Systems/DeckSetupSystem.cs +++ b/Game.Blackjack/Systems/DeckSetupSystem.cs @@ -36,7 +36,7 @@ public class DeckSetupSystem : ISystem { var cardEntity = world.CreateEntity(); world.AddComponent(cardEntity, new Card { Suit = suit, Rank = rank }); - world.AddComponent(cardEntity, new InDeck { Source = cardEntity, Target = deckEntity }); + world.AddComponent(cardEntity, new InDeck { Target = deckEntity }); } } @@ -81,7 +81,7 @@ public class DeckSetupSystem : ISystem } for (int i = 0; i < cardEntities.Length; i++) { - world.AddComponent(cardEntities[i], new InDeck { Source = cardEntities[i], Target = deckEntity }); + world.AddComponent(cardEntities[i], new InDeck { Target = deckEntity }); } } } \ No newline at end of file diff --git a/Game.CardWars/CardEffects.cs b/Game.CardWars/CardEffects.cs index 38f8442..fd8cb1d 100644 --- a/Game.CardWars/CardEffects.cs +++ b/Game.CardWars/CardEffects.cs @@ -206,7 +206,7 @@ public class NunEffect : ICardEffect world.RemoveComponent(target); if (world.HasComponent(target)) world.RemoveComponent(target); - world.AddComponent(target, new InDeck { Source = target, Target = discardDeck }); + world.AddComponent(target, new InDeck { Target = discardDeck }); } world.Commands.Enqueue(new DrawCardCommand { Player = pending.Player, Kind = null }); world.RemoveSingleton(); @@ -293,7 +293,7 @@ public class PegasusEffect : ICardEffect world.RemoveComponent(target); if (world.HasComponent(target)) world.RemoveComponent(target); - world.AddComponent(target, new HeldBy { Source = target, Target = pending.Player }); + world.AddComponent(target, new HeldBy { Target = pending.Player }); } world.RemoveSingleton(); return true; @@ -321,7 +321,7 @@ public class CurseMasterEffect : ICardEffect { var skull = world.CreateEntity(); world.AddComponent(skull, new Skull()); - world.AddComponent(skull, new PlacedOn { Source = skull, Target = pt }); + world.AddComponent(skull, new PlacedOn { Target = pt }); } } world.RemoveSingleton(); @@ -366,14 +366,14 @@ public static class CardEffectHelpers { var horn = world.CreateEntity(); world.AddComponent(horn, new Horn()); - world.AddComponent(horn, new PlacedOn { Source = horn, Target = target }); + world.AddComponent(horn, new PlacedOn { Target = target }); } public static void AddSkull(World world, Entity target) { var skull = world.CreateEntity(); world.AddComponent(skull, new Skull()); - world.AddComponent(skull, new PlacedOn { Source = skull, Target = target }); + world.AddComponent(skull, new PlacedOn { Target = target }); } public static Entity GetBanner(World world, Entity player) diff --git a/Game.CardWars/Commands/PlayCardCommand.cs b/Game.CardWars/Commands/PlayCardCommand.cs index ac62054..07e19a8 100644 --- a/Game.CardWars/Commands/PlayCardCommand.cs +++ b/Game.CardWars/Commands/PlayCardCommand.cs @@ -38,7 +38,7 @@ public struct PlayCardCommand : ICommand // Move from hand to field. world.RemoveComponent(CardEntity); - world.AddComponent(CardEntity, new OnField { Source = CardEntity, Target = fieldOwner }); + world.AddComponent(CardEntity, new OnField { Target = fieldOwner }); world.AddComponent(CardEntity, new Card { Rank = Rank, FaceDown = FaceDown }); // Resolve on-play effects via registry. diff --git a/Game.CardWars/Components/HeldBy.cs b/Game.CardWars/Components/HeldBy.cs index a65f787..a222824 100644 --- a/Game.CardWars/Components/HeldBy.cs +++ b/Game.CardWars/Components/HeldBy.cs @@ -10,6 +10,5 @@ namespace Game.CardWars; [MessagePackObject] public record struct HeldBy : IRelationship { - [Key(0)] public Entity Source { get; set; } - [Key(1)] public Entity Target { get; set; } + [Key(0)] public Entity Target { get; set; } } \ No newline at end of file diff --git a/Game.CardWars/Components/InDeck.cs b/Game.CardWars/Components/InDeck.cs index d389485..6faf0fe 100644 --- a/Game.CardWars/Components/InDeck.cs +++ b/Game.CardWars/Components/InDeck.cs @@ -9,6 +9,5 @@ namespace Game.CardWars; [MessagePackObject] public record struct InDeck : IRelationship { - [Key(0)] public Entity Source { get; set; } - [Key(1)] public Entity Target { get; set; } + [Key(0)] public Entity Target { get; set; } } \ No newline at end of file diff --git a/Game.CardWars/Components/OnField.cs b/Game.CardWars/Components/OnField.cs index 9f305fc..56daae7 100644 --- a/Game.CardWars/Components/OnField.cs +++ b/Game.CardWars/Components/OnField.cs @@ -9,6 +9,5 @@ namespace Game.CardWars; [MessagePackObject] public record struct OnField : IRelationship { - [Key(0)] public Entity Source { get; set; } - [Key(1)] public Entity Target { get; set; } + [Key(0)] public Entity Target { get; set; } } \ No newline at end of file diff --git a/Game.CardWars/Components/PlacedOn.cs b/Game.CardWars/Components/PlacedOn.cs index b40dc4f..73a246c 100644 --- a/Game.CardWars/Components/PlacedOn.cs +++ b/Game.CardWars/Components/PlacedOn.cs @@ -9,6 +9,5 @@ namespace Game.CardWars; [MessagePackObject] public record struct PlacedOn : IRelationship { - [Key(0)] public Entity Source { get; set; } - [Key(1)] public Entity Target { get; set; } + [Key(0)] public Entity Target { get; set; } } \ No newline at end of file diff --git a/Game.CardWars/GameUtil.cs b/Game.CardWars/GameUtil.cs index 83d2055..f9eb408 100644 --- a/Game.CardWars/GameUtil.cs +++ b/Game.CardWars/GameUtil.cs @@ -38,7 +38,7 @@ public static class GameUtil var cardEntity = cards.First(); world.RemoveComponent(cardEntity); - world.AddComponent(cardEntity, new HeldBy { Source = cardEntity, Target = player }); + world.AddComponent(cardEntity, new HeldBy { Target = player }); return true; } @@ -54,7 +54,7 @@ public static class GameUtil for (int i = cards.Length - 1; i >= 0; i--) world.RemoveComponent(cards[i]); for (int i = 0; i < cards.Length; i++) - world.AddComponent(cards[i], new InDeck { Source = cards[i], Target = deck }); + world.AddComponent(cards[i], new InDeck { Target = deck }); } /// Get all field cards for a player. @@ -135,7 +135,7 @@ public static class GameUtil { world.RemoveComponent(card); world.RemoveComponent(card); - world.AddComponent(card, new InDeck { Source = card, Target = discardDeck }); + world.AddComponent(card, new InDeck { Target = discardDeck }); } // Destroy horns and skulls placed on discarded cards. @@ -156,7 +156,7 @@ public static class GameUtil foreach (var card in cards) { world.RemoveComponent(card); - world.AddComponent(card, new InDeck { Source = card, Target = drawDeck }); + world.AddComponent(card, new InDeck { Target = drawDeck }); } } } \ No newline at end of file diff --git a/Game.CardWars/Systems/GameSetupSystem.cs b/Game.CardWars/Systems/GameSetupSystem.cs index efcacdc..76d5fba 100644 --- a/Game.CardWars/Systems/GameSetupSystem.cs +++ b/Game.CardWars/Systems/GameSetupSystem.cs @@ -57,7 +57,7 @@ public class GameSetupSystem : ISystem var defs = CardDataLoader.LoadDefinitions(world, _cardDataCsv); foreach (var (defEntity, _) in defs) { - world.AddComponent(defEntity, new InDeck { Source = defEntity, Target = publicDeck }); + world.AddComponent(defEntity, new InDeck { Target = publicDeck }); } // Shuffle public deck. @@ -79,12 +79,12 @@ public class GameSetupSystem : ISystem world.AddComponent(leader, new Leader()); world.AddComponent(leader, new CardDef { Name = $"领袖{i}", Ranks = new[] { 0 }, Effect = CardEffect.None, Kind = CardKind.Faction }); world.AddComponent(leader, new Card { Rank = 0, FaceDown = false }); - world.AddComponent(leader, new OnField { Source = leader, Target = player }); + world.AddComponent(leader, new OnField { Target = player }); // Create banner. var banner = world.CreateEntity(); world.AddComponent(banner, new Banner()); - world.AddComponent(banner, new OnField { Source = banner, Target = player }); + world.AddComponent(banner, new OnField { Target = player }); // Create faction deck. var factionDeck = world.CreateEntity(); diff --git a/Game.CardWars/Systems/ScoringSystem.cs b/Game.CardWars/Systems/ScoringSystem.cs index c4fe2ce..f49445c 100644 --- a/Game.CardWars/Systems/ScoringSystem.cs +++ b/Game.CardWars/Systems/ScoringSystem.cs @@ -42,7 +42,7 @@ public class ScoringSystem : ISystem if (castles.Count > 0) { var castle = castles[0]; - world.AddComponent(castle, new HeldBy { Source = castle, Target = winner }); + world.AddComponent(castle, new HeldBy { Target = winner }); world.RemoveComponent(castle); } diff --git a/OECS.Tests/EdgeCaseTests.cs b/OECS.Tests/EdgeCaseTests.cs index aee1a72..872ed61 100644 --- a/OECS.Tests/EdgeCaseTests.cs +++ b/OECS.Tests/EdgeCaseTests.cs @@ -111,7 +111,6 @@ public class EdgeCaseTests sources.Add(source); world.AddComponent(source, new Relationship { - Source = source, Target = target }); } diff --git a/OECS.Tests/RelationshipTests.cs b/OECS.Tests/RelationshipTests.cs index 31764c7..70051dc 100644 --- a/OECS.Tests/RelationshipTests.cs +++ b/OECS.Tests/RelationshipTests.cs @@ -24,7 +24,6 @@ public class RelationshipTests world.AddComponent(child, new Relationship { - Source = child, Target = parent }); @@ -41,7 +40,6 @@ public class RelationshipTests world.AddComponent(child, new Relationship { - Source = child, Target = parent }); @@ -61,14 +59,12 @@ public class RelationshipTests world.AddComponent(child, new Relationship { - Source = child, Target = parent1 }); // Replace with a new target. world.AddComponent(child, new Relationship { - Source = child, Target = parent2 }); @@ -85,7 +81,6 @@ public class RelationshipTests world.AddComponent(child, new Relationship { - Source = child, Target = parent }); @@ -107,7 +102,6 @@ public class RelationshipTests world.AddComponent(child, new Relationship { - Source = child, Target = parent }); @@ -131,15 +125,15 @@ public class RelationshipTests world.AddComponent(child1, new Relationship { - Source = child1, Target = parent + Target = parent }); world.AddComponent(child2, new Relationship { - Source = child2, Target = parent + Target = parent }); world.AddComponent(child3, new Relationship { - Source = child3, Target = parent + Target = parent }); var sources = world.GetSources>(parent); @@ -156,11 +150,11 @@ public class RelationshipTests world.AddComponent(entity, new Relationship { - Source = entity, Target = owner + Target = owner }); world.AddComponent(entity, new Relationship { - Source = entity, Target = group + Target = group }); world.GetSources>(owner).Should().BeEquivalentTo([entity]); @@ -189,7 +183,7 @@ public class RelationshipTests world.AddComponent(child, new Relationship { - Source = child, Target = parent + Target = parent }); var query = world.Query().With>().Build(); @@ -215,13 +209,13 @@ public class RelationshipTests // a → b (a is child of b) world.AddComponent(a, new Relationship { - Source = a, Target = b + Target = b }); // c → a (c is child of a) world.AddComponent(c, new Relationship { - Source = c, Target = a + Target = a }); // Destroy a. This should: @@ -233,4 +227,4 @@ public class RelationshipTests world.HasComponent>(c).Should().BeFalse(); world.GetSources>(b).Should().BeEmpty(); } -} +} \ No newline at end of file diff --git a/OECS/IRelationship.cs b/OECS/IRelationship.cs index 349a34f..78092a1 100644 --- a/OECS/IRelationship.cs +++ b/OECS/IRelationship.cs @@ -2,19 +2,14 @@ namespace OECS; /// /// Marker interface for components that represent a directed relationship -/// between two entities. The component is stored on the -/// entity and points to the entity. +/// between two entities. The component is stored on the source entity +/// (the entity it's added to) and points to the entity. /// /// The automatically maintains a reverse index so that /// all sources pointing to a given target can be looked up efficiently. /// public interface IRelationship { - /// - /// The entity that owns this relationship component. - /// - Entity Source { get; } - /// /// The entity that this relationship points to. /// diff --git a/OECS/Relationship.cs b/OECS/Relationship.cs index 008d2e2..cdb8975 100644 --- a/OECS/Relationship.cs +++ b/OECS/Relationship.cs @@ -14,7 +14,6 @@ namespace OECS; /// // Define a "ChildOf" relationship between a child and a parent entity. /// world.AddComponent(child, new Relationship<ChildOf, Parent> /// { -/// Source = child, /// Target = parent /// }); /// @@ -27,15 +26,9 @@ namespace OECS; public struct Relationship : IRelationship where TSelf : struct where TTarget : struct { - /// - /// The entity that owns this relationship component. - /// - [Key(0)] - public Entity Source { get; set; } - /// /// The entity that this relationship points to. /// - [Key(1)] + [Key(0)] public Entity Target { get; set; } } diff --git a/OECS/World.cs b/OECS/World.cs index a1cc480..2b1f675 100644 --- a/OECS/World.cs +++ b/OECS/World.cs @@ -203,19 +203,8 @@ public class World : IDisposable ThrowIfNotAlive(entity); // If replacing an existing relationship, remove the old index entry first. - if (component is IRelationship newRel) + if (component is IRelationship) { - // Guard against mismatched Source: the relationship must be stored - // on the entity it claims as its Source, otherwise the reverse - // index becomes corrupted. - if (newRel.Source != entity) - { - throw new InvalidOperationException( - $"Relationship of type {typeof(T).Name} has Source={newRel.Source} " + - $"but is being added to entity {entity}. " + - $"The Source must match the entity the component is added to."); - } - if (_components.TryGet(entity, out var old)) { var oldRel = (IRelationship)(object)old; diff --git a/OECS/WorldSerializer.cs b/OECS/WorldSerializer.cs index 173545c..302b8be 100644 --- a/OECS/WorldSerializer.cs +++ b/OECS/WorldSerializer.cs @@ -1,4 +1,3 @@ -using System.Reflection; using MessagePack; namespace OECS; @@ -94,32 +93,10 @@ public static class WorldSerializer $"Ensure the component type is used with the World " + $"API so the source generator can discover it."); - // Deserialize and fix up IRelationship.Source before adding. + // Deserialize and add via the typed internal method. var component = desc.Deserialize(ce.Data); - if (component is IRelationship rel) - FixupRelationshipSource(rel, entity); - - // Add via the typed internal method — no reflection for the add itself. world.AddComponentBoxed(entity, component, desc.Type); } } } - - /// - /// Sets the Source field on a deserialized IRelationship to match - /// the entity it's being restored to. Uses a small cache of PropertyInfo - /// to avoid repeated reflection lookups. - /// - private static void FixupRelationshipSource(IRelationship rel, Entity entity) - { - var type = rel.GetType(); - if (!_sourcePropCache.TryGetValue(type, out var prop)) - { - prop = type.GetProperty("Source"); - _sourcePropCache[type] = prop; - } - prop?.SetValue(rel, entity); - } - - private static readonly Dictionary _sourcePropCache = new(); } \ No newline at end of file