Реализация системы сигнал-слот

MagicEngine использует сигналы для реализации событий.

Сигнал генерируется в ответ на некоторое событие, он несет в себе информацию об этом событии. Слот  это функция, которая вызывается в ответ на определнный сигнал.

Для сигналов и слотов в движке MagicEngine были выставлены следующие требования:

  1. Сигналы ничего не знают о слотах в которые они отправляются
  2. Сигналы несут в себе некоторые обобщенные данные
  3. Слот знает какие данные он хочет получить от сигнала
  4. Слот незнает кто отправил сигнал
  5. Допускаются множественные соединения (сигнал может быть присоединен к разным слотам, и к 1 слоту может быть прикреплено несколько сигналов)
  6. Сигналы и слоты имеют имена.

При реализации данной системы в начале была рассмотрена возможность использования сигналов/слотов из библиотеки boost: http://www.boost.org/doc/libs/1_38_0/doc/html/signals.html. Однако как оказалось они слегка не укладывались в имеющиеся требования, в частсности небыло возможности обращаться к сигналам и слотам только по именам, к томуже имена это строки. Требование данное вытекает из желания иметь возможность соединять сигналы и слоты в файле описания интерфейса, оперируя понятными человеку идиентификаторами. Например код ниже соединяет сиглан shoot от источника game_scene со слотом on_shoot объекта.

[signal1]
connect on_load
signal_source game_scene
signal_name shoot
slot_name on_shoot
[/]

С точки зрения разработчика система сигнал/слот состоит из 3-х частей:

  1. Сигналы
  2. Слоты
  3. Менеджер соединений

Сигнал — это некий именованный объект, который может хранить в себе данные.

Слот — это, в общем случае, указатель на некоторую функцию которой будет передан объект типа Сигнал.

Менеджер соединений — это набор функций, который обеспечивает соединение сигналов и слотов и доставку сигналов по их назначению.

Самая простая в реализации часть — это сигналы. Учитывая выставляемые требования реализация сигнала примерно такова:

class Signal

{

std::string name;

std::vector<int64_t> int_args;
std::vector<float> float_args;
std::vector<std::string> string_args;
} ;

Слоты:

template<class SlotClass, class SlotMethod> class Slot : public AbstractSlot
{
public:
virtual bool _invoke( SlotArgs *params ) throw( SlotAssert )
{
return ( _slot_class_->*_slot_method_ )( params );
}
Slot( SlotClass *t_class, SlotMethod t_method) : _slot_class_( t_class ), _slot_method_( t_method ){};
private:
SlotClass *_slot_class_;
SlotMethod _slot_method_;
};

В данном случае слот это шаблон. Он хранит в себе указатель на экземпляр класса и на метод этого класса которым должнем быть доставлен сигнал. В результате в программе использование таких слотов превращается в довольно ужасные строки типа:

Slot<MyClass, bool (class::*)(SlotArgs*)> SlotName;

Разумеется использовать такие определения не очень удобно, поэтому они заменяются на макросы, которыми можно легко и непринужденно объявлять, инициализировать и описывать слоты.

Менеджер сигналов должен реализовывать набор базовой функциональности:

  1. Регистрация слота — необходимо чтобы менеджер узнал что такой слот вообще существует
  2. Отмена регистрации слота — соответственно нужен чтобы забыть о слоте. При этом здесь необходимо также отсоединить все сигналы от данного слота.
  3. Соединение и рассоединение сигнала и слота (connect/disconnect) — чтобы задать соответствие слота и сигнала
  4. Посылка сигнала — данный метод должен обеспечивать поиск слота и отправку сигнала ему. Также неплохо иметь возможность по возврату слота определить стоит ли дальше посылать этот сигнал остальным слотам.
  5. Опциональный метод посылки сигнала по указанному адресу. При этом получить данный сигнла должен только указанный слот.

Хранение информации о сигналах и слотах реализовано в виде двух карт:

typedef std::multimap<SignalName, SlotName> SignalMap;
typedef std::map<SlotName, AbstractSlot*> SlotMap;

Первая карта связывает множество сигналов со множеством слотов а вторая позволяет по имени слота найти соответствующий объект.

Соответственно в таком случае соединение слота сводится к одной вставке в SignalMap. А его отсоединение есть задача более сложная — надо удалить из SignalMap все записи для указанного сигнала. Функция  посылки сигнала в такой схеме хранения также становится достаточно простой: необходимо найти первое и последнее вхождения имени сигнала в SignalMap и для каждого вхождения в этом диапазоне вызвать соответствующий слот через маппинг SlotMap.

В результате такой несложной реализации получается весьма мощная система управления событиями. Конечно неотъемлемый минус этой реализации это использование std::string в качестве идиентификаторов, однако за удобство надо платить 🙂


One Response to Реализация системы сигнал-слот

  1. Avatar Linker
    Linker says:

    Вот чего только люди не выдумают, лишь бы на Лиспе не писать. 😉