Менеджер событий ver1

При масштабировании программного кода возникла необходимость взаимодействия между собой большого количества объектов. Реализовывать внутри объектов ссылки на другие объекту идея не очень. Необходимо что бы взаимодействие осуществлялось между собой абсолютно не связанных между собой объектов. Что бы они понятия не имели о существовании друг друга но имели возможность "общения" между собой.

Функциональные требования к менеджеру:

1. Иметь возможность любому объекту подписки на событие сразу в событии Create(касается п.7), и отправка сообщения подписчикам с параметрами (это базовые требования).

2. Исключена возможность двойной подписки конкретного объекта, на конкретное событие.

3. Должна быть как ручная отписка, так и автоматическая, в случае уничтожения(исчезновения) объекта, да бы если подписчик вдруг "откинется" и не засорять пустыми данными структуру подписчиков.

4. возможность отписки от всего и сразу!

5. отложенная во времени отправка сообщений подписчикам - это значительно экономит такты процессорного времени в каждом шаге/step`е программы ,  так же решает проблему функциональной атомарности.

6. Возможность подписки сразу же в событии Create объекта, т.е. менеджер событий уже должен быть готов как штык к плотной работе!  

7. отвязка менеджера от базового "объекта менеджера" в комнате (или по крайне мере от хронологии его создания в комнате) касается п.1

8. простота использования

 

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

И так содержимое скрипта sript_listen_manager.gml

для начала создаём глобальные переменные:

globalvar events_array;				//глобальная структура событий
events_array = {};					//создаём структуру

globalvar events_manager_obj;		//объект, используется для alarm что бы отправлять отложенные сообщения подписчикам
events_manager_obj = undefined;		//

//globalvar events_queque_status;		//статус отложенной отправки событий
//events_queque_status=false;			//

globalvar _events_queque_index;		//индекс объекта на котором остановилась отправка отложки
_events_queque_index=-1;			//

globalvar _events_queque_event;		//храним тут сам EVENT сообщение которое отправляется всем подписчикам
_events_queque_event=-1;			//

globalvar _events_queque_data;		//здесь хранятся данные для отправки подписчикам
_events_queque_data=undefined;		//

globalvar _events_queque_listens_array; //массив выделенных подписчиков по определённому сообщению
_events_queque_listens_array=undefined; //

 

//данные будут хранится в виде структуры
enum evenum
{
    _id      = 0,	//айди объекта
    _func    = 1	//функция которую необходимо вызвать
}

 

//подписка на событие, т.е. добавить объект/подписчика в массив событий
function events_listen(_event, _func) 
{
    //если такого эвента еще не создано
	if (is_undefined(events_array[$ _event])) 
	{	//то создаём его
        events_array[$ _event] = [];
    } 
	else 
	//если уже подписан то выходим 
	if (events_is_listen(id, _event) != -1) then return;
    
	//добавляем в массив нашего слушателя
    array_push(events_array[$ _event], [id, _func]);	
}

 

//проверка не подписан ли уже объект на данное событие
function events_is_listen(_id, _event) 
{
    //бежим по всему списку эвентов
	for (var i = 0; i < array_length(events_array[$ _event]); i += 1) 
	{
        //если таковой есть то возвращаем его индекс
		if (events_array[$ _event][i][evenum._id] == _id) then return i; //есть такой, возвращаем его индекс       
    } 
    //нету такого, возвращаем отрицательный ответ
	return -1;
}

 

//отправка сообщения слушателю
function events_publishing(_index, _array, _data)
{
	//если объект существует то отправляем ему сообщение
	if (instance_exists(_array[_index][evenum._id])) then _array[_index][evenum._func](_data);
    
	//иначе удаляем этого товарища из массива к едрени
	else array_delete(_array, _index, 1); 
}

 

//отправляется сообщение всем подписанным на него объектам/подписчикам
function events_publish(_event, _data) 
{
	//получаем массив подписчиков на данный	 EVENT
    var _listens_array = events_array[$ _event];
    
    //если слушателей нет то выходим
	if (is_undefined(_listens_array)) then return;    
    
	//пробегаемся по массиву слушателей, двигаемся обязательно с конца к началу
	//что бы по ходу "пьессы" удалять не существующих уже слушателей из массива
	//и при удалении(массив сдвигается) не пропустить слушателей
    for (var i = (array_length(_listens_array) - 1); i >= 0; i -= 1) 
	{
		events_publishing(i, _listens_array, _data)
    }    
}

 

Неотъемлемой частью разработки является так же и борьба за скорость. Данная функция отправляет отложенные сообщения по времени что бы не тормозить шаг программы циклом БОЛЬШОГО КОЛИЧЕСТВА сообщений.

//выполняется ОТЛОЖЕННАЯ отправка через таймер(объекта), сообщения всем объектам/подписчикам
function events_queque_publish(_event=_events_queque_event, _data=_events_queque_data) 
{	
	//если индекс объекта -1 значит это первая инициализация работы отложки
	if _events_queque_index == -1 then
	{
		//if instance_exists(events_manager_obj)==false then events_manager_obj = instance_create_depth(0,0,0,obj_event_manager);
		if is_undefined(events_manager_obj)==true then events_manager_obj = instance_create_depth(0,0,0,obj_event_manager);
		
		//получаем массив подписчиков на данный	 EVENT
		_events_queque_listens_array = events_array[$ _event];    
		
		//если слушателей нет в априоре вообще, то выходим
		if (is_undefined(_events_queque_listens_array)) then return;
	
		//получаем количество подписчиков
		_events_queque_index = array_length(_events_queque_listens_array) - 1;
		
		//сохраняем передаваемые параметры, их будем использовать при отложенной отправке
		_events_queque_data = _data;
		_events_queque_event = _event;
	};
	
	//делаем первую посылку
	events_publishing(_events_queque_index, _events_queque_listens_array, _events_queque_data);
	//уменьшаем индекс
	_events_queque_index -= 1;
	//запускаем алярм в объекте, который будет вызывать эту же функцию events_queque_publish каждый step шаг
	events_manager_obj.alarm[0] = 1;
	
	//если закончили отправлять сообщения
	if _events_queque_index == -1 then
	{	//обнуляем всё
		_events_queque_data = undefined;
		_events_queque_event = undefined;
		_events_queque_listens_array = undefined;
		//отключаем тайймер
		events_manager_obj.alarm[0] = 0;
	}	
}

 

//отписать объект/подписчик по его айди от события
function events_unlisten_id(_id, _event) 
{
    //если такого эвента не существует то выходим
	if (is_undefined(events_array[$ _event])) return;
    
	//получаем позицию объекта в массиве 
    var _index = events_is_listen(_id, _event);
	
    //удаляем его если нашелся
	if (_index != -1) then   array_delete(events_array[$ _event], _index, 1);    
}

 

//отписать текущий объект/подписчик от события
function events_unlisten(_event) {    events_unlisten_id(id, _event);   }

 

//отписать объект/подписчик от всех событий
function events_unlisten_all_id(_id=id)
{
    //получаем все значения имён массива
	var _keys_array = variable_struct_get_names(events_array);
	
    for (var i = (array_length(_keys_array) - 1); i >= 0; i -= 1) 
	{
        events_unlisten_id(_id, _keys_array[i]);
    }
}

 

//удаляем все позиции/подписчиков с данным эвентом
function events_remove_event(_event) 
{
    if (variable_struct_exists(events_array, _event)) then  variable_struct_remove(events_array, _event);    
}

 

//удаляем полностью  массив сообщений и подписчиков
function events_remove_all_events()
{
    delete events_array;
    events_array = {};
}

 

//очистка массива от не существующих объектов/подписчиков
function events_remove_dead_instances()
{
    var _keys_array = variable_struct_get_names(events_array);
    for (var i = 0; i < array_length(_keys_array); i += 1) 
	{
        var _keys_array_subs = events_array[$ _keys_array[i]];
        for (var j = (array_length(_keys_array_subs) - 1); j >= 0; j -= 1) 
		{
            if (!instance_exists(_keys_array_subs[j][0])) 
			{
                array_delete(events_array[$ _keys_array[i]], j, 1);
            }
        }
    }
}

 Пример использования:

//событие Create любого объета происходит подписка на событие
events_listen("куку", function(_data) 
{
	trace("получили куку = ", _data);
});

 

//в любом месте или в любом событии и в любом объекте теперь можно опубликовать событие

//отправка немедленная т.е. сразу всем подписчикам зв один шаг/step 
events_publish("kuku", 55);

//отправка отложенная во времени по шагам step, используя alarm[]=1
events_queque_publish("kuku", 66);

В данной реализации есть конеччно же свои недостатки. 

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

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