Relations
Relationships - are a way of linking some entities to other entities, relationships allow you to add hierarchies between entities
- links can be of several types and can be controlled automatically
- links are based on components or multi-components, and use a global entity identifier
Types of links:
Unidirectional link to one
Example:
// A Passenger -> B
public struct Passenger : IEntityLinkComponent<Passenger> {
public EntityGID Link;
ref EntityGID IRefProvider<Passenger, EntityGID>.RefValue(ref Passenger component) => ref component.Link;
}
W.RegisterToOneRelationType<Passenger>();
Unidirectional link to many
Example:
// Passenger -> B
// /
// A-- Passenger -> C
// \
// Passenger -> D
public struct Passengers : IEntityLinksComponent<Passengers> {
public ROMulti<EntityGID> Links;
ref ROMulti<EntityGID> IRefProvider<Passengers, ROMulti<EntityGID>>.RefValue(ref Passengers component) => ref component.Links;
}
W.RegisterToManyRelationType<Passengers>();
Bidirectional one-to-one link
Example:
// A <- Parent Child -> B <- Parent Child -> C
public struct Parent : IEntityLinkComponent<Parent> {
public EntityGID Link;
ref EntityGID IRefProvider<Parent, EntityGID>.RefValue(ref Parent component) => ref component.Link;
}
public struct Child : IEntityLinkComponent<Child> {
public EntityGID Link;
ref EntityGID IRefProvider<Child, EntityGID>.RefValue(ref Child component) => ref component.Link;
}
W.RegisterOneToOneRelationType<Parent, Child>();
Bidirectional one-to-one link (closed pair)
Example:
// Married
// A -------> B
// Married
// A <------- B
public struct MarriedTo : IEntityLinkComponent<MarriedTo> {
public EntityGID Link;
ref EntityGID IRefProvider<MarriedTo, EntityGID>.RefValue(ref MarriedTo component) => ref component.Link;
}
W.RegisterOneToOneRelationType<MarriedTo, MarriedTo>()
Bidirectional one-to-many link
Example:
// <- Parent Child -> B
// /
// A- <- Parent Child -> С
// \
// <- Parent Child -> D
public struct Parent : IEntityLinkComponent<Parent> {
public EntityGID Link;
ref EntityGID IRefProvider<Parent, EntityGID>.RefValue(ref Parent component) => ref component.Link;
}
public struct Childs: IEntityLinksComponent<Childs> {
public ROMulti<EntityGID> Links;
ref ROMulti<EntityGID> IRefProvider<Childs, ROMulti<EntityGID>>.RefValue(ref Childs component) => ref component.Links;
}
W.RegisterOneToManyRelationType<Parent, Childs>(defaultComponentCapacity: 4);
Bidirectional many-to-many link
Example:
// <- Owners Ownerships -> B
// /
// A- <- Owners Ownerships -> С
// /
// D- <- Owners Ownerships -> E
public struct Ownerships : IEntityLinksComponent<Ownerships> {
public ROMulti<EntityGID> Links;
ref ROMulti<EntityGID> IRefProvider<Ownerships, ROMulti<EntityGID>>.RefValue(ref Ownerships component) => ref component.Links;
}
public struct Owners : IEntityLinksComponent<Owners> {
public ROMulti<EntityGID> Links;
ref ROMulti<EntityGID> IRefProvider<Owners, ROMulti<EntityGID>>.RefValue(ref Owners component) => ref component.Links;
}
W.RegisterManyToManyRelationType<Ownerships, Owners>(16)
Let’s look at the configuration and example step by step using the example of One to many
link
- Component definition
Components can be of two types:
IEntityLinkComponent
- component interface for storing a reference to a single entity
IEntityLinksComponent
- component interface for storing references to several entities
// Let's define the Parent - `One` link component of type IEntityLinkComponent
public struct Parent : IEntityLinkComponent<Parent> {
public EntityGID Link;
// We implement a technical method to access the value of the relation
ref EntityGID IRefProvider<Parent, EntityGID>.RefValue(ref Parent component) => ref component.Link;
public override string ToString() => Link.ToString();
}
// Let's define a Child links component - `Many` of type IEntityLinksComponent
public struct Childs: IEntityLinksComponent<Childs> {
public ROMulti<EntityGID> Links;
// We implement a technical method to access the value of the relation
ref ROMulti<EntityGID> IRefProvider<Childs, ROMulti<EntityGID>>.RefValue(ref Childs component) => ref component.Links;
public override string ToString() => Links.ToString();
}
- Creation of the world and entities
W.Create(WorldConfig.Default());
// ...
// Register component types defaultComponentCapacity sets the minimum size of Childs in a multicomponent
W.RegisterOneToManyRelationType<Parent, Childs>(defaultComponentCapacity: 4);
// ...
W.Initialize();
var father = W.Entity.New(new Name("Father"));
var sonAlex = W.Entity.New(new Name("Son Alex"));
var sonJack = W.Entity.New(new Name("Son Jack"));
var sonKevin = W.Entity.New(new Name("Son Kevin"));
- Link Registration (Option 1 on the parent’s side)
Set up a link where father refers to children {
sonAlex
,sonJack
,sonKevin
}
When the component is installed, children will automatically receive a reverseParent
component with a reference to father
TheSetLinks
method creates or uses an existing component (used forIEntityLinksComponent
type)
takes from 1-5EntityGID
values and returns a reference to the component, in case the value is already set toDEBUG
there will be an error
ref Childs childs = ref father.SetLinks<Childs>(sonAlex, sonJack, sonKevin);
- Link Registration (Option 2 on the children’s side)
We could make a link from the children’s side
When the parent installs the component, the parent will automatically receive a Childs reverse component with a link to the child
TheSetLink
method adds a component and sets the value (used forIEntityLinkComponent
type)
if the component is already present, the component is deleted and a new one is added.
it’s necessary for automatic link management
ref Parent sonAlexParent = ref sonAlex.SetLink<Parent>(father);
ref Parent sonJackParent = ref sonJack.SetLink<Parent>(father);
ref Parent sonKevinParent = ref sonKevin.SetLink<Parent>(father);
So it does not matter on which side the link is established, the backlink will be established in any case
By looking at all the entities we can make sure of this
foreach (var entity in W.AllEntities()) {
Console.WriteLine(entity.PrettyString);
}
// Entity ID: 3
// Components:
// - [0] Name ( Son Kevin )
// - [1] Parent ( Father )
//
// Entity ID: 2
// Components:
// - [0] Name ( Son Jack )
// - [1] Parent ( Father )
//
// Entity ID: 1
// Components:
// - [0] Name ( Son Alex )
// - [1] Parent ( Father )
//
// Entity ID: 0
// Components:
// - [0] Name ( Father )
// - [2] Childs ( Son Alex, Son Jack, Son Kevin )
- Deletion of the link (Option 1 on the children’s side )
The
TryDeleteLink
method deletes the link (and component) if it exists (used forIEntityLinkComponent
type)
sonAlex.TryDeleteLink<Parent>();
sonJack.TryDeleteLink<Parent>();
sonKevin.TryDeleteLink<Parent>();
By default, deleting a link will remove the backlink when the link is deleted
This means thatfather' will have the reference to all children removed. To override this behavior you need to specify a deletion strategy when registering components
leftDeleteStrategy- deletion strategy for
Parent` component
W.RegisterOneToManyRelationType<Parent, Childs>(defaultComponentCapacity: 4, leftDeleteStrategy: Default);
The following types of strategies are available:
Default
: Doesn’t do anything when deletingDestroyLinkedEntity
: Destroys the attached entity-
DeleteAnotherLink
: Removes the link from the attached entity (default behavior) - Deletion of the link (Option 2 on the parent side )
The
TryDeleteLinks
method deletes a link if it exists (used forIEntityLinksComponent
type)
accepts from 0-5EntityGID
value, if no value is passed deletes all links
If there are no links left, the component will also be deleted.
father.TryDeleteLinks<Childs>();
By default, deleting a link will remove the backlink when the link is deleted
That means all the children will have their parental reference removed.
To override this behavior you need to specify a deletion strategy when registering components
rightDeleteStrategy
- removal strategy for a componentChilds
W.RegisterOneToManyRelationType<Parent, Childs>(defaultComponentCapacity: 4, rightDeleteStrategy: Default);
The following types of strategies are available:
Default
: Doesn’t do anything when deletingDestroyLinkedEntity
: Destroys the attached entityDeleteAnotherLink
: Removes the link from the attached entity (default behavior)
- Additionally
DEBUG validation of cyclic links can be overridden during registration, it is enabled by default
To do this, specifydisableRelationsCheckDebug
=true
in the component registration methodWhen registering, you can override the copy behavior by specifying CopyStrategy
FFS.Libraries.StaticEcs.CopyStrategy
Relationship components are normal components and all standard methods of operation are available with some special features
entity.Ref<Parent>();
entity.HasAllOf<Parent>();
//..
It is worth cautioning against changing the values of links manually (not through special methods such as SetLink
, SetLinks
, TryDeleteLink
, TryDeleteLinks
)
for example, you don’t want to do things like this: entity.Ref<Parent>().Link = someGid;
Because it doesn’t automatically manage backlinks and other actions and can lead to broken game logic
at the same time nothing prevents from storing additional data in components besides the connection itself
- Filtering methods
Relationship components can be used in queries like any other components
W.QueryEntities.For<All<Parent, Childs>>()
// ..
There are specific query methods:
WithLink<P, WT, QM>
Allows you to specify the type of single link component P
The type of world to which the link belongs WT
Standard query filter such as All Any, etc.
This query will find all entities that have an active relationship with P, and the relationship entity’s filter matches the specified one
The example below will find all entities that have a relationship of typeParent
, and the parent has Name and Position components
foreach (var entity in W.QueryEntities.For<WithLink<Parent, WT, All<Name, Position>>>()) {
//..
}
WithLinksAny<P, WT, QM>
Allows you to specify the type of multiple link component P
The type of world to which the link belongs WT
Standard query filter such as All Any, etc.
This query will find all entities that have an active relationship with P, and AT LEAST ONE entity has a relationship filter that matches the specified one
The example below will find all entities that have a relationship of typeChilds
, and at least one child has Name and Position components
foreach (var entity in W.QueryEntities.For<WithLinksAny<Childs, WT, All<Name, Position>>>()) {
//..
}
WithLinksAll<P, WT, QM>
Allows you to specify the type of multiple link component P
The type of world to which the link belongs WT
Standard query filter such as All Any, etc.
This query will find all entities that have an active relationship with P, and ALL entities have a filter that matches the specified one
The example below will find all entities that have a relationship of typeChilds
, and all children have Name and Position components
foreach (var entity in W.QueryEntities.For<WithLinksAll<Childs, WT, All<Name, Position>>>()) {
//..
}