refactor: remove Source property from IRelationship
Remove the redundant `Source` property from `IRelationship` and its implementations. Since the relationship component is inherently stored on the source entity, the `Source` field was unnecessary and required manual synchronization during deserialization. This change simplifies the relationship model, reduces memory usage, and removes the need for reflection-based fixups in the serializer.
This commit is contained in:
parent
2de735498c
commit
91c6f4aa2c
|
|
@ -243,7 +243,7 @@ public class GameFlowTests
|
||||||
|
|
||||||
private static int CountCardsInHand(World world, Entity handEntity)
|
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.
|
// GetSources returns all card entities that have a Holds pointing to handEntity.
|
||||||
return world.GetSources<Holds>(handEntity).Count;
|
return world.GetSources<Holds>(handEntity).Count;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ public struct HitCommand : ICommand
|
||||||
|
|
||||||
// Remove from deck, add to hand.
|
// Remove from deck, add to hand.
|
||||||
world.RemoveComponent<InDeck>(cardEntity);
|
world.RemoveComponent<InDeck>(cardEntity);
|
||||||
world.AddComponent(cardEntity, new Holds { Source = cardEntity, Target = handEntity });
|
world.AddComponent(cardEntity, new Holds { Target = handEntity });
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static Entity FindEntity<T>(World world, Entity singletonEntity)
|
internal static Entity FindEntity<T>(World world, Entity singletonEntity)
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ public struct NewRoundCommand : ICommand
|
||||||
foreach (var card in cards)
|
foreach (var card in cards)
|
||||||
{
|
{
|
||||||
world.RemoveComponent<Holds>(card);
|
world.RemoveComponent<Holds>(card);
|
||||||
world.AddComponent(card, new InDeck { Source = card, Target = deckEntity });
|
world.AddComponent(card, new InDeck { Target = deckEntity });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,5 @@ namespace Game.Blackjack;
|
||||||
[MessagePackObject]
|
[MessagePackObject]
|
||||||
public record struct Holds : IRelationship
|
public record struct Holds : IRelationship
|
||||||
{
|
{
|
||||||
[Key(0)] public Entity Source { get; set; }
|
[Key(0)] public Entity Target { get; set; }
|
||||||
[Key(1)] public Entity Target { get; set; }
|
|
||||||
}
|
}
|
||||||
|
|
@ -10,6 +10,5 @@ namespace Game.Blackjack;
|
||||||
[MessagePackObject]
|
[MessagePackObject]
|
||||||
public record struct InDeck : IRelationship
|
public record struct InDeck : IRelationship
|
||||||
{
|
{
|
||||||
[Key(0)] public Entity Source { get; set; }
|
[Key(0)] public Entity Target { get; set; }
|
||||||
[Key(1)] public Entity Target { get; set; }
|
|
||||||
}
|
}
|
||||||
|
|
@ -36,7 +36,7 @@ public class DeckSetupSystem : ISystem
|
||||||
{
|
{
|
||||||
var cardEntity = world.CreateEntity();
|
var cardEntity = world.CreateEntity();
|
||||||
world.AddComponent(cardEntity, new Card { Suit = suit, Rank = rank });
|
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++)
|
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 });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -206,7 +206,7 @@ public class NunEffect : ICardEffect
|
||||||
world.RemoveComponent<OnField>(target);
|
world.RemoveComponent<OnField>(target);
|
||||||
if (world.HasComponent<Card>(target))
|
if (world.HasComponent<Card>(target))
|
||||||
world.RemoveComponent<Card>(target);
|
world.RemoveComponent<Card>(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.Commands.Enqueue(new DrawCardCommand { Player = pending.Player, Kind = null });
|
||||||
world.RemoveSingleton<PendingNun>();
|
world.RemoveSingleton<PendingNun>();
|
||||||
|
|
@ -293,7 +293,7 @@ public class PegasusEffect : ICardEffect
|
||||||
world.RemoveComponent<OnField>(target);
|
world.RemoveComponent<OnField>(target);
|
||||||
if (world.HasComponent<Card>(target))
|
if (world.HasComponent<Card>(target))
|
||||||
world.RemoveComponent<Card>(target);
|
world.RemoveComponent<Card>(target);
|
||||||
world.AddComponent(target, new HeldBy { Source = target, Target = pending.Player });
|
world.AddComponent(target, new HeldBy { Target = pending.Player });
|
||||||
}
|
}
|
||||||
world.RemoveSingleton<PendingPegasus>();
|
world.RemoveSingleton<PendingPegasus>();
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -321,7 +321,7 @@ public class CurseMasterEffect : ICardEffect
|
||||||
{
|
{
|
||||||
var skull = world.CreateEntity();
|
var skull = world.CreateEntity();
|
||||||
world.AddComponent(skull, new Skull());
|
world.AddComponent(skull, new Skull());
|
||||||
world.AddComponent(skull, new PlacedOn { Source = skull, Target = pt });
|
world.AddComponent(skull, new PlacedOn { Target = pt });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
world.RemoveSingleton<PendingCurseMaster>();
|
world.RemoveSingleton<PendingCurseMaster>();
|
||||||
|
|
@ -366,14 +366,14 @@ public static class CardEffectHelpers
|
||||||
{
|
{
|
||||||
var horn = world.CreateEntity();
|
var horn = world.CreateEntity();
|
||||||
world.AddComponent(horn, new Horn());
|
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)
|
public static void AddSkull(World world, Entity target)
|
||||||
{
|
{
|
||||||
var skull = world.CreateEntity();
|
var skull = world.CreateEntity();
|
||||||
world.AddComponent(skull, new Skull());
|
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)
|
public static Entity GetBanner(World world, Entity player)
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ public struct PlayCardCommand : ICommand
|
||||||
|
|
||||||
// Move from hand to field.
|
// Move from hand to field.
|
||||||
world.RemoveComponent<HeldBy>(CardEntity);
|
world.RemoveComponent<HeldBy>(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 });
|
world.AddComponent(CardEntity, new Card { Rank = Rank, FaceDown = FaceDown });
|
||||||
|
|
||||||
// Resolve on-play effects via registry.
|
// Resolve on-play effects via registry.
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,5 @@ namespace Game.CardWars;
|
||||||
[MessagePackObject]
|
[MessagePackObject]
|
||||||
public record struct HeldBy : IRelationship
|
public record struct HeldBy : IRelationship
|
||||||
{
|
{
|
||||||
[Key(0)] public Entity Source { get; set; }
|
[Key(0)] public Entity Target { get; set; }
|
||||||
[Key(1)] public Entity Target { get; set; }
|
|
||||||
}
|
}
|
||||||
|
|
@ -9,6 +9,5 @@ namespace Game.CardWars;
|
||||||
[MessagePackObject]
|
[MessagePackObject]
|
||||||
public record struct InDeck : IRelationship
|
public record struct InDeck : IRelationship
|
||||||
{
|
{
|
||||||
[Key(0)] public Entity Source { get; set; }
|
[Key(0)] public Entity Target { get; set; }
|
||||||
[Key(1)] public Entity Target { get; set; }
|
|
||||||
}
|
}
|
||||||
|
|
@ -9,6 +9,5 @@ namespace Game.CardWars;
|
||||||
[MessagePackObject]
|
[MessagePackObject]
|
||||||
public record struct OnField : IRelationship
|
public record struct OnField : IRelationship
|
||||||
{
|
{
|
||||||
[Key(0)] public Entity Source { get; set; }
|
[Key(0)] public Entity Target { get; set; }
|
||||||
[Key(1)] public Entity Target { get; set; }
|
|
||||||
}
|
}
|
||||||
|
|
@ -9,6 +9,5 @@ namespace Game.CardWars;
|
||||||
[MessagePackObject]
|
[MessagePackObject]
|
||||||
public record struct PlacedOn : IRelationship
|
public record struct PlacedOn : IRelationship
|
||||||
{
|
{
|
||||||
[Key(0)] public Entity Source { get; set; }
|
[Key(0)] public Entity Target { get; set; }
|
||||||
[Key(1)] public Entity Target { get; set; }
|
|
||||||
}
|
}
|
||||||
|
|
@ -38,7 +38,7 @@ public static class GameUtil
|
||||||
|
|
||||||
var cardEntity = cards.First();
|
var cardEntity = cards.First();
|
||||||
world.RemoveComponent<InDeck>(cardEntity);
|
world.RemoveComponent<InDeck>(cardEntity);
|
||||||
world.AddComponent(cardEntity, new HeldBy { Source = cardEntity, Target = player });
|
world.AddComponent(cardEntity, new HeldBy { Target = player });
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -54,7 +54,7 @@ public static class GameUtil
|
||||||
for (int i = cards.Length - 1; i >= 0; i--)
|
for (int i = cards.Length - 1; i >= 0; i--)
|
||||||
world.RemoveComponent<InDeck>(cards[i]);
|
world.RemoveComponent<InDeck>(cards[i]);
|
||||||
for (int i = 0; i < cards.Length; 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 });
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Get all field cards for a player.</summary>
|
/// <summary>Get all field cards for a player.</summary>
|
||||||
|
|
@ -135,7 +135,7 @@ public static class GameUtil
|
||||||
{
|
{
|
||||||
world.RemoveComponent<OnField>(card);
|
world.RemoveComponent<OnField>(card);
|
||||||
world.RemoveComponent<Card>(card);
|
world.RemoveComponent<Card>(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.
|
// Destroy horns and skulls placed on discarded cards.
|
||||||
|
|
@ -156,7 +156,7 @@ public static class GameUtil
|
||||||
foreach (var card in cards)
|
foreach (var card in cards)
|
||||||
{
|
{
|
||||||
world.RemoveComponent<InDeck>(card);
|
world.RemoveComponent<InDeck>(card);
|
||||||
world.AddComponent(card, new InDeck { Source = card, Target = drawDeck });
|
world.AddComponent(card, new InDeck { Target = drawDeck });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -57,7 +57,7 @@ public class GameSetupSystem : ISystem
|
||||||
var defs = CardDataLoader.LoadDefinitions(world, _cardDataCsv);
|
var defs = CardDataLoader.LoadDefinitions(world, _cardDataCsv);
|
||||||
foreach (var (defEntity, _) in defs)
|
foreach (var (defEntity, _) in defs)
|
||||||
{
|
{
|
||||||
world.AddComponent(defEntity, new InDeck { Source = defEntity, Target = publicDeck });
|
world.AddComponent(defEntity, new InDeck { Target = publicDeck });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shuffle public deck.
|
// Shuffle public deck.
|
||||||
|
|
@ -79,12 +79,12 @@ public class GameSetupSystem : ISystem
|
||||||
world.AddComponent(leader, new Leader());
|
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 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 Card { Rank = 0, FaceDown = false });
|
||||||
world.AddComponent(leader, new OnField { Source = leader, Target = player });
|
world.AddComponent(leader, new OnField { Target = player });
|
||||||
|
|
||||||
// Create banner.
|
// Create banner.
|
||||||
var banner = world.CreateEntity();
|
var banner = world.CreateEntity();
|
||||||
world.AddComponent(banner, new Banner());
|
world.AddComponent(banner, new Banner());
|
||||||
world.AddComponent(banner, new OnField { Source = banner, Target = player });
|
world.AddComponent(banner, new OnField { Target = player });
|
||||||
|
|
||||||
// Create faction deck.
|
// Create faction deck.
|
||||||
var factionDeck = world.CreateEntity();
|
var factionDeck = world.CreateEntity();
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ public class ScoringSystem : ISystem
|
||||||
if (castles.Count > 0)
|
if (castles.Count > 0)
|
||||||
{
|
{
|
||||||
var castle = castles[0];
|
var castle = castles[0];
|
||||||
world.AddComponent(castle, new HeldBy { Source = castle, Target = winner });
|
world.AddComponent(castle, new HeldBy { Target = winner });
|
||||||
world.RemoveComponent<Castle>(castle);
|
world.RemoveComponent<Castle>(castle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -111,7 +111,6 @@ public class EdgeCaseTests
|
||||||
sources.Add(source);
|
sources.Add(source);
|
||||||
world.AddComponent(source, new Relationship<ChildOf, ParentOf>
|
world.AddComponent(source, new Relationship<ChildOf, ParentOf>
|
||||||
{
|
{
|
||||||
Source = source,
|
|
||||||
Target = target
|
Target = target
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,6 @@ public class RelationshipTests
|
||||||
|
|
||||||
world.AddComponent(child, new Relationship<ChildOf, ParentOf>
|
world.AddComponent(child, new Relationship<ChildOf, ParentOf>
|
||||||
{
|
{
|
||||||
Source = child,
|
|
||||||
Target = parent
|
Target = parent
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -41,7 +40,6 @@ public class RelationshipTests
|
||||||
|
|
||||||
world.AddComponent(child, new Relationship<ChildOf, ParentOf>
|
world.AddComponent(child, new Relationship<ChildOf, ParentOf>
|
||||||
{
|
{
|
||||||
Source = child,
|
|
||||||
Target = parent
|
Target = parent
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -61,14 +59,12 @@ public class RelationshipTests
|
||||||
|
|
||||||
world.AddComponent(child, new Relationship<ChildOf, ParentOf>
|
world.AddComponent(child, new Relationship<ChildOf, ParentOf>
|
||||||
{
|
{
|
||||||
Source = child,
|
|
||||||
Target = parent1
|
Target = parent1
|
||||||
});
|
});
|
||||||
|
|
||||||
// Replace with a new target.
|
// Replace with a new target.
|
||||||
world.AddComponent(child, new Relationship<ChildOf, ParentOf>
|
world.AddComponent(child, new Relationship<ChildOf, ParentOf>
|
||||||
{
|
{
|
||||||
Source = child,
|
|
||||||
Target = parent2
|
Target = parent2
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -85,7 +81,6 @@ public class RelationshipTests
|
||||||
|
|
||||||
world.AddComponent(child, new Relationship<ChildOf, ParentOf>
|
world.AddComponent(child, new Relationship<ChildOf, ParentOf>
|
||||||
{
|
{
|
||||||
Source = child,
|
|
||||||
Target = parent
|
Target = parent
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -107,7 +102,6 @@ public class RelationshipTests
|
||||||
|
|
||||||
world.AddComponent(child, new Relationship<ChildOf, ParentOf>
|
world.AddComponent(child, new Relationship<ChildOf, ParentOf>
|
||||||
{
|
{
|
||||||
Source = child,
|
|
||||||
Target = parent
|
Target = parent
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -131,15 +125,15 @@ public class RelationshipTests
|
||||||
|
|
||||||
world.AddComponent(child1, new Relationship<ChildOf, ParentOf>
|
world.AddComponent(child1, new Relationship<ChildOf, ParentOf>
|
||||||
{
|
{
|
||||||
Source = child1, Target = parent
|
Target = parent
|
||||||
});
|
});
|
||||||
world.AddComponent(child2, new Relationship<ChildOf, ParentOf>
|
world.AddComponent(child2, new Relationship<ChildOf, ParentOf>
|
||||||
{
|
{
|
||||||
Source = child2, Target = parent
|
Target = parent
|
||||||
});
|
});
|
||||||
world.AddComponent(child3, new Relationship<ChildOf, ParentOf>
|
world.AddComponent(child3, new Relationship<ChildOf, ParentOf>
|
||||||
{
|
{
|
||||||
Source = child3, Target = parent
|
Target = parent
|
||||||
});
|
});
|
||||||
|
|
||||||
var sources = world.GetSources<Relationship<ChildOf, ParentOf>>(parent);
|
var sources = world.GetSources<Relationship<ChildOf, ParentOf>>(parent);
|
||||||
|
|
@ -156,11 +150,11 @@ public class RelationshipTests
|
||||||
|
|
||||||
world.AddComponent(entity, new Relationship<OwnedBy, Thing>
|
world.AddComponent(entity, new Relationship<OwnedBy, Thing>
|
||||||
{
|
{
|
||||||
Source = entity, Target = owner
|
Target = owner
|
||||||
});
|
});
|
||||||
world.AddComponent(entity, new Relationship<MemberOf, Thing>
|
world.AddComponent(entity, new Relationship<MemberOf, Thing>
|
||||||
{
|
{
|
||||||
Source = entity, Target = group
|
Target = group
|
||||||
});
|
});
|
||||||
|
|
||||||
world.GetSources<Relationship<OwnedBy, Thing>>(owner).Should().BeEquivalentTo([entity]);
|
world.GetSources<Relationship<OwnedBy, Thing>>(owner).Should().BeEquivalentTo([entity]);
|
||||||
|
|
@ -189,7 +183,7 @@ public class RelationshipTests
|
||||||
|
|
||||||
world.AddComponent(child, new Relationship<ChildOf, ParentOf>
|
world.AddComponent(child, new Relationship<ChildOf, ParentOf>
|
||||||
{
|
{
|
||||||
Source = child, Target = parent
|
Target = parent
|
||||||
});
|
});
|
||||||
|
|
||||||
var query = world.Query().With<Relationship<ChildOf, ParentOf>>().Build();
|
var query = world.Query().With<Relationship<ChildOf, ParentOf>>().Build();
|
||||||
|
|
@ -215,13 +209,13 @@ public class RelationshipTests
|
||||||
// a → b (a is child of b)
|
// a → b (a is child of b)
|
||||||
world.AddComponent(a, new Relationship<ChildOf, ParentOf>
|
world.AddComponent(a, new Relationship<ChildOf, ParentOf>
|
||||||
{
|
{
|
||||||
Source = a, Target = b
|
Target = b
|
||||||
});
|
});
|
||||||
|
|
||||||
// c → a (c is child of a)
|
// c → a (c is child of a)
|
||||||
world.AddComponent(c, new Relationship<ChildOf, ParentOf>
|
world.AddComponent(c, new Relationship<ChildOf, ParentOf>
|
||||||
{
|
{
|
||||||
Source = c, Target = a
|
Target = a
|
||||||
});
|
});
|
||||||
|
|
||||||
// Destroy a. This should:
|
// Destroy a. This should:
|
||||||
|
|
|
||||||
|
|
@ -2,19 +2,14 @@ namespace OECS;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Marker interface for components that represent a directed relationship
|
/// Marker interface for components that represent a directed relationship
|
||||||
/// between two entities. The component is stored on the <see cref="Source"/>
|
/// between two entities. The component is stored on the source entity
|
||||||
/// entity and points to the <see cref="Target"/> entity.
|
/// (the entity it's added to) and points to the <see cref="Target"/> entity.
|
||||||
///
|
///
|
||||||
/// The <see cref="World"/> automatically maintains a reverse index so that
|
/// The <see cref="World"/> automatically maintains a reverse index so that
|
||||||
/// all sources pointing to a given target can be looked up efficiently.
|
/// all sources pointing to a given target can be looked up efficiently.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IRelationship
|
public interface IRelationship
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// The entity that owns this relationship component.
|
|
||||||
/// </summary>
|
|
||||||
Entity Source { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The entity that this relationship points to.
|
/// The entity that this relationship points to.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,6 @@ namespace OECS;
|
||||||
/// // Define a "ChildOf" relationship between a child and a parent entity.
|
/// // Define a "ChildOf" relationship between a child and a parent entity.
|
||||||
/// world.AddComponent(child, new Relationship<ChildOf, Parent>
|
/// world.AddComponent(child, new Relationship<ChildOf, Parent>
|
||||||
/// {
|
/// {
|
||||||
/// Source = child,
|
|
||||||
/// Target = parent
|
/// Target = parent
|
||||||
/// });
|
/// });
|
||||||
///
|
///
|
||||||
|
|
@ -27,15 +26,9 @@ namespace OECS;
|
||||||
public struct Relationship<TSelf, TTarget> : IRelationship
|
public struct Relationship<TSelf, TTarget> : IRelationship
|
||||||
where TSelf : struct where TTarget : struct
|
where TSelf : struct where TTarget : struct
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// The entity that owns this relationship component.
|
|
||||||
/// </summary>
|
|
||||||
[Key(0)]
|
|
||||||
public Entity Source { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The entity that this relationship points to.
|
/// The entity that this relationship points to.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Key(1)]
|
[Key(0)]
|
||||||
public Entity Target { get; set; }
|
public Entity Target { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -203,19 +203,8 @@ public class World : IDisposable
|
||||||
ThrowIfNotAlive(entity);
|
ThrowIfNotAlive(entity);
|
||||||
|
|
||||||
// If replacing an existing relationship, remove the old index entry first.
|
// 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<T>(entity, out var old))
|
if (_components.TryGet<T>(entity, out var old))
|
||||||
{
|
{
|
||||||
var oldRel = (IRelationship)(object)old;
|
var oldRel = (IRelationship)(object)old;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
using System.Reflection;
|
|
||||||
using MessagePack;
|
using MessagePack;
|
||||||
|
|
||||||
namespace OECS;
|
namespace OECS;
|
||||||
|
|
@ -94,32 +93,10 @@ public static class WorldSerializer
|
||||||
$"Ensure the component type is used with the World " +
|
$"Ensure the component type is used with the World " +
|
||||||
$"API so the source generator can discover it.");
|
$"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);
|
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);
|
world.AddComponentBoxed(entity, component, desc.Type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 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.
|
|
||||||
/// </summary>
|
|
||||||
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<Type, PropertyInfo?> _sourcePropCache = new();
|
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue