【步兵 lua】事件模型和事件解耦

【步兵 lua】事件模型和事件解耦 By EOS.

因为大多数程序是单线程,如果想处理外部事件,都离不开消息循环,
而事件模型和消息循环是天生一对,先了解下事件模式~


事件模型的特点

  • (1)可动态增加减少接收者
  • (2)可动态调整接收优先级
  • (3)每个接收对象都有机会响应事件,每个事件可由多个对象同时处理
  • (4)对于处理不了的事件,接收对象可以不做处理
  • (5)对于已经处理的事件,接收对象可以选择停止派发,也可以选择继续派发

对应关系:
(1)addListener
(2)changeListenerPriority
(3)addEvent、addListener、broadcast
(4)recvFunc 随意处理接受到的事件
(5)recvFunc – return def.event.ret_break 中断此消息的广播


源码

--Event.lua
Event = class("Event")
function Event:init(name, args)
    self.name = name
    self.args = args
end

EventListener = class("EventListener")
function EventListener:init(name, recvFunc, priority)
    self.name = name
    self.recv = recvFunc
    self.priority = priority or 0
end

--EventDispatcher.lua
EventDispatcher = class("EventDispatcher")
local cls = EventDispatcher

local EventListener = {
    name = "error",
    priority = 0,
    excute = function (event)

    end,
}

function cls:init()
    self._listeners = {}
    self._indexs = {}
    self._prioritys = {}
    self._events = {}
    self._maskEvents = {}
end

function cls:addEvent(event)
    self._events[event] = true
end

function cls:maskEvent(eventName)
    self._maskEvents[eventName] = true
end
function cls:cancelMaskEvent(event)
    self._maskEvents[eventName] = nil
end
function cls:cancelAllMaskEvent()
    self._maskEvents = {}
end

function cls:addListener(listener)
    local name = listener.name
    local priority = listener.priority or 0

    local _listeners = self._listeners
    local _indexs = self._indexs
    local _prioritys = self._prioritys

    _listeners[name] = _listeners[name] or {}
    _listeners[name][priority] = _listeners[name][priority] or {}
    _indexs[name] = _indexs[name] or {}
    _indexs[name][priority] = _indexs[name][priority] or 0
    _prioritys[name] = _prioritys[name] or {}


    local l_list = _listeners[name][priority]
    _indexs[name][priority] = _indexs[name][priority] + 1
    local index = _indexs[name][priority]
    l_list[index] = listener

    if not table.find(_prioritys[name], priority) then
        table.insert(_prioritys[name], priority)
        table.sort(_prioritys[name], function (a, b)
            return a > b
        end)
    end

    return name.."_"..priority.."_"..index
end

function cls:removeListener(id)
    local tags = string.split(id, "_")
    local name = tags[1]
    local priority = tonumber(tags[2])
    local index = tonumber(tags[3])

    local _listeners = self._listeners
    if _listeners[name] and _listeners[name][priority] then 
        _listeners[name][priority][index] = nil
    end
end

function cls:changeListenerPriority(id, newPriority)
    local tags = string.split(id, "_")
    local name = tags[1]
    local priority = tonumber(tags[2])
    local index = tonumber(tags[3])

    local _listeners = self._listeners
    if _listeners[name] and _listeners[name][priority] then 
        local listener = _listeners[name][priority][index] 
        _listeners[name][priority][index] = nil 
        listener.priority = newPriority
        self:addListener(listener)
    end
end

function cls:clear()
    self._listeners = {}
    self._indexs = {}
    self._prioritys = {}
end

function cls:broadcast()
    print("broadcast event:")
    for event,_ in pairs(self._events) do
        if not self._maskEvents[event.name] then
            table.dump(event)

            local _listeners = self._listeners
            local _prioritys = self._prioritys
            local name = event.name
            local listeners = _listeners[name]

            if listeners then
                for i=1, #_prioritys[name] do
                    local priority = _prioritys[name][i]
                    for k,v in pairs(listeners[priority]) do
                        local ret = v.recv(event.args)
                        if ret == def.event.ret_break then
                            return
                        end
                    end
                end
            end
        end
    end
end

测试代码:

local dispatcher = EventDispatcher.new()
    local event = Event.new(def.event.TestOne, {tab = "this is event One"})

    local listener = EventListener.new(def.event.TestOne, function(evt)
        print("recv: "..evt.tab)
    end, 1)

    local listener2 = EventListener.new(def.event.TestOne, function(evt)
        print("recv_2: "..evt.tab)
        -- return def.event.ret_break
    end, 2)

    local lsr_id = dispatcher:addListener(listener)
    local lsr_id_2 = dispatcher:addListener(listener2)

    dispatcher:addEvent(event)

    -- dispatcher:maskEvent(def.event.TestOne)
    -- dispatcher:cancelAllMaskEvent()
    -- dispatcher:removeListener(lsr_id_2)
    -- dispatcher:changeListenerPriority(lsr_id_2, 0)
    -- print(listener2.priority)
    dispatcher:broadcast()
end

为什么说事件可以解耦

正因为是单线程所以程序是顺序执行的。而事件系统却可以使其分离,实现模块化。
下面简单举个例子:
比如使用量个道具,人物升级了,刷新对应显示人物等级的地方。

方式一:

bag[useItem -> player[expAdd-> levelUp] -> ui_1[fresh] -> ...ui_n[fresh] -> end]

这样我使用一个道具的流程如果想结束。就必须背负着 人物属性相关模块
以及N个属性显示相关的模块,可能引发的问题就是,别人其中一个界面出了问题,
会导致我的模块报错,而我处理并没有任何问题,这就是耦合了。

方式二:

player[addListener(AddExp)]
ui_1[addListener(LevelUp)]
...
ui_n[addListener(LevelUp)]

bag[useItem -> addEvent(AddExp) -> end]
player[recv(AddExp) -> addEvent(LevelUp)]
ui_1[recv(LevelUp)]
...
ui_n[recv(LevelUp)]

效果是不是一目了然,各个模块都清晰的分离开了。这就是解耦~
因为我们用事件模式,“延迟调用”的方式(先添加,不处理),才得以实现。


总结

实现了一个独立的事件模型,但还有值得优化的地方。
我认为事件模型能解偶,主要在于延迟调用,如果即时调用的那发送事件是没意义的。
无非就是a->b->end变为了 a->c->b->end而已。

当然,事件过多的应用,可能会是逻辑过于分散,不容易调试。
不过只要规划好事件名就可以了,因为事件名就是这些逻辑的主心骨~ =、=

See Again~
之前
真爱无价,欢迎打赏~
赞赏码

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值