From fc997106581c8006bc266b59723c632af44fdbda Mon Sep 17 00:00:00 2001 From: Adrian Perez de Castro Date: Fri, 1 Jul 2016 04:55:16 +0300 Subject: [PATCH] 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. --- matrix/eventable.lua | 76 ++++++++++++++++++++++++++++++++++++++------ test/eventable.lua | 51 ++++++++++++++++++++--------- 2 files changed, 103 insertions(+), 24 deletions(-) diff --git a/matrix/eventable.lua b/matrix/eventable.lua index 9ba3156..e273d95 100644 --- a/matrix/eventable.lua +++ b/matrix/eventable.lua @@ -1,4 +1,3 @@ -#! /usr/bin/env lua -- -- eventable.lua -- Copyright (C) 2016 Adrian Perez @@ -32,11 +31,29 @@ local function _expack (args, ...) return _unpack(r) end -return function (...) - local event_map = {} - local events = {} +local log = (function () + local env_value = os.getenv("MATRIX_EVENTABLE_DEBUG_LOG") + 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 event_map[event] = nil else @@ -48,10 +65,28 @@ return function (...) 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("#", ...) if nargs == 0 then -- Simplest version, no arguments. - function events.fire(event, ...) + fire = function (event, ...) + log("fire: " .. event .. ":", ...) local handlers = event_map[event] if handlers then for i = 1, #handlers do @@ -65,7 +100,8 @@ return function (...) elseif nargs == 1 then -- Common single-argument case: optimized. local arg = _select(1, ...) - function events.fire(event, ...) + fire = function (event, ...) + log("fire: " .. event .. ":", arg, ...) local handlers = event_map[event] if handlers then for i = 1, #handlers do @@ -79,7 +115,8 @@ return function (...) else -- Generic multi-argument case. local args = _pack(...) - function events.fire(event, ...) + fire = function (event, ...) + log("fire: " .. event .. ":", _expack(args, ...)) local handlers = event_map[event] if handlers then for i = 1, #handlers do @@ -92,5 +129,26 @@ return function (...) end end - return events + return fire, hook, unhook 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, +} diff --git a/test/eventable.lua b/test/eventable.lua index 2918548..d4cc09e 100644 --- a/test/eventable.lua +++ b/test/eventable.lua @@ -1,4 +1,3 @@ -#! /usr/bin/env lua -- -- eventable.lua -- Copyright (C) 2016 Adrian Perez @@ -9,20 +8,20 @@ local eventable = require "matrix.eventable" do -- Simple event - local ev = assert(eventable()) + local fire, hook = assert(eventable()) local flag = false - ev.hook("foo", function () flag = true end) - ev.fire("foo") + hook("foo", function () flag = true end) + fire("foo") assert(flag) end 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 - ev.hook("foo", function () flag1 = true end) - ev.hook("foo", function () flag2 = true ; return 42 end) - ev.hook("foo", function () flag3 = true ; return 0 end) - assert(ev.fire("foo") == 42) + hook("foo", function () flag1 = true end) + hook("foo", function () flag2 = true ; return 42 end) + hook("foo", function () flag3 = true ; return 0 end) + assert(fire("foo") == 42) assert(flag1 == true) assert(flag2 == true) assert(flag3 == false) @@ -30,23 +29,45 @@ end do -- Arguments to eventable() are passed to handlers local obj = { answer = 42 } - local ev = assert(eventable(obj)) - ev.hook("foo", function (o) + local fire, hook = assert(eventable(obj)) + hook("foo", function (o) assert(o == obj) assert(o.answer == 42) o.answer = 0 -- Mutate end) - ev.fire("foo") + fire("foo") assert(obj.answer == 0) end do -- Multiple arguments passed to eventable - local ev = assert(eventable(42, "bar", nil, { v=10 })) - ev.hook("foo", function (a, s, n, o) + local fire, hook = assert(eventable(42, "bar", nil, { v=10 })) + hook("foo", function (a, s, n, o) assert(a == 42) assert(s == "bar") assert(n == nil) assert(o.v == 10) 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