Handlers - RamilGauss/MMO-Framework GitHub Wiki

Теория

Обработчики нужны для обработки событий из модулей (GraphicEngine (mouse, keyboard events), PhysicEngine (collsion events), NetTransportEngine (new packet events), etc.) и из кастомных событий, которые могут испускаться из префабов или просто объектов (entity) сцены.

По качеству источника событий:

  • Источник объект. Например, клик мыши на кнопке. Коллизия физ. объектов.
  • Источник не объект. Например, события ядра, нажатие кнопки клавиатуры, кастомное событие из системы. Глобальный источник.

Handler types:

  • Global. Глобальный, где бы не находился Entity. Декларация что может произойти событие. Причем неизвестно когда откуда. Просто оно есть. Example: KeyBoardHandlerComponent {}
  • Target by object. Нужно настроить из какого объекта (guid) получать события и фильтр объектов. Про фильтр надо подробнее описать. Потому что много вариантов и нужно исключить неработоспособные. Оставить только самые разумные. Например, описать все возможные варианты и редактор должен давать интерфейс только самые практичные.

Пример работы кастомного обработчика. Допустим есть иерархия:

...  
|  
| Entity - {WindowComponent, CustomButtonPrefabHandlerComponent}  
   |   
   |  (Prefab)  
   |->Entity {ButtonComponent, ButtonClickHandlerComponent, ...}  
      |
      |->...

Метод объекта ButtonClickHandlerComponent вызовется в случае клика на кнопку и владеющее этой кнопкой окно получит событие через другой обработчик CustomButtonPrefabHandlerComponent. По сути префаб декларирует внешнему миру какие события от него можно получить. ButtonClickHandlerComponent - внутренняя кухня префаба.

class IGlobalHandler {};
class ITargetHandler {};

class IButtonClickHandler : public ITargetHandler
{
public:
	virtual void Handle() = 0;
};
class IKeyboardHandler : public IGlobalHandler
{
public:
	virtual void Handle() = 0;
};

class TOkButtonClickHandler : public IButtonClickHandler
{
public:
	void Handle() override {}
};

struct TTargetHandlerComponent : public nsECSFramework::IComponent
{
        std::string entityGuid; // интерпретируется как комбобокс со списком всех кнопок в этом префабе
	std::string handlerType; // = "TOkButtonClickHandler"
	void* p = nullptr; // IButtonClickHandler
};
struct TGlobalHandlerComponent : public nsECSFramework::IComponent
{
	std::string handlerType;
	void* p = nullptr;
};

Хотелось бы иметь класс для обработки всех событий для всего объекта (на первых порах для низкого порога входа). В идеале, я считаю, должно быть одно событие - один класс. Так проще разруливать кирпичиками и проще писать тесты. Теория: Можно для такого класса сделать наследование от IHandler (для того чтобы генератор "видел" его). Далее в редакторе настраиваем связи, например, для 2 кнопок на форме указывать один и тот же класс, но разные методы. Тут проблема в том, что надо знать входные аргументы под это событие:

class IDirectoryChooseFormHandler: public IHandler
{
public:
    virtual void OkHandler(TEntityID eid) {};
    virtual void CancelHandler(TEntityID eid) {};
};

Потом после редактирования нужна кодогенерация связывания вызова лямбды для кнопок и методов класса (по имени класса и имени метода). С точки зрения разработчика будет понижен порог вхождения после опыта с ООП. Все обработчики будут локализованы в одном классе. Но нужно по документации знать аргументы обработчика в соответствии с событием.

Как альтернатива решения это задачи можно использовать отдельные объекты, в которых на каждый объект приходится по одному обработчику.

Способы реализации

  1. При создании объекта, на который ссылается обработчик. Ищем кто на него ссылается и пробрасываем в лямбду. При возникновении события вызываем метод обработчика.
  2. По факту возникновения события ищем кто ссылается и вызываем метод. Тут дольше искать, но нет зависимости от времени жизни ссылающихся и источника событий.

Первый способ выбирается основным.