|
|
---
|
|
|
-- Signal object implementation.
|
|
|
-- @class module
|
|
|
-- @name notify.signal
|
|
|
local setmetatable = setmetatable
|
|
|
local Queue = require "notify.dqueue"
|
|
|
|
|
|
local Signal = {}
|
|
|
|
|
|
-- Class attributes and methods goes on this table --
|
|
|
local SignalObject = {}
|
|
|
|
|
|
-- Metamethods goes on this table --
|
|
|
local SignalObject_mt = {
|
|
|
__index = SignalObject,
|
|
|
}
|
|
|
|
|
|
-- Class definition and methods --
|
|
|
|
|
|
---
|
|
|
-- Disconnects a handler function from this signal, the function will no longer be called.
|
|
|
-- Example:
|
|
|
--
|
|
|
-- local signal = require "notify.signal"
|
|
|
-- local s = signal.new()
|
|
|
--
|
|
|
-- function handler(arg)
|
|
|
-- print(arg)
|
|
|
-- end
|
|
|
--
|
|
|
-- s:connect(handler)
|
|
|
-- s:emit("example") -- example gets printed
|
|
|
-- s:disconnect(handler)
|
|
|
-- s:emit("example") -- nothing gets printed
|
|
|
--
|
|
|
-- @param handler_function – The function that will be disconnected.
|
|
|
function SignalObject:disconnect(handler_function)
|
|
|
self.handlers:remove(handler_function)
|
|
|
self.handlers_block[handler_function] = nil
|
|
|
end
|
|
|
|
|
|
---
|
|
|
-- Connects a handler function on this signal, all handlers connected will be called
|
|
|
-- when the signal is emitted with a FIFO behaviour (The first connected will be the first called).
|
|
|
-- Example:
|
|
|
-- local signal = require "notify.signal"
|
|
|
--
|
|
|
-- function handler1(arg)
|
|
|
-- print(arg.."1")
|
|
|
-- end
|
|
|
-- function handler2(arg)
|
|
|
-- print(arg.."2")
|
|
|
-- end
|
|
|
--
|
|
|
-- local s = signal.new()
|
|
|
-- s:connect(handler1)
|
|
|
-- s:connect(handler2)
|
|
|
-- s:emit("example") -- example1 gets printed before example2.
|
|
|
--
|
|
|
-- @param handler_function – The function that will be called when this signal is emitted.
|
|
|
function SignalObject:connect(handler_function)
|
|
|
if (not self.handlers_block[handler_function]) then
|
|
|
self.handlers_block[handler_function] = 0
|
|
|
self.handlers:push_back(handler_function)
|
|
|
end
|
|
|
end
|
|
|
|
|
|
---
|
|
|
-- Does not execute the given handler function when the signal is emitted until it is unblocked.
|
|
|
-- It can be called several times for the same handler function.
|
|
|
-- Example:
|
|
|
--
|
|
|
-- local signal = require "notify.signal"
|
|
|
-- local s = signal.new()
|
|
|
--
|
|
|
-- function handler(arg)
|
|
|
-- print(arg)
|
|
|
-- end
|
|
|
--
|
|
|
-- s:connect(handler)
|
|
|
-- s:emit("example") -- example gets printed
|
|
|
--
|
|
|
-- s:block(handler)
|
|
|
-- s:emit("example") -- nothing gets printed
|
|
|
--
|
|
|
-- @param handler_function – The handler function that will be blocked.
|
|
|
function SignalObject:block(handler_function)
|
|
|
if (self.handlers_block[handler_function]) then
|
|
|
self.handlers_block[handler_function] = self.handlers_block[handler_function] + 1
|
|
|
end
|
|
|
end
|
|
|
|
|
|
---
|
|
|
-- Unblocks the given handler function, this handler function will be executed on
|
|
|
-- the order it was previously connected, and it will only be unblocked when
|
|
|
-- the calls to unblock are equal to the calls to block.
|
|
|
-- Example:
|
|
|
--
|
|
|
-- local signal = require "notify.signal"
|
|
|
-- local s = signal.new()
|
|
|
-- function handler(arg)
|
|
|
-- print(arg)
|
|
|
-- end
|
|
|
-- s:connect(handler)
|
|
|
-- s:emit("example") -- example gets printed
|
|
|
-- s:block(handler)
|
|
|
-- s:emit("example") -- nothing gets printed
|
|
|
-- s:block(handler)
|
|
|
-- s:emit("example") -- nothing gets printed
|
|
|
-- s:unblock(handler)
|
|
|
-- s:emit("example") -- nothing gets printed
|
|
|
-- s:unblock(handler)
|
|
|
-- s:emit("example") -- example gets printed
|
|
|
--
|
|
|
-- @param handler_function – The handler function that will be unblocked.
|
|
|
function SignalObject:unblock(handler_function)
|
|
|
if (self.handlers_block[handler_function]) then
|
|
|
if (self.handlers_block[handler_function] > 0) then
|
|
|
self.handlers_block[handler_function] = self.handlers_block[handler_function] - 1
|
|
|
end
|
|
|
end
|
|
|
end
|
|
|
|
|
|
---
|
|
|
-- Emits a signal calling the handler functions connected to this signal passing the given args.
|
|
|
--
|
|
|
-- local signal = require "notify.signal"
|
|
|
-- local s = signal.new()
|
|
|
-- function handler1(arg1, arg2)
|
|
|
-- print(arg1)
|
|
|
-- print(arg2)
|
|
|
-- end
|
|
|
-- function handler2(arg)
|
|
|
-- print(arg)
|
|
|
-- end
|
|
|
|
|
|
-- s:connect(handler1)
|
|
|
-- s:connect(handler2)
|
|
|
-- s:emit("example") -- a nil will get printed because only one argument was passed
|
|
|
-- s:emit("example1", "example2") -- No nil will get printed.
|
|
|
-- s:emit() -- Only nils will get printed because no argument was passed.
|
|
|
--
|
|
|
-- @param … – A optional list of parameters, they will be repassed to the handler functions connected to this signal.
|
|
|
function SignalObject:emit(...)
|
|
|
self.signal_stopped = false
|
|
|
|
|
|
for set_up in self.pre_emit_funcs:get_iterator() do
|
|
|
set_up()
|
|
|
end
|
|
|
|
|
|
for handler in self.handlers:get_iterator() do
|
|
|
if (self.signal_stopped) then
|
|
|
break
|
|
|
end
|
|
|
if (self.handlers_block[handler] == 0) then
|
|
|
handler(...)
|
|
|
end
|
|
|
end
|
|
|
|
|
|
for tear_down in self.post_emit_funcs:get_iterator() do
|
|
|
tear_down()
|
|
|
end
|
|
|
end
|
|
|
|
|
|
---
|
|
|
-- Typical signal emission discards handler 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 signals at emission time.
|
|
|
-- They can combine, alter or discard handler 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 user has the freedom to do whatever he wants with accumulators.
|
|
|
--
|
|
|
-- local signal = require "notify.signal"
|
|
|
-- local s = signal.new()
|
|
|
--
|
|
|
-- function handler1(arg)
|
|
|
-- return arg * 2
|
|
|
-- end
|
|
|
--
|
|
|
-- function handler2(arg)
|
|
|
-- return arg * 3
|
|
|
-- end
|
|
|
--
|
|
|
-- local result = {}
|
|
|
-- function accum(arg)
|
|
|
-- result[#result+1] = arg
|
|
|
-- end
|
|
|
--
|
|
|
-- s:connect(handler1)
|
|
|
-- s:connect(handler2)
|
|
|
--
|
|
|
-- s:emit_with_accumulator(accum, 2)
|
|
|
--
|
|
|
-- for k,v in ipairs(result) do -- print 4, 6
|
|
|
-- print(v)
|
|
|
-- end
|
|
|
--
|
|
|
-- @param accumulator – Function that will accumulate handlers results.
|
|
|
-- @param … – A optional list of parameters, they will be repassed to the handler functions connected to this signal.
|
|
|
function SignalObject:emit_with_accumulator(accumulator, ...)
|
|
|
self.signal_stopped = false;
|
|
|
|
|
|
for set_up in self.pre_emit_funcs:get_iterator() do
|
|
|
set_up()
|
|
|
end
|
|
|
|
|
|
for handler in self.handlers:get_iterator() do
|
|
|
if (self.signal_stopped) then
|
|
|
break
|
|
|
end
|
|
|
if (self.handlers_block[handler] == 0) then
|
|
|
accumulator(handler(...))
|
|
|
end
|
|
|
end
|
|
|
|
|
|
for tear_down in self.post_emit_funcs:get_iterator() do
|
|
|
tear_down()
|
|
|
end
|
|
|
end
|
|
|
|
|
|
---
|
|
|
-- Adds a pre_emit func, pre_emit functions cant be blocked, only added or removed,
|
|
|
-- they cannot have their return collected by accumulators, 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 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 function. They are called on a queue (FIFO) policy based on the order they added.
|
|
|
--
|
|
|
-- local signal = require "notify.signal"
|
|
|
-- local s = signal.new()
|
|
|
--
|
|
|
-- function handler1()
|
|
|
-- print(1)
|
|
|
-- end
|
|
|
--
|
|
|
-- function handler2()
|
|
|
-- print(2)
|
|
|
-- end
|
|
|
--
|
|
|
-- function pre_emit()
|
|
|
-- print("0")
|
|
|
-- end
|
|
|
--
|
|
|
-- s:connect(handler1)
|
|
|
-- s:connect(handler2)
|
|
|
-- s:emit() -- 1 and 2 printed.
|
|
|
-- s:add_pre_emit(pre_emit)
|
|
|
-- s:emit() -- 0,1 and 2 are printed.
|
|
|
--
|
|
|
-- @param pre_emit_func – The pre_emit function.
|
|
|
function SignalObject:add_pre_emit(pre_emit_func)
|
|
|
self.pre_emit_funcs:push_back(pre_emit_func)
|
|
|
end
|
|
|
|
|
|
---
|
|
|
-- Removes the pre_emit function
|
|
|
-- @param pre_emit_func – The pre_emit function.
|
|
|
function SignalObject:remove_pre_emit(pre_emit_func)
|
|
|
self.pre_emit_funcs:remove(pre_emit_func)
|
|
|
end
|
|
|
|
|
|
---
|
|
|
-- Adds a post_emit function, post_emit functions cant be blocked, only added or removed,
|
|
|
-- they cannot 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 that the handlers might need to use or a database 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.
|
|
|
-- Example:
|
|
|
--
|
|
|
-- local signal = require "notify.signal"
|
|
|
-- local s = signal.new()
|
|
|
--
|
|
|
-- function handler1()
|
|
|
-- print(1)
|
|
|
-- end
|
|
|
--
|
|
|
-- function handler2()
|
|
|
-- print(2)
|
|
|
-- end
|
|
|
--
|
|
|
-- function post_emit()
|
|
|
-- print("3")
|
|
|
-- end
|
|
|
--
|
|
|
-- s:connect(handler1)
|
|
|
-- s:connect(handler2)
|
|
|
-- s:emit() -- 1 and 2 printed.
|
|
|
-- s:add_post_emit(post_emit)
|
|
|
-- s:emit() -- 1, 2 and 3 are printed.
|
|
|
--
|
|
|
-- @param post_emit_func – The post_emit function.
|
|
|
function SignalObject:add_post_emit(post_emit_func)
|
|
|
self.post_emit_funcs:push_front(post_emit_func)
|
|
|
end
|
|
|
|
|
|
---
|
|
|
-- Removes the post_emit function
|
|
|
-- @param post_emit_func – The post_emit function.
|
|
|
function SignalObject:remove_post_emit(post_emit_func)
|
|
|
self.post_emit_funcs:remove(post_emit_func)
|
|
|
end
|
|
|
|
|
|
---
|
|
|
-- Stops the current emission, if there is any handler left to be called by the signal it wont be called.
|
|
|
-- Example:
|
|
|
--
|
|
|
-- local signal = require "notify.signal"
|
|
|
-- local s = signal.new()
|
|
|
--
|
|
|
-- local function handler1()
|
|
|
-- print("hanlder1")
|
|
|
-- signal:stop()
|
|
|
-- end
|
|
|
--
|
|
|
-- local function handler2()
|
|
|
-- print("hanlder2")
|
|
|
-- end
|
|
|
--
|
|
|
-- s:connect(handler1)
|
|
|
-- s:connect(handler2)
|
|
|
-- s:emit() -- handler2 never gets printed because handler1 always stops the emission
|
|
|
--
|
|
|
function SignalObject:stop()
|
|
|
self.signal_stopped = true
|
|
|
end
|
|
|
|
|
|
-- Signal module exported functions --
|
|
|
|
|
|
---
|
|
|
-- Creates a new SignalObject.
|
|
|
function Signal.new()
|
|
|
local object = {}
|
|
|
-- set the metatable of the new object as the SignalObject_mt table (inherits SignalObject).
|
|
|
setmetatable(object, SignalObject_mt)
|
|
|
|
|
|
-- create all the instance state data.
|
|
|
object.handlers_block = {}
|
|
|
object.handlers = Queue.new()
|
|
|
object.pre_emit_funcs = Queue.new()
|
|
|
object.post_emit_funcs = Queue.new()
|
|
|
object.signal_stopped = false
|
|
|
return object
|
|
|
end
|
|
|
|
|
|
return Signal
|
|
|
|