Query
Запросы - механизм позволяющий осуществлять поиск сущностей и их компонентов в мире
Query Methods
Типы позволяющие описать фильтрации по компонентам\тегам используемые в QueryEntities и Query
Все типы ниже не требуют явной инициализации, не требуют кеширования, каждый из них занимает 1 байт и может использоваться “на лету”
Компоненты:
All
- фильтрует сущности на наличие всех указанных включенных компонентов (перегрузка от 1 до 8)
All<Position, Direction, Velocity> all = default;
AllOnlyDisabled
- фильтрует сущности на наличие всех указанных отключенных компонентов (перегрузка от 1 до 8)
AllOnlyDisabled<Position, Direction, Velocity> all = default;
AllWithDisabled
- фильтрует сущности на наличие всех указанных (включенных и отключенных) компонентов (перегрузка от 1 до 8)
AllWithDisabled<Position, Direction, Velocity> all = default;
None
- фильтрует сущности на отсутствие всех указанных включенных компонентов (может использоваться только в составе других методов) (перегрузка от 1 до 8)
None<Position, Name> none = default;
NoneWithDisabled
- фильтрует сущности на отсутствие всех указанных (включенных и отключенных) компонентов (может использоваться только в составе других методов) (перегрузка от 1 до 8)
NoneWithDisabled<Position, Direction, Velocity> none = default;
Any
- фильтрует сущности на наличие любого из указанных включенных компонентов (может использоваться только в составе других методов) (перегрузка от 1 до 8)
Any<Position, Direction, Velocity> any = default;
AnyOnlyDisabled
- фильтрует сущности на наличие любого из указанных отключенных компонентов (может использоваться только в составе других методов) (перегрузка от 1 до 8)
AnyOnlyDisabled<Position, Direction, Velocity> any = default;
AnyWithDisabled
- фильтрует сущности на наличие любого из указанных (включенных и отключенных) компонентов (может использоваться только в составе других методов) (перегрузка от 1 до 8)
AnyWithDisabled<Position, Direction, Velocity> any = default;
Теги:
TagAll
- фильтрует сущности на наличие всех указанных тегов (перегрузка от 1 до 8)
All<Unit, Player> all = default;
TagNone
- фильтрует сущности на отсутствие всех указанных тегов (может использоваться только в составе других методов) (перегрузка от 1 до 8)
TagNone<Unit, Player> none = default;
TagAny
- фильтрует сущности на наличие любого из указанных тегов (может использоваться только в составе других методов) (перегрузка от 1 до 8)
TagAny<Unit, Player> any = default;
Query Entities
Классический поиск сущностей в мире с указанными компонентами\тегами
Все способы запросов ниже, не требуют кеширования, аллоцируются на стеке и могут использоваться “на лету”
// Итерация по всем сущностям в мире без фильтрации:
foreach (var entity in W.QueryEntities.For()) {
Console.WriteLine(entity.PrettyString);
}
// Различные наборы методов фильтрации могут быть применины к методу World.QueryEntities.For() например:
// Вариант с 1 методом через дженерик
foreach (var entity in W.QueryEntities.For<All<Position, Velocity, Direction>>()) {
entity.Ref<Position>().Value += entity.Ref<Direction>().Value * entity.Ref<Velocity>().Value;
}
// Вариант с 1 методом через значение
var all = default(All<Position, Direction, Velocity>);
foreach (var entity in W.QueryEntities.For(all)) {
entity.Ref<Position>().Value += entity.Ref<Direction>().Value * entity.Ref<Velocity>().Value;
}
// Вариант с 2 методами через дженерик
foreach (var entity in W.QueryEntities.For<
All<Position, Velocity, Name>,
None<Name>>()) {
entity.Ref<Position>().Value += entity.Ref<Direction>().Value * entity.Ref<Velocity>().Value;
}
// Вариант с 2 методами (All и None) через дженерик, можно указать до 8 методов фильтраций
foreach (var entity in W.QueryEntities.For<All<Position, Direction, Velocity>, None<Name>>()) {
entity.Ref<Position>().Value += entity.Ref<Direction>().Value * entity.Ref<Velocity>().Value;
}
// Вариант с 2 методами через значение
All<Position, Direction, Velocity> all2 = default;
None<Name> none2 = default;
foreach (var entity in W.QueryEntities.For(all2, none2)) {
entity.Ref<Position>().Value += entity.Ref<Direction>().Value * entity.Ref<Velocity>().Value;
}
Также все методы фильтрации могут быть сгруппированны в тип With
который может применяться к методу World.QueryEntities.For()
например:
// Способ 1 через дженерика
foreach (var entity in W.QueryEntities.For<With<
All<Position, Velocity, Direction>,
None<Name>,
TagAny<Unit, Player>
>>()) {
entity.Ref<Position>().Value += entity.Ref<Direction>().Value * entity.Ref<Velocity>().Value;
}
// Способ 2 через значения
With<
All<Position, Velocity, Direction>,
None<Name>,
TagAny<Unit, Player>
> with = default;
foreach (var entity in W.QueryEntities.For(with)) {
entity.Ref<Position>().Value += entity.Ref<Direction>().Value * entity.Ref<Velocity>().Value;
}
// Способ 3 через значения альтернативный
var with2 = With.Create(
default(All<Position, Velocity, Direction>),
default(None<Name>),
default(TagAny<Unit, Player>)
);
foreach (var entity in W.QueryEntities.For(with2)) {
entity.Ref<Position>().Value += entity.Ref<Direction>().Value * entity.Ref<Velocity>().Value;
}
Query Components
Оптимизированный поиск сущностей и компонентов в мире с помощью делегатов
Данный способ “под капотом” разворачивает циклы и является более удобным и эффективным способом
Все способы запросов ниже, не требуют кеширования, аллоцируются на стеке и могут использоваться “на лету”
- Пример поиска всех активных сущностей в мире:
W.Query.For(entity => { Console.WriteLine(entity.PrettyString); });
- Пример поиска всех сущностей с указанными компонентами, может быть указано от 1 до 8 типов компонентов:
W.Query.For(static (ref Position pos, ref Velocity vel, ref Direction dir) => { pos.Value += dir.Value * vel.Value; });
- Можно указать сущность перед компонентами если она требуется:
W.Query.For(static (W.Entity ent, ref Position pos, ref Velocity vel, ref Direction dir) => { pos.Value += dir.Value * vel.Value; });
- Для избегания аллокаций делегата возможно передать первым параметром данные любого пользовательского типа:
W.Query.For(deltaTime, static (ref float dt, W.Entity ent /* Сущность опционально */, ref Position pos, ref Velocity vel, ref Direction dir) => {
pos.Value += dir.Value * vel.Value * dt;
});
// Можно использовать кортежи для нескольких параметров
W.Query.For((deltaTime, fixedDeltaTime), static (ref (float dt, float fdt) data, W.Entity entity /* Сущность опционально */, ref Position pos, ref Velocity vel, ref Direction dir) => {
// ...
});
// Также можно передать ref значение любого пользовательского типа
int count = 0;
W.Query.For(ref count, static (ref int counter, W.Entity ent /* Опционально */, ref Position pos, ref Velocity vel, ref Direction dir) => {
pos.Value += dir.Value * vel.Value;
counter++;
});
- Дополнительно можно указать в каком статусе необходимо искать сущностей или компоненты:
W.Query.For(
static (ref Position pos, ref Velocity vel, ref Direction dir) => {
// ...
},
entities: EntityStatusType.Disabled, // (Enabled, Disabled, Any) По умолчанию Enabled
components: ComponentStatus.Disabled // (Enabled, Disabled, Any) По умолчанию Enabled
);
- Также возможно использовать With() для дополнительной фильтрации сущностей
Стоит заметить что компоненты которые указаны в делегате расцениваются как фильтр All
это значит что With() лишь дополняет фильтрацию и не требует указания используемых в делегате компонентов
W.Query.With<TagAny<Unit, Player>>().For((ref Position pos, ref Velocity vel, ref Direction dir) => {
pos.Value += dir.Value * vel.Value;
});
// или
TagAny<Unit, Player> any = default;
W.Query.With(any).For((ref Position pos, ref Velocity vel, ref Direction dir) => {
pos.Value += dir.Value * vel.Value;
});
// или можно использовать With
With<
None<Name>,
TagAny<Unit, Player>
> with = default;
W.Query.With(with).For((ref Position pos, ref Velocity vel, ref Direction dir) => {
pos.Value += dir.Value * vel.Value;
});
Parallel
Существует возможность многопоточной обработки:
Важно! Внутри итерации всегда работает QueryMode.Strict
это значит что модификация других (не итерируемой) сущностей запрещена (Будет ошибка в DEBUG) Временно нельзя в многопоточной обработке создавать новые сущности, расширять мультикомпоненты, и отправлять или читать события (Будет улучшено в следующих версиях) (Будет ошибка в DEBUG)
По умолчанию сервис многопоточной обработки отключен, чтобы его включить необходимо при создании мира указать в конфиге ParallelQueryType
как MaxThreadsCount
или (CustomThreadsCount
и указать максимальное количество потоков) - полезно когда хочется задать разное количество для разных миров
Все способы запросов ниже, не требуют кеширования, аллоцируются на стеке и могут использоваться “на лету”
minChunkSize
- значение определяет минимальное количество потенциальных сущностей после которого функция будет использовать несколько потоков
Примеры:
W.Query.Parallel.For(minChunkSize: 50000, (W.Entity ent /* Опционально */, ref Position pos, ref Velocity vel, ref Direction dir) => {
pos.Value += dir.Value * vel.Value;
});
W.Query.Parallel.For(minChunkSize: 50000, deltaTime, (ref float dt, W.Entity ent /* Опционально */, ref Position pos, ref Velocity vel, ref Direction dir) => {
pos.Value += dir.Value * vel.Value * dt;
});
With<
None<Name>,
TagAny<Unit, Player>
> with = default;
W.Query.Parallel.With(with).For(minChunkSize: 50000, ent => {
ent.Add<Name>();
});
Query Function
Query
позволяет определять структуры функции вместо делегатов
Может быть использовано для оптимизации, передачи состояния в структуру или для вынесения логики
// Определим структуру-функцию которой можем заменить делегат
// Она должна реализовывать интерфейс World.IQueryFunction с указанием от 1-8 компонентов
readonly struct StructFunction : W.IQueryFunction<Position, Velocity, Direction> {
public void Run(W.Entity entity, ref Position pos, ref Velocity vel, ref Direction dir) {
pos.Value += dir.Value * vel.Value;
}
}
// Вариант 1 с указанием дженерика (default Структура создается автоматически)
W.Query.For<Position, Velocity, Direction, StructFunction>();
// Вариант 1 с передачей через значение
W.Query.For<Position, Velocity, Direction, StructFunction>(new StructFunction());
// Вариант 1 с передачей через ref значение
var func = new StructFunction();
W.Query.For<Position, Velocity, Direction, StructFunction>(ref func);
// Вариант 2 с With через дженерик
W.Query.With<With<
None<Name>,
TagAny<Unit, Player>
>>().For<Position, Velocity, Direction, StructFunction>();
// Вариант 2 с With через значение
With<
None<Name>,
TagAny<Unit, Player>
> with = default;
W.Query.With(with).For<Position, Velocity, Direction, StructFunction>();
// Также возможно комбинировать систему и IQueryFunction, например:
// это может улучшить восприятия кода и увеличить производительность + это позволяет обращаться к нестатическим членам системы
public struct SomeFunctionSystem : IInitSystem, IUpdateSystem, W.IQueryFunction<Position, Velocity, Direction> {
private UserService1 _userService1;
With<
None<Name>,
TagAny<Unit, Player>
> with;
public void Init() {
_userService1 = World.Context<UserService1>.Get();
}
public void Update() {
W.Query
.With(with)
.For<Position, Velocity, Direction, SomeFunctionSystem>(ref this); // Передаем ссылку на функцию (систему)
}
// Определяем функцию
public void Run(W.Entity entity, ref Position pos, ref Velocity vel, ref Direction dir) {
pos.Value += dir.Value * vel.Value;
_userService1.CallSomeMethod(entity);
}
}
Query Mode
Для каждого метода фильтрации Query.For()
, Query.Search()
, QueryEntities.For()
можно указать строгость обращения к не итерируемым сущностям Доступны параметры:
QueryMode.Default
- По умолчанию если в конфигурации мираWorldConfig.DefaultQueryModeStrict = true
: будетStrict
иначеFlexible
QueryMode.Strict
- Запрещает модификацию фильтруемых типов компонентов\тегов у других сущностей (Итерация работает немного быстрей чем Flexible)QueryMode.Flexible
- Позволяет модификацию фильтруемых типов компонентов\тегов у других сущностей, и корректно контролирует корректность текущей итерации
Примеры:
var anotherEntity = ...;
for (var entity : W.QueryEntities.For<All<Position>>(queryMode: QueryMode.Strict)) {
anotherEntity.Delete<Position>(); // В DEBUG будет ошибка так как Strict режим и мы пытаемся модифицировать итерируемым типом компонента другую сущность
}
for (var entity : W.QueryEntities.For<All<Position>>(queryMode: QueryMode.Flexible)) {
anotherEntity.Delete<Position>(); // Ошибки не будет, и anotherEntity будет корректно исключена из текущей итерации
}
for (var entity : W.QueryEntities.For<None<Position>>(queryMode: QueryMode.Flexible)) {
anotherEntity.Add<Position>(); // Ошибки не будет, и anotherEntity будет корректно исключена из текущей итерации
}
// Аналогично для все остальных видов фильтрации, All, Any, None и тд - при Flexible режиме итерация будет стабильна
// Flexible полезен например для иерархий или кешей, когда мы обращаемся и модифицируем сущности из компонентов других сущностей или из сохраненных значений
// в остальных случаях предпочтительно использовать Strict режим по соображениям производительности
Для каждого метода фильтрации Query.For()
, Query.Parallel.For()
, Query.Search()
, QueryEntities.For()
можно указать фильтрацию по статусу сущности, например:
W.QueryEntities.For<All<Position>>(entities: EntityStatusType.Disabled)
World.Query.For<Position>((World.Entity entity, ref Position position) => {
position.Val *= velocity.Val;
}, entities: EntityStatusType.Disabled);