Query

Запросы - механизм позволяющий осуществлять поиск сущностей и их компонентов в мире


Query Methods

Типы позволяющие описать фильтрации по компонентам\тегам\маскам используемые в QueryEntities и QueryComponents
Все типы ниже не требуют явной инициализации, не требуют кеширования, каждый из них занимает 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;

Маски:

MaskAll - фильтрует сущности на наличие всех указанных масок (может использоваться только в составе других методов) (перегрузка от 1 до 8)

MaskAll<Flammable, Frozen, Visible> all = default;

MaskNone - фильтрует сущности на отсутствие всех указанных масок (может использоваться только в составе других методов) (перегрузка от 1 до 8)

MaskNone<Flammable, Frozen, Visible> none = default;

MaskAny - фильтрует сущности на наличие любой из указанных масок (может использоваться только в составе других методов) (перегрузка от 1 до 8)

MaskAny<Flammable, Frozen, Visible> any = default;

Query Entities

Классический поиск сущностей в мире с указанными компонентами\тегами\масками
Все способы запросов ниже, не требуют кеширования, аллоцируются на стеке и могут использоваться “на лету”

// Различные наборы методов фильтрации могут быть применины к методу 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

Оптимизированный поиск сущностей и компонентов в мире с помощью делегатов
Данный способ “под капотом” разворачивает циклы и является более удобным и эффективным способом
Все способы запросов ниже, не требуют кеширования, аллоцируются на стеке и могут использоваться “на лету”

QueryEntities все еще полезен когда нужен “ранний” выход из цикла или не нужны данные компонентов

  • Пример поиска всех сущностей с указанными компонентами, может быть указано от 1 до 8 типов компонентов:
    W.QueryComponents.For(static (ref Position pos, ref Velocity vel, ref Direction dir) => {
      pos.Value += dir.Value * vel.Value;
    });
    
  • Можно указать сущность перед компонентами если она требуется:
    W.QueryComponents.For(static (W.Entity ent, ref Position pos, ref Velocity vel, ref Direction dir) => {
      pos.Value += dir.Value * vel.Value;
    });
    
  • Для избегания аллокаций делегата возможно передать первым параметром данные любого пользовательского типа:
W.QueryComponents.For(deltaTime, static (float dt, W.Entity ent /* Опционально */, ref Position pos, ref Velocity vel, ref Direction dir) => {
    pos.Value += dir.Value * vel.Value * dt;
});

// Можно использовать кортежи для нескольких параметров
W.QueryComponents.For((deltaTime, fixedDeltaTime), static ((float dt, float fdt) data, W.Entity entity /* Опционально */, ref Position pos, ref Velocity vel, ref Direction dir) => {
    // ...
});

// Также можно передать ref значение структуры любого пользовательского типа
int count = 0;
W.QueryComponents.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.QueryComponents.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.QueryComponents.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.QueryComponents.With(any).For((ref Position pos, ref Velocity vel, ref Direction dir) => {
    pos.Value += dir.Value * vel.Value;
});

// или можно использовать WithAdds\With (WithAdds аналогичен With но разрешающий указания только вторичных методов фильтрации (таких как None, Any))
WithAdds<
    None<Name>,
    TagAny<Unit, Player>
> with = default;

W.QueryComponents.With(with).For((ref Position pos, ref Velocity vel, ref Direction dir) => {
    pos.Value += dir.Value * vel.Value;
});

Parallel

Существует возможность многопоточной обработки:
Важно! Возвращается специальный тип сущности который запрещает все операции такие как (Add, Put …), разрешены только Ref, Has и тд
Нельзя в многопоточной обработке создавать, удалять сущности или компоненты, только читать и изменять существующие
По умолчанию сервис многопоточной обработки отключен, чтобы его включить необходимо при создании мира указать в конфиге ParallelQueryType как MaxThreadsCount
или (CustomThreadsCount и указать максимальное количество потоков) - полезно когда хочется задать разное количество для разных миров
Все способы запросов ниже, не требуют кеширования, аллоцируются на стеке и могут использоваться “на лету”

minChunkSize - значение определяет минимальное количество потенциальных сущностей после которого функция будет использовать несколько потоков

Примеры:

W.QueryComponents.Parallel.For(minChunkSize: 50000, (W.ROEntity ent /* Опционально */, ref Position pos, ref Velocity vel, ref Direction dir) => {
    pos.Value += dir.Value * vel.Value;
});

W.QueryComponents.Parallel.For(minChunkSize: 50000, deltaTime, (float dt, W.ROEntity ent /* Опционально */, ref Position pos, ref Velocity vel, ref Direction dir) => {
    pos.Value += dir.Value * vel.Value * dt;
});

WithAdds<
    None<Name>,
    TagAny<Unit, Player>
> with = default;
W.QueryComponents.Parallel.With(with).For(minChunkSize: 50000, (ref Position pos, ref Velocity vel, ref Direction dir) => {
    pos.Value += dir.Value * vel.Value;
});

Query Function

QueryComponents позволяет определять структуры функции вместо делегатов
Может быть использовано для оптимизации, передачи состояния в структуру или для вынесения логики

// Определим структуру-функцию которой можем заменить делегат
// Она должна реализовывать интерфейс 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.QueryComponents.For<Position, Velocity, Direction, StructFunction>();

// Вариант 1 с передачей через значение
W.QueryComponents.For<Position, Velocity, Direction, StructFunction>(new StructFunction());

// Вариант 1 с передачей через ref значение
var func = new StructFunction();
W.QueryComponents.For<Position, Velocity, Direction, StructFunction>(ref func);

// Вариант 2 с With через дженерик
W.QueryComponents.With<WithAdds<
    None<Name>,
    TagAny<Unit, Player>
>>().For<Position, Velocity, Direction, StructFunction>();

// Вариант 2 с With через значение
WithAdds<
    None<Name>,
    TagAny<Unit, Player>
> with = default;
W.QueryComponents.With(with).For<Position, Velocity, Direction, StructFunction>();

// Также возможно комбинировать систему и IQueryFunction, например:
// это может улучшить восприятия кода и увеличить производительность + это позволяет обращаться к нестатическим членам системы
public struct SomeFunctionSystem : IInitSystem, IUpdateSystem, W.IQueryFunction<Position, Velocity, Direction> {
    private UserService1 _userService1;
    
    WithAdds<
        None<Name>,
        TagAny<Unit, Player>
    > with;
    
    public void Init() {
        _userService1 = World.Context<UserService1>.Get();
    }
    
   public void Update() {
       W.QueryComponents
            .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);
    }
}

Для каждого метода фильтрации QueryComponents.For(), QueryEntities.For()
можно указать фильтрацию по статусу сущности, например:

W.QueryEntities.For<All<Position>>(entities: EntityStatusType.Disabled)
    
World.QueryComponents.For<Position>((World.Entity entity, ref Position position) => {
    position.Val *= velocity.Val;
}, entities: EntityStatusType.Disabled);