Skip to content

Измененения 2019.08.30

Dimitry edited this page Sep 13, 2019 · 5 revisions

В фреймворк внесено несколько изменений которые потребуют рефакторинг на текущих проектах.

  1. Процессоры с группой по умолчанию. Полезно если у вас для процессора только 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();
            }
        }
    }
  1. 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;
			   }
			}
		}
}
  1. Изменен настроечный код ( 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();
		}
	}

Это все изменения на текущий момент :) По вопросам пишите в дискорд!