Отношения
Отношения - это способ связи одних сущностей с другими, связи позволяют добавлять иерархии между сущностями
- связи могут быть нескольких видов и могут управляться автоматически
- связи основаны на компонентах или мультикомпонентах, и используют глобальный идентификатор сущности
Виды связей:
Однонаправленная связь к одному (To-One)
Пример:
// 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>();
Однонаправленная связь ко многим (To-Many)
Пример:
// 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>();
Двунаправленная связь один к одному (One-To-One)
Пример:
// 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>();
Двунаправленная связь один к одному (замкнутая пара) (One-To-One)
Пример:
// 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>()
Двунаправленная связь один ко многим (One-To-Many)
Пример:
// <- 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);
Двунаправленная связь многие ко многим (Many-To-Many)
Пример:
// <- 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)
Рассмотрим конфигурацию и пример шаг за шагом на примере связи Один ко многим
- Определение компонентов
Компоненты могут быть двух видов:
IEntityLinkComponent
- интерфейс компонента для хранения ссылки на одну сущность
IEntityLinksComponent
- интерфейс компонента для хранения ссылок на несколько сущностей
// Определим компонент связи Родителя - `One` типа IEntityLinkComponent
public struct Parent : IEntityLinkComponent<Parent> {
// Значение связи
public EntityGID Link;
ref EntityGID IRefProvider<Parent, EntityGID>.RefValue(ref Parent component) => ref component.Link;
public override string ToString() => Link.ToString();
}
// Определим компонент связи Детей - `Many` типа IEntityLinksComponent
public struct Childs: IEntityLinksComponent<Childs> {
// Значение связей
public ROMulti<EntityGID> Links;
// Реализуем технический метод для доступа к значению связи
ref ROMulti<EntityGID> IRefProvider<Childs, ROMulti<EntityGID>>.RefValue(ref Childs component) => ref component.Links;
public override string ToString() => Links.ToString();
}
- Создание мира и сущностей
W.Create(WorldConfig.Default());
// ...
// Регистрируем типы компонентов defaultComponentCapacity устанавливает минимальный размер Childs в мультикомпоненте
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"));
- Регистрация связи (Вариант 1 со стороны родителя)
Устанавливаем связь где father ссылается на детей {
sonAlex
,sonJack
,sonKevin
}
При установке компонента дети автоматически получат обратный компонентParent
с ссылкой на father
МетодSetLinks
создает или использует существующий компонент (используется для типаIEntityLinksComponent
)
принимает от 1-5EntityGID
значений и возвращает ссылку на компонент, в случае если значение уже установлена вDEBUG
будет ошибка
ref Childs childs = ref father.SetLinks<Childs>(sonAlex, sonJack, sonKevin);
- Регистрация связи (Вариант 2 со стороны детей)
Мы могли бы установить связь со стороны детей
При установке компонента родитель автоматически получит обратный компонент Childs с ссылкой на ребенка
МетодSetLink
добавляет компонент и устанавливает значение (используется для типаIEntityLinkComponent
)
в случае если компонент уже присутствует то компонент удаляется и добавляется новый
это необходимо для автоматического менеджмента связей
ref Parent sonAlexParent = ref sonAlex.SetLink<Parent>(father);
ref Parent sonJackParent = ref sonJack.SetLink<Parent>(father);
ref Parent sonKevinParent = ref sonKevin.SetLink<Parent>(father);
Таким образом не имеет значения с какой стороны установлена связь, обратная ссылка будет установлена в любом случае
Просмотрев все сущности мы можем убедиться в этом
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 )
- Удаление связи (Вариант 1 со стороны детей )
Метод
TryDeleteLink
удаляет связь (и компонент) если она есть (используется для типаIEntityLinkComponent
)
sonAlex.TryDeleteLink<Parent>();
sonJack.TryDeleteLink<Parent>();
sonKevin.TryDeleteLink<Parent>();
По умолчанию при удалении связи будет удалена обратная ссылка
Это значит что уfather
будет удалена ссылка на всех детей
Чтобы переопределить это поведение необходимо указать стратегию удаления при регистрации компонентов
leftDeleteStrategy
- стратегия удаления для компонентаParent
W.RegisterOneToManyRelationType<Parent, Childs>(defaultComponentCapacity: 4, leftDeleteStrategy: Default);
Доступны следующие виды стратегий:
- Default
: Ничего не делает при удалении
- DestroyLinkedEntity
: Уничтожает прилинкованную сущность
- DeleteAnotherLink
: Удаляет связь у прилинкованной сущности (поведение по умолчанию)
- Удаление связи (Вариант 2 со стороны родителя )
Метод
TryDeleteLinks
удаляет связь если она есть (используется для типаIEntityLinksComponent
)
принимает от 0-5EntityGID
значение, если значение не передано удаляет все связи
если связей не осталось то компонент тоже будет удален
father.TryDeleteLinks<Childs>();
По умолчанию при удалении связи будет удалена обратная ссылка Это значит что у всех детей будет удалена ссылка на родителя Чтобы переопределить это поведение необходимо указать стратегию удаления при регистрации компонентов
rightDeleteStrategy
- стратегия удаления для компонентаChilds
W.RegisterOneToManyRelationType<Parent, Childs>(defaultComponentCapacity: 4, rightDeleteStrategy: Default);
Доступны следующие виды стратегий:
- Default
: Ничего не делает при удалении
- DestroyLinkedEntity
: Уничтожает прилинкованную сущность
- DeleteAnotherLink
: Удаляет связь у прилинкованной сущности (поведение по умолчанию)
- Дополнительно
При регистрации можно переопределить DEBUG валидацию циклических связей, по умолчанию она включена
Для этого необходимо указатьdisableRelationsCheckDebug
=true
в методе регистрации компонентовПри регистрации можно переопределить поведение при копировании указав CopyStrategy
FFS.Libraries.StaticEcs.CopyStrategy
Компоненты отношений являются обычными компонентами и доступны все стандартные методы работы с некоторыми особенностями
entity.Ref<Parent>();
entity.HasAllOf<Parent>();
//..
Стоит предостеречь от изменения значений связей вручную (не через специальные методы такие как SetLink
, SetLinks
, TryDeleteLink
, TryDeleteLinks
)
например не стоит делать так: entity.Ref<Parent>().Link = someGid;
потому что это не позволяет автоматически управлять обратными ссылками и другими действиями и может привести к сломаной игровой логике
при этом ничего не мешает хранить дополнительные данные в компонентах помимо самой связи
- Методы фильтрации
Компоненты отношений можно использовать в запросах как и любые другие компоненты
W.QueryEntities.For<All<Parent, Childs>>()
// ..
Существуют специальные методы запросов:
WithLink<P, WT, QM>
Позволяет указать тип компонента одиночной связи P
Тип мира к которому принадлежит связь WT
Стандартный фильтр запроса такой как All Any и тд
Данный запрос найдет всех сущностей у которых есть активная связь с P, и у сущности связи фильтр совпадает с указанным
В примере ниже будут найдены все сущности у которых есть связь типаParent
, и у родителя есть компоненты Name и Position
foreach (var entity in W.QueryEntities.For<WithLink<Parent, WT, All<Name, Position>>>()) {
//..
}
WithLinksAny<P, WT, QM>
Позволяет указать тип компонента множественной связи P
Тип мира к которому принадлежит связь WT
Стандартный фильтр запроса такой как All Any и тд
Данный запрос найдет всех сущностей у которых есть активная связь с P, и ХОТЯ БЫ У ОДНОЙ сущности связи фильтр совпадает с указанным
В примере ниже будут найдены все сущности у которых есть связь типаChilds
, и у хотя бы одного ребенка есть компоненты Name и Position
foreach (var entity in W.QueryEntities.For<WithLinksAny<Childs, WT, All<Name, Position>>>()) {
//..
}
WithLinksAll<P, WT, QM>
Позволяет указать тип компонента множественной связи P
Тип мира к которому принадлежит связь WT
Стандартный фильтр запроса такой как All Any и тд
Данный запрос найдет всех сущностей у которых есть активная связь с P, и у ВСЕХ сущностей связи фильтр совпадает с указанным
В примере ниже будут найдены все сущности у которых есть связь типаChilds
, и у всех детей есть компоненты Name и Position
foreach (var entity in W.QueryEntities.For<WithLinksAll<Childs, WT, All<Name, Position>>>()) {
//..
}