Добавлен , опубликован
Способ реализации:
Lua
Версия Warcraft:
Диспетчер событий, реализованный на Lua.
Является одним из фундаментальных модулей при построении ахритектуры приложения. Решает проблему высокого зацепления. В основе - паттерн observer
Простыми словами - создает прослойку общения между вашими наработками, после чего вы можете использовать в других проектах только часть из них и ничего не сломается.

Установка

Использование

local EventDispatcher = Imp.import(EventDispatcher)

-- Подписаться на событие
-- Параметр event - объект со свойствами "data", "name" и "stopPropagation"
-- Можно установить event.stopPropagation = true внутри коллбека, чтобы прервать текущий цикл рассылки
EventDispatcher.on("любое имя события", function(event)
    print("Callback A 1: " .. event.data)
end)

-- ... another file ...

-- Отправить событие со своими данными
EventDispatcher.dispatch("любое имя события", "данные события, любого типа")

История версий

1.2: исправлена возможность десинка (разного поведения на разных машинах) при множестве слушателей одного события
2.0: Добавлена поддержка IMP, имя модуля изменено на EventDispatcher
`
ОЖИДАНИЕ РЕКЛАМЫ...
29
Может не будем превращать это дело в NPM с тоннами бесполезных трехстрочных пекеджей?
38
Doc, ещё как будем, это именно фундамент для того, на чём я буду пилить дальнейшие пекеджи)
7
Очень полезная штука.
Ощущение, что 99.99999% обитателей форума не поняли, что это.
Приведу простой пример, если у вас одно событие на получение урона, то через некоторое время оно обрастает большим количеством условий под каждый предмет/спелл и тд. Это полностью решает данную проблему и делает код более понятным и удобным.
33
Ощущение, что 99.99999% обитателей форума не поняли, что это.
Это гуи - нет
Тут есть инком - нет
Работает на джасс - нет
Работает на 126 - нет
Это аниме - нет
Вот и ответ, к сожалению, просто понимать не кому

Я в посдений раз когда в варе кодил, вообще делал отдельные эвенты в отдельном триггере, для того чтобы можно было в 100 раз проще скопипастить в другую карту, 1 блок кода с 3 событиями, мини отлов урона, мини каст спелов и никаких систем вообще.
А так вещь очень хорошая и полезная
38
МрачныйВорон, непрямое обращение к разным частям системы

Типа если есть какой-то модуль, он слушает эти эвенты, если нет - то и пофиг, и так со всеми. Отвязанность
27
ScorpioT1000, уже видел в вашей демо наработке на движение с векторами. но ничего не понял. похоже на дебаг
38
Если по простому - это альтернатива вызова функции.
Способ Вызов:
Библиотека А реагирует на кнопку и вызывает функцию move() библиотеки Б, а Б двигает юнита
Способ События:
Библиотека Б подписывается на событие "move" и при его срабатывании двигает юнита
Библиотека А ждет нажатие кнопки и создает событие "move"
Отличие:
Попробуй удалить одну из библиотек не меняя их внутреннего кода. Прямой вызов придется переписывать

Преимущество в том, что на move могут подписаться многие библиотеки, при этом А не должна ничего о них знать
38
Вышла новая версия! Прокрутить к ресурсу

Версия 1.2

Исправлена возможность десинка (разного поведения на разных машинах) при множестве слушателей одного события

Обновление

При работе с wlpm просто вызвать wlpm install, при копировании вручную обновить код из инструкции по установке
11
Если по простому - это альтернатива вызова функции.
Способ Вызов:
Библиотека А реагирует на кнопку и вызывает функцию move() библиотеки Б, а Б двигает юнита
Способ События:
Библиотека Б подписывается на событие "move" и при его срабатывании двигает юнита
Библиотека А ждет нажатие кнопки и создает событие "move"
Отличие:
Попробуй удалить одну из библиотек не меняя их внутреннего кода. Прямой вызов придется переписывать

Преимущество в том, что на move могут подписаться многие библиотеки, при этом А не должна ничего о них знать
По этому объяснению я не понял в чем разница между триггерами, мы же по сути создавая триггер и подписываемся на разные события.
38
Koladik, речь про моддерский код и взаимодействие написанных нами систем. Триггеры тоже событийно-ориентированные, да. Только вот они работают на движке игры (с сетевой моделью), поэтому значительно медленнее, даже если бы мы могли создавать собственные события.
А эту штуку в целом можно и вне контекста warcraft 3 юзать.
11
Koladik, речь про моддерский код и взаимодействие написанных нами систем. Триггеры тоже событийно-ориентированные, да. Только вот они работают на движке игры (с сетевой моделью), поэтому значительно медленнее, даже если бы мы могли создавать собственные события.
А эту штуку в целом можно и вне контекста warcraft 3 юзать.
ну то есть, это, по сути, реализация подписки на события, аналогичная триггерам по функционалу но быстрее и более универсальная?
38
По тому же абстрактному принципу, но не аналогичная.
  • Здесь можно передавать любые типы данных
  • Событие адресуется по строке, а не хендлу эвента триггера
  • Цикл обработки завязан только на движок lua
  • Внутри обработчика можно остановить распространение текущего срабатывания на остальные обработчики (stopPropagation)
  • Мы не привязываемся к каким-либо константам и классам (хотя строку вполне можно объявлять как константу)
22
Раньше у тебя лежал код под катом прямо здесь. Зачем ты его выпилил? Было очень удобно.
Я несколько раз использовал эту наработку (правда не в играх), очень нравится.
Ответы (1)
38
avuremybe, на гитхабе актуальнее
17
"1.2: исправлена возможность десинка (разного поведения на разных машинах) при множестве слушателей одного события"
А с чем тот десинк был связан? Спрашиваю для общего развития, чтобы самому в будущем избегать кейса
Ответы (1)
38
Cancel, pairs vs ipairs, порядок ключей в итерации мап может быть разный
38
Вышла новая версия! Прокрутить к ресурсу
Версия 2.0
  • Добавлена поддержка IMP
  • Убрана поддержка WLPM
  • Имя модуля изменено на EventDispatcher (с нижнего регистра)
11
Немного улучшил читаемость кода и привел имена переменных в snake_case. Может заинтересует.

do
    ---@class EventDispatcher
    EventDispatcher = EventDispatcher or {}
    EventDispatcher.__meta = {
        __index = EventDispatcher
    }
    
    -- Конструктор класса
    function EventDispatcher:create()
        local obj = {
            event_dict = {}
        }
        return setmetatable(obj, EventDispatcher.__meta)
    end

    -- Подписаться на событие
    ---@param eventName string
    ---@param callback function
    function EventDispatcher:subscribe(event_name, callback)
        if not self.event_dict[event_name] then
            self.event_dict[event_name] = {}
        end
        table.insert(self.event_dict[event_name], callback)
    end
  
    
    table.find = table.find or function(tbl, item)
        local idxs = {}
        for i = 1, #tbl do
            if tbl[i] == item then
                table.insert(idxs, i)
            end
        end
        return idxs
    end
    
    -- Отписаться от события
    ---@param eventName string
    ---@param specialCallback function (опционально)
    function EventDispatcher:unsubscribe(event_name, subscriber)
        if subscriber == nil then return nil end
        local subscribers = self.event_dict[event_name]
        if subscribers == nil then return end
        if subscriber == nil then
            self.subscribers[event_name] = nil
        end
        -- поиск подписчиков в массиве подписчиков события
        idxs = table.find(subscribers, subscriber)
        for _, idx in ipairs(idxs) do
            table.remove(subscribers, i)
        end
        -- Если после удаления нет обработчиков, удаляем событие
        if #callbacks == 0 then
            self.handlers[event_name] = nil
        end
    end

    -- Отправить событие
    ---@param eventName string
    ---@param data any (опционально)
    function EventDispatcher:dispatch(event_name,...)
        local subscribers = self.event_dict[event_name]
        if callbacks == nil then return end

        -- Создаём копию списка обработчиков, чтобы избежать возможных изменений во время итерации
        local subscribers_copy = { table.unpack(callbacks) }
        for _, subscribers in ipairs(subscribers) do
            subscriber(...)
            -- subscriber(table.unpack(table.deepcopy({...}))) -- другой вариант
        end
    end

    -- Удалить все обработчики
    function EventDispatcher:clear()
        self.handlers = {}
    end
   setmetatable(EventDispatcher, {__call = EventDispatcher.create})
end
Ответы (4)
38
Koladik, snake case должен гореть в аду
11
ScorpioT1000, я принципами pep8 просто стараюсь пользоваться в луа. Не знаю насколько он тут пригоден, но кажется что это лучше чем без какого-то единообразия своего кода.
38
Koladik, что-то не заметно, чтобы разрабы питухона сами им пользовались, они вообще никаким кейсом не пользуются - просто хреначим слова слитно, видимо, не любят шифт)
Но вообще думал, любители тыкать по 2 клавиши между словами живут только где-то глубоко в си-подобных сообществах
11
ScorpioT1000, насколько мне известно, ситуации, когда пишут слитно слова в нижнем регистре имеют место когда группа слов образуют единое понятие. Не знаю прописанно ли это в пепе, но сюда относятся слова такие как deepcopy, getattr, isininstance.
Чтобы оставить комментарий, пожалуйста, войдите на сайт.