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.

353 lines
10 KiB
Lua

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

---
-- 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