-
Notifications
You must be signed in to change notification settings - Fork 76
Измененения 2019.08.30
В фреймворк внесено несколько изменений которые потребуют рефакторинг на текущих проектах.
- Процессоры с группой по умолчанию. Полезно если у вас для процессора только 1 группа и вы хотите минимизировать кол-во настроек. Такие Процессоры работают со всеми фильтрами и атрибутами для групп.
// атрибут говорит что у группы процессора есть два события : на добавление сущности в группу и на выбывание.
[GroupWantEvent(Op.Add | Op.Remove)]
sealed class ProcessorObserver : Processor<ComponentObserver>, ITick
{
public void Tick(float delta)
{
// source является полем группы процессора.
for (int i = 0; i < source.length; i++)
{
ref var cObserver = ref source.entities[i].ComponentObserver();
for (int j = 0; j < cObserver.length; j++)
cObserver.wrappers[j].Check();
}
}
// Методы обработки событий группы.
public override void OnAdd(ent[] entities, int length)
{
for (int i = 0; i < length; i++)
{
ref var cObserver = ref entities[i].ComponentObserver();
for (int j = 0; j < cObserver.length; j++)
cObserver.wrappers[j].FirstTime();
}
}
public override void OnRemove(ent[] entities, int length)
{
for (int i = 0; i < length; i++)
{
ref var cObserver = ref entities[i].ComponentObserver();
for (int j = 0; j < cObserver.length; j++)
cObserver.wrappers[j].Check();
}
}
}
- Cобытия для групп существенно переработаны. Прежде следует рассказать о причинах. Раньше за события отвечали делегаты. К группам подключались методы с текущей добавленной или удаленной из группы сущности. Это давало очень удобную и приятную запись, но с точки зрения производительности это самый неоптимальный путь в юнити особенно для IL2CPP сборки. Так же мы подписывали обертку на событие для каждой отдельно добавленной сущности.
Пример: на кадре пришло 500 рекрутов в группу. У группы есть событие что каждому новому рекруту выдается копье. Итого 500 вызовов метода.
Теперь все 500 рекрутов обработаются в 1 вызове. Я попытался сохранить гибкость делегатов и к группам по прежнему можно "подписывать" множество событий.
Код выше показан для обработки событий у процессора. Достаточно указать только атрибут с нужными операциями
[GroupWantEvent(Op.Add | Op.Remove)]
И вызвать перезапись методов OnAdd, OnRemove.
Как быть с группами? Для групп создается специальный класс обертка наследующийся от GroupEvents. Он предоставляет нужные методы для событий.
sealed partial class ProcessorGame : Processor, ITick
{
Group<ComponentDweller> groupOfEveryone;
public ProcessorGame() =>
groupOfEveryone.Set<EventEveryone>(Op.Add | Op.Remove);
class EventEveryone : GroupEvents
{
public override void OnAdd(ent[] entities, int length)
{
for (int i = 0; i < length; i++)
{
Game.resources[ResType.Population].amount++;
}
}
public override void OnRemove(ent[] entities, int length)
{
for (int i = 0; i < length; i++)
{
Game.resources[ResType.Population].amount--;
}
}
}
}
Как это работает. Для группы создается копия EventEveryone через метод Set. С помощью Op.Add|Op.Remove
эта копия кладется в массивы событий на добавление и/или удаление сущности из группы.
Однако теперь мы "дробим" класс на два и может так случится что нам потребуется доступ к процессору из класса событий. Как быть в таком случае?
За базу класса событий берется GroupEvents<T>
где T
- текущий процессор ProcessorGame
.
При инициализации событий передается процессор в метод Set
groupOfLivingSpace.Set<EventsLivingSpace>(Op.Add | Op.Remove, this);
sealed partial class ProcessorGame : Processor, ITick
{
Group<ComponentLivingSpace> groupOfLivingSpace;
[GroupBy(Tag.Homeless)]
Group<ComponentDweller> groupOfHomeless;
public ProcessorGame() =>
groupOfLivingSpace.Set<EventsLivingSpace>(Op.Add | Op.Remove, this);
// Из события можно обратиться к процессору через переменную proc.
class EventsLivingSpace : GroupEvents<ProcessorGame>
{
public override void OnAdd(ent[] entities, int length)
{
var amountOfHomeless = Mathf.Clamp(proc.groupOfHomeless.length, 0, proc.CalculatePopulationCap());
for (var i = 0; i < amountOfHomeless; i++)
Game.PawnChangeTo(proc.groupOfHomeless[i], Prefab.PawnCivil, Model.PawnCivil);
}
public override void OnRemove(ent[] entities, int length)
{
for (int i = 0; i < length; i++)
{
ref var entity = ref entities[i];
ref var population = ref Game.resources[ResType.Population];
population.cap -= entity.ComponentLivingSpace().size;
}
}
}
}
- Изменен настроечный код ( boilerplate ) для компонентов. Фреймворк обладает очень примитивной генерацией компонентов которую можно реализовать как шаблон на IDE или использовать генератор компонентов фреймворка прямо из Unity. Все изменения идут в ключе предыдущего пункта : избавление от делегатов и оптимизация вызовов.
Для настроек используется специальный static class Components.
static SComponentFSM sComponentFSM = new SComponentFSM();
Cоздается класс настройщик компонента который свящан с хранилищем компонента ComponentFSM.
sealed class ComponentFSM
{
public IFSM source;
public int stateNext = -1;
public int stateCurrent = -1;
}
#region HELPERS
static partial class Components
{
[RuntimeInitializeOnLoadMethod]
static void ComponentFSMInit() => new SComponentFSM();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static ref ComponentFSM ComponentFSM(in this ent entity)
=> ref Storage<ComponentFSM>.components[entity.id];
internal class SComponentFSM : Storage<ComponentFSM>.Setup
{
// метод для возврата нового компонента.
public override ComponentFSM Create() => new ComponentFSM();
// метод для сброса всех покинувших на кадре компонентов этого типа.
// чистим поля и выставляем стандартные настройки.
public override void Dispose(int[] id, int len)
{
for (int i = 0; i < len; i++)
{
ref var component = ref components[id[i]];
component.source = null;
component.stateCurrent = -1;
component.stateNext = -1;
}
}
}
}
#endregion
В случае если dispose метод для компонента не нужен то просто пишем без него.
static partial class Components
{
static SComponentFSM sComponentFSM = new SComponentFSM();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static ref ComponentFSM ComponentFSM(in this ent entity)
=> ref Storage<ComponentFSM>.components[entity.id];
internal class SComponentFSM : Storage<ComponentFSM>.Setup
{
public override ComponentFSM Create() => new ComponentFSM();
}
}
Это все изменения на текущий момент :) По вопросам пишите в дискорд!