Improvements to matrix.eventable

* Support debug-logging to stderr by defining the MATRIX_EVENTABLE_DEBUG_LOG
  environment variable.
* Support for unhooking handlers from events.
* Simplify the low-level interface: eventable.functions() now returns the
  created functions directly, cutting down on an intermediate table.
* A new eventable.object() allows adding :hook(), :unhook() and :fire()
  methods to any table. Firing events always sends the table itself as first
  event parameter when firing any event.
This commit is contained in:
Adrian Perez de Castro
2016-07-01 04:55:16 +03:00
parent 99fbf8eb28
commit fc99710658
2 changed files with 103 additions and 24 deletions

View File

@@ -1,4 +1,3 @@
#! /usr/bin/env lua
-- --
-- eventable.lua -- eventable.lua
-- Copyright (C) 2016 Adrian Perez <aperez@igalia.com> -- Copyright (C) 2016 Adrian Perez <aperez@igalia.com>
@@ -32,11 +31,29 @@ local function _expack (args, ...)
return _unpack(r) return _unpack(r)
end end
return function (...) local log = (function ()
local event_map = {} local env_value = os.getenv("MATRIX_EVENTABLE_DEBUG_LOG")
local events = {} if env_value and #env_value > 0 and env_value ~= "0" then
local out, _tostring = io.stderr, tostring
return function (...)
out:write("[eventable]")
local args = _pack(...)
for i = 1, args.n do
out:write(" " .. _tostring(args[i]))
end
out:write("\n")
out:flush()
end
else
return function (...) end
end
end)()
function events.hook(event, handler) local function eventable_functions (...)
local event_map = {}
local hook = function (event, handler)
log("hook:", event, handler)
if not handler then if not handler then
event_map[event] = nil event_map[event] = nil
else else
@@ -48,10 +65,28 @@ return function (...)
end end
end end
local unhook = function (event, handler)
log("unhook:", event, handler)
local old_handlers = event_map[event]
if old_handlers then
local handlers = {}
for i = 1, #old_handlers do
local h = old_handlers[i]
if h ~= handler then
handlers[#handlers + 1] = h
end
end
event_map[event] = handlers
end
end
local fire
local nargs = _select("#", ...) local nargs = _select("#", ...)
if nargs == 0 then if nargs == 0 then
-- Simplest version, no arguments. -- Simplest version, no arguments.
function events.fire(event, ...) fire = function (event, ...)
log("fire: " .. event .. ":", ...)
local handlers = event_map[event] local handlers = event_map[event]
if handlers then if handlers then
for i = 1, #handlers do for i = 1, #handlers do
@@ -65,7 +100,8 @@ return function (...)
elseif nargs == 1 then elseif nargs == 1 then
-- Common single-argument case: optimized. -- Common single-argument case: optimized.
local arg = _select(1, ...) local arg = _select(1, ...)
function events.fire(event, ...) fire = function (event, ...)
log("fire: " .. event .. ":", arg, ...)
local handlers = event_map[event] local handlers = event_map[event]
if handlers then if handlers then
for i = 1, #handlers do for i = 1, #handlers do
@@ -79,7 +115,8 @@ return function (...)
else else
-- Generic multi-argument case. -- Generic multi-argument case.
local args = _pack(...) local args = _pack(...)
function events.fire(event, ...) fire = function (event, ...)
log("fire: " .. event .. ":", _expack(args, ...))
local handlers = event_map[event] local handlers = event_map[event]
if handlers then if handlers then
for i = 1, #handlers do for i = 1, #handlers do
@@ -92,5 +129,26 @@ return function (...)
end end
end end
return events return fire, hook, unhook
end end
local function eventable_object(obj)
local fire, hook, unhook = eventable_functions()
function obj:fire(name, ...)
return fire(name, self, ...)
end
function obj:hook(...)
hook(...)
return self -- Allow chaining
end
function obj:unhook(...)
unhook(...)
return self -- Allow chaining
end
return obj
end
return {
functions = eventable_functions,
object = eventable_object,
}

View File

@@ -1,4 +1,3 @@
#! /usr/bin/env lua
-- --
-- eventable.lua -- eventable.lua
-- Copyright (C) 2016 Adrian Perez <aperez@igalia.com> -- Copyright (C) 2016 Adrian Perez <aperez@igalia.com>
@@ -9,20 +8,20 @@
local eventable = require "matrix.eventable" local eventable = require "matrix.eventable"
do -- Simple event do -- Simple event
local ev = assert(eventable()) local fire, hook = assert(eventable())
local flag = false local flag = false
ev.hook("foo", function () flag = true end) hook("foo", function () flag = true end)
ev.fire("foo") fire("foo")
assert(flag) assert(flag)
end end
do -- Stop at first handler that returns some value do -- Stop at first handler that returns some value
local ev = assert(eventable()) local fire, hook = assert(eventable())
local flag1, flag2, flag3 = false, false, false local flag1, flag2, flag3 = false, false, false
ev.hook("foo", function () flag1 = true end) hook("foo", function () flag1 = true end)
ev.hook("foo", function () flag2 = true ; return 42 end) hook("foo", function () flag2 = true ; return 42 end)
ev.hook("foo", function () flag3 = true ; return 0 end) hook("foo", function () flag3 = true ; return 0 end)
assert(ev.fire("foo") == 42) assert(fire("foo") == 42)
assert(flag1 == true) assert(flag1 == true)
assert(flag2 == true) assert(flag2 == true)
assert(flag3 == false) assert(flag3 == false)
@@ -30,23 +29,45 @@ end
do -- Arguments to eventable() are passed to handlers do -- Arguments to eventable() are passed to handlers
local obj = { answer = 42 } local obj = { answer = 42 }
local ev = assert(eventable(obj)) local fire, hook = assert(eventable(obj))
ev.hook("foo", function (o) hook("foo", function (o)
assert(o == obj) assert(o == obj)
assert(o.answer == 42) assert(o.answer == 42)
o.answer = 0 -- Mutate o.answer = 0 -- Mutate
end) end)
ev.fire("foo") fire("foo")
assert(obj.answer == 0) assert(obj.answer == 0)
end end
do -- Multiple arguments passed to eventable do -- Multiple arguments passed to eventable
local ev = assert(eventable(42, "bar", nil, { v=10 })) local fire, hook = assert(eventable(42, "bar", nil, { v=10 }))
ev.hook("foo", function (a, s, n, o) hook("foo", function (a, s, n, o)
assert(a == 42) assert(a == 42)
assert(s == "bar") assert(s == "bar")
assert(n == nil) assert(n == nil)
assert(o.v == 10) assert(o.v == 10)
end) end)
ev.fire("foo") fire("foo")
end
do -- Unhooking should work
local flag1, flag2 = false, false
local h1 = function () flag1 = true end
local h2 = function () flag2 = true end
local fire, hook, unhook = assert(eventable())
hook("foo", h1)
hook("foo", h2)
fire("foo")
assert(flag1 == true)
assert(flag2 == true)
flag1, flag2 = false, false
unhook("foo", h2)
fire("foo")
assert(flag1 == true)
assert(flag2 == false)
flag1, flag2 = false, false
unhook("foo")
end end