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) {};
};
Потом после редактирования нужна кодогенерация связывания вызова лямбды для кнопок и методов класса (по имени класса и имени метода). С точки зрения разработчика будет понижен порог вхождения после опыта с ООП. Все обработчики будут локализованы в одном классе. Но нужно по документации знать аргументы обработчика в соответствии с событием.
Как альтернатива решения это задачи можно использовать отдельные объекты, в которых на каждый объект приходится по одному обработчику.
Способы реализации
- При создании объекта, на который ссылается обработчик. Ищем кто на него ссылается и пробрасываем в лямбду. При возникновении события вызываем метод обработчика.
- По факту возникновения события ищем кто ссылается и вызываем метод. Тут дольше искать, но нет зависимости от времени жизни ссылающихся и источника событий.
Первый способ выбирается основным.