You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
432 lines
14 KiB
Lua
432 lines
14 KiB
Lua
--- https://github.com/katcipis/luanotify
|
|
|
|
|
|
-- This module aims to build a generic hierarchic event system.
|
|
-- The hierarchic model uses string event names to define what event are you working with.
|
|
--
|
|
-- For example:
|
|
--
|
|
-- * "event" -> Just a normal event.
|
|
-- * "event:subevent" -> Using the subevent feature.
|
|
--
|
|
-- The ":" is what defines that you are using hierarchic events,
|
|
-- every ":" you put is a new hierarchic level.
|
|
--
|
|
-- When emiting "event:subevent", everyone connected at "event" and "event:subevent"
|
|
-- is going to be notified. When emitting "event", only the ones connected at "event" are
|
|
-- going to be notified.
|
|
--
|
|
-- Using this hierarchical structure it is easy to be notified only when a specific event happens
|
|
-- or when a whole bunch of events inside a category happens. All events can be expressed on a tree,
|
|
-- where a node is a event, and you can connect,add a pre emit, add a post emit,
|
|
-- disconnect, block, unblock, emit, on any node of the tree.
|
|
--
|
|
-- @class module
|
|
-- @name notify.event
|
|
local setmetatable = setmetatable
|
|
local string = string
|
|
local unpack = unpack
|
|
|
|
local Queue = require "notify.dqueue"
|
|
local separator = ":"
|
|
|
|
-- Module exported functions
|
|
local Event = {}
|
|
|
|
-- Class attributes and methods goes on this table --
|
|
local EventObject = {}
|
|
|
|
-- Metamethods goes on this table --
|
|
local EventObject_mt = {
|
|
__index = EventObject,
|
|
}
|
|
|
|
-- Private methods definition --
|
|
local function new_node()
|
|
return {
|
|
handlers = Queue.new(),
|
|
pre_emits = Queue.new(),
|
|
post_emits = Queue.new(),
|
|
blocked_handlers = {},
|
|
subevents = {},
|
|
}
|
|
end
|
|
|
|
local function get_nodes_names(event_name)
|
|
local nodes_names = {}
|
|
for n in string.gmatch(event_name, "[^" .. separator .. "]+") do
|
|
nodes_names[#nodes_names + 1] = n
|
|
end
|
|
return nodes_names
|
|
end
|
|
|
|
local function get_node(self, event_name)
|
|
local events_names = get_nodes_names(event_name)
|
|
local current_node = self.events[events_names[1]] or new_node()
|
|
|
|
self.events[events_names[1]] = current_node
|
|
for i = 2, #events_names do
|
|
local sub_node = current_node.subevents[events_names[i]] or new_node()
|
|
current_node.subevents[events_names[i]] = sub_node
|
|
current_node = sub_node
|
|
end
|
|
return current_node
|
|
end
|
|
|
|
local function unused_event(self, event_name)
|
|
local events_names = get_nodes_names(event_name)
|
|
local current_node = self.events[events_names[1]]
|
|
|
|
if not current_node then
|
|
return true
|
|
end
|
|
|
|
for i = 2, #events_names do
|
|
local sub_node = current_node.subevents[events_names[i]]
|
|
if not sub_node then
|
|
return true
|
|
end
|
|
current_node = sub_node
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
local function event_iterator(self, event_name)
|
|
local events_names = get_nodes_names(event_name)
|
|
local i = 2
|
|
local current_node = self.events[events_names[1]]
|
|
|
|
local function iterator()
|
|
if not current_node then
|
|
return
|
|
end
|
|
local ret = current_node
|
|
|
|
if events_names[i] then
|
|
current_node = current_node.subevents[events_names[i]]
|
|
i = i + 1
|
|
else
|
|
current_node = nil
|
|
end
|
|
|
|
return ret
|
|
end
|
|
|
|
return iterator
|
|
end
|
|
|
|
local function call_pre_emits(self, event_name)
|
|
local nodes = Queue.new()
|
|
local reversed_nodes = Queue.new()
|
|
|
|
for node in event_iterator(self, event_name) do
|
|
for pre_emit in node.pre_emits:get_iterator() do
|
|
pre_emit(event_name)
|
|
end
|
|
nodes:push_back(node)
|
|
reversed_nodes:push_front(node)
|
|
end
|
|
|
|
return nodes, reversed_nodes
|
|
end
|
|
|
|
local function call_post_emits(event_name, reversed_nodes)
|
|
for node in reversed_nodes:get_iterator() do
|
|
for post_emit in node.post_emits:get_iterator() do
|
|
post_emit(event_name)
|
|
end
|
|
end
|
|
end
|
|
|
|
local function call_handlers(self, params)
|
|
for node in params.nodes:get_iterator() do
|
|
for handler in node.handlers:get_iterator() do
|
|
if (self.stopped) then
|
|
return
|
|
end
|
|
if (node.blocked_handlers[handler] == 0) then
|
|
if (params.accumulator) then
|
|
params.accumulator(handler(params.event_name, unpack(params.args)))
|
|
else
|
|
handler(params.event_name, unpack(params.args))
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Module exported functions
|
|
|
|
---
|
|
-- Creates a new Event object.
|
|
-- @return The new Event object.
|
|
function Event.new()
|
|
local object = setmetatable({}, EventObject_mt)
|
|
|
|
-- create all the instance state data.
|
|
object.stopped = false
|
|
object.events = {}
|
|
return object
|
|
end
|
|
|
|
-- Class definition and methods --
|
|
|
|
---
|
|
-- Connects a handler function on this event.
|
|
-- If any subevent is emitted, this handler will be called too.
|
|
-- @param event_name - The event name.
|
|
-- @param handler_function - The function that will be called when the event_name is emitted.
|
|
function EventObject:connect(event_name, handler_function)
|
|
local node = get_node(self, event_name)
|
|
node.handlers:push_back(handler_function)
|
|
|
|
if not node.blocked_handlers[handler_function] then
|
|
node.blocked_handlers[handler_function] = 0
|
|
end
|
|
end
|
|
|
|
---
|
|
-- Disconnects a handler function on this event.
|
|
-- @param event_name - The event name.
|
|
-- @param handler_function - The function that will be disconnected.
|
|
function EventObject:disconnect(event_name, handler_function)
|
|
if unused_event(self, event_name) then
|
|
return
|
|
end
|
|
|
|
local node = get_node(self, event_name)
|
|
node.handlers:remove(handler_function)
|
|
node.blocked_handlers[handler_function] = nil
|
|
end
|
|
|
|
---
|
|
-- Does not execute the given handler function when the give event is emitted until it is unblocked.
|
|
-- It can be called several times for the same handler function.
|
|
--
|
|
-- Example:
|
|
--
|
|
-- local Event = require "notify.event"
|
|
-- local event = Event.new()
|
|
|
|
-- local function handler1(arg)
|
|
-- print(arg)
|
|
-- end
|
|
--
|
|
-- event:connect("mouse", handler1)
|
|
-- event:emit("mouse", "example") -- example gets printed.
|
|
--
|
|
-- event:block("mouse", handler1);
|
|
-- event:emit("mouse", "example") -- nothing gets printed.
|
|
--
|
|
--
|
|
-- @param event_name - The event name.
|
|
-- @param handler_function - The handler function that will be blocked.
|
|
function EventObject:block(event_name, handler_function)
|
|
if unused_event(self, event_name) then
|
|
return
|
|
end
|
|
|
|
local node = get_node(self, event_name)
|
|
local block = node.blocked_handlers[handler_function]
|
|
if block then
|
|
node.blocked_handlers[handler_function] = block + 1
|
|
end
|
|
end
|
|
|
|
---
|
|
-- Unblocks the handler function from the given event.
|
|
-- The calls to unblock must match the calls to block.
|
|
--
|
|
-- Example:
|
|
--
|
|
-- local Event = require "notify.event"
|
|
-- local event = Event.new()
|
|
--
|
|
-- local function handler1(arg)
|
|
-- print(arg)
|
|
-- end
|
|
--
|
|
-- event:connect("mouse", handler1)
|
|
-- event:emit("mouse", "example") -- example gets printed.
|
|
--
|
|
-- event:block("mouse", handler1);
|
|
-- event:emit("mouse", "example") -- nothing gets printed.
|
|
--
|
|
-- event:block("mouse", handler1);
|
|
-- event:emit("mouse", "example") -- nothing gets printed.
|
|
--
|
|
-- event:unblock("mouse", handler1);
|
|
-- event:emit("mouse", "example") -- nothing gets printed.
|
|
-- event:unblock("mouse", handler1);
|
|
-- event:emit("mouse", "example") -- example gets printed.
|
|
--
|
|
--
|
|
-- @param event_name - The event name.
|
|
-- @param handler_function - The handler function that will be unblocked.
|
|
function EventObject:unblock(event_name, handler_function)
|
|
if unused_event(self, event_name) then
|
|
return
|
|
end
|
|
|
|
local node = get_node(self, event_name)
|
|
if node.blocked_handlers[handler_function] and node.blocked_handlers[handler_function] > 0 then
|
|
|
|
node.blocked_handlers[handler_function] = node.blocked_handlers[handler_function] - 1
|
|
end
|
|
end
|
|
|
|
---
|
|
-- Emits an event and all handler functions connected to it will be called.
|
|
-- Emiting the event "event1::event2::event3" will call the handlers connected
|
|
-- on the following events, on this order:
|
|
-- * event1
|
|
-- * event1:event2
|
|
-- * event1:event2:event3
|
|
--
|
|
-- @param event_name - The event name.
|
|
-- @param ... - A optional list of parameters, they will be repassed to the handler functions connected to this event.
|
|
function EventObject:emit(event_name, ...)
|
|
self.stopped = false
|
|
local nodes, reversed_nodes = call_pre_emits(self, event_name)
|
|
call_handlers(self, {
|
|
event_name = event_name,
|
|
nodes = nodes,
|
|
args = {...},
|
|
})
|
|
call_post_emits(event_name, reversed_nodes)
|
|
end
|
|
|
|
---
|
|
-- Typical emission discards handlers return values completely.
|
|
-- This is most often what you need: just inform the world about something.
|
|
-- However, sometimes you need a way to get feedback.
|
|
-- For instance, you may want to ask: “is this value acceptable ?”
|
|
-- This is what accumulators are for. Accumulators are specified to events at emission time.
|
|
-- They can combine, alter or discard handlers return values, post-process them or even stop emission.
|
|
-- Since a handler can return multiple values, accumulators can receive multiple args too.
|
|
-- Following Lua flexible style we give the user the freedom to do whatever he wants with accumulators.
|
|
-- If you are using the hierarchic event system the behaviour of handlers calling is similar to the emit function.
|
|
-- @param event_name - The event name.
|
|
-- @param accumulator - Function that will receive handlers results or a table to accumulate
|
|
-- all the handlers returned values.
|
|
-- @param ... - A optional list of parameters, they will be repassed to the handler
|
|
-- functions connected to this signal.
|
|
function EventObject:emit_with_accumulator(event_name, accumulator, ...)
|
|
self.stopped = false
|
|
local nodes, reversed_nodes = call_pre_emits(self, event_name)
|
|
call_handlers(self, {
|
|
event_name = event_name,
|
|
nodes = nodes,
|
|
accumulator = accumulator,
|
|
args = {...},
|
|
})
|
|
call_post_emits(event_name, reversed_nodes)
|
|
end
|
|
|
|
---
|
|
-- Adds a pre_emit func, pre_emit functions can't be blocked, only added or removed.
|
|
-- They can't have their return collected by accumulators, they will not receive any data
|
|
-- passed on the emission and they are always called before ANY handler is called.
|
|
-- This is useful when you want to perform some global task before handling an event,
|
|
-- like opening a socket that the handlers might need to use or a opening a database.
|
|
-- pre_emit functions can make sure everything is ok before handling an event, reducing
|
|
-- the need to do this check_ups inside the handler functions itself (sometimes multiple times).
|
|
-- They are called on a queue (FIFO) policy based on the order they added.
|
|
-- When using hierarchy, pre_emission happen top-bottom. For example, with a mouse::button1 event,
|
|
-- first the pre_emit functions on mouse will be called, then mouse::button1 post_emit functions will be called.
|
|
-- @param event_name - The event name.
|
|
-- @param pre_emit_func - The pre_emit function.
|
|
function EventObject:add_pre_emit(event_name, pre_emit_func)
|
|
get_node(self, event_name).pre_emits:push_back(pre_emit_func)
|
|
end
|
|
|
|
---
|
|
-- Removes a pre-emit func from the given event.
|
|
-- @param event_name - The event name.
|
|
-- @param pre_emit_func - The pre_emit function.
|
|
function EventObject:remove_pre_emit(event_name, pre_emit_func)
|
|
if unused_event(self, event_name) then
|
|
return
|
|
end
|
|
get_node(self, event_name).pre_emits:remove(pre_emit_func)
|
|
end
|
|
|
|
---
|
|
-- Adds a post_emit function, post_emit functions can't be blocked, only added or removed,
|
|
-- they can't have their return collected by accumulators, they will not receive any data passed
|
|
-- on the emission and they are always called after ALL handlers where called.
|
|
-- This is useful when you want to perform some global task after handling an event,
|
|
-- like closing a socket or a database that the handlers might need to use or do some cleanup.
|
|
-- post_emit functions can make sure everything is released after handling an event, reducing the need
|
|
-- to do this check_ups inside some handler function, since some resources can be shared by multiple handlers.
|
|
-- They are called on a stack (LIFO) policy based on the order they added. When using hierarchy,
|
|
-- post_emission happen bottom-top. For example, with a mouse::button1 event, first the post_emit
|
|
-- functions on mouse::button1 will be called, then mouse post_emit functions will be called.
|
|
-- @param event_name - The event name.
|
|
-- @param post_emit_func - The post_emit function.
|
|
function EventObject:add_post_emit(event_name, post_emit_func)
|
|
get_node(self, event_name).post_emits:push_front(post_emit_func)
|
|
end
|
|
|
|
---
|
|
-- Removes a post-emit func from the given event.
|
|
-- @param event_name - The event name.
|
|
-- @param post_emit_func - The post_emit function.
|
|
function EventObject:remove_post_emit(event_name, post_emit_func)
|
|
if unused_event(self, event_name) then
|
|
return
|
|
end
|
|
get_node(self, event_name).post_emits:remove(post_emit_func)
|
|
end
|
|
|
|
---
|
|
-- Has effect only during a emission and will stop only this particular emission of the event.
|
|
-- Usually called inside a pre-emit (when a condition fail) or on any handler.
|
|
--
|
|
-- Example:
|
|
--
|
|
-- local Event = require "notify.event"
|
|
-- local event = Event.new()
|
|
--
|
|
-- function handler1()
|
|
-- print("handler1")
|
|
-- event.stop();
|
|
-- end
|
|
--
|
|
-- function handler2()
|
|
-- print("2")
|
|
-- end
|
|
--
|
|
-- event:connect("mouse", handler1)
|
|
-- event:connect("mouse::click", handler2)
|
|
--
|
|
-- event:emit("mouse::click") --handler2 never gets printed because handler1 always stops the emission
|
|
--
|
|
function EventObject:stop()
|
|
self.stopped = true
|
|
end
|
|
|
|
---
|
|
-- Removes all pre/post-emits and handlers from the given event_name.
|
|
-- If no name is given all pre/post-emits and handlers will be removed.
|
|
-- @param event_name - The name of the event that will be cleared, or nil to clear all events.
|
|
function EventObject:clear(event_name)
|
|
if (not event_name) then
|
|
self.events = {}
|
|
return
|
|
end
|
|
end
|
|
|
|
-- Public functions --
|
|
local global_event = Event.new()
|
|
|
|
---
|
|
-- Always returns the same Event instance, this way is easy to share the same Event object across different modules.
|
|
-- @return An EventObject instance.
|
|
function Event.get_global_event()
|
|
return global_event
|
|
end
|
|
|
|
return Event
|