From 99fbf8eb2852b7c421845cae47400c4ec7816924 Mon Sep 17 00:00:00 2001 From: Adrian Perez de Castro Date: Thu, 30 Jun 2016 00:24:24 +0300 Subject: [PATCH] First pass at an event-based notification facilty The matrix.eventable module/function returns a table with a pair of functions which can be used to connect event handlers to events, and to generate them. Basic usage is as follows: obj = eventable() obj.hook("event-name", print) -- Handle the event with "print" obj.fire("event-name", "Hello, world") -- Prints "Hello, world" This initial implementation has the following limitations: * Event handlers are always invoked in the same order in which they have been connected using the .hook() function. * Individual event handlers cannot be disconnected. Only disconnecting all handlers for en event at once is possible via .hook("event", nil). --- matrix/eventable.lua | 96 ++++++++++++++++++++++++++++++++++++++++++++ test/eventable.lua | 52 ++++++++++++++++++++++++ 2 files changed, 148 insertions(+) create mode 100644 matrix/eventable.lua create mode 100644 test/eventable.lua diff --git a/matrix/eventable.lua b/matrix/eventable.lua new file mode 100644 index 0000000..9ba3156 --- /dev/null +++ b/matrix/eventable.lua @@ -0,0 +1,96 @@ +#! /usr/bin/env lua +-- +-- eventable.lua +-- Copyright (C) 2016 Adrian Perez +-- +-- Distributed under terms of the MIT license. +-- + +local _select, _pack, _unpack = select, table.pack, table.unpack or unpack + +if not _pack then + _pack = function (...) + local n = _select("#", ...) + local r = { n = n } + for i = 1, n do + r[i] = _select(i, ...) + end + return r + end +end + +local function _expack (args, ...) + local r = { n = args.n } + local n = _select("#", ...) + for i = 1, args.n do + r[i] = args[i] + end + for i = 1, n do + r[r.n + i] = _select(i, ...) + end + r.n = r.n + n + return _unpack(r) +end + +return function (...) + local event_map = {} + local events = {} + + function events.hook(event, handler) + if not handler then + event_map[event] = nil + else + if not event_map[event] then + event_map[event] = {} + end + local handlers = event_map[event] + handlers[#handlers + 1] = handler + end + end + + local nargs = _select("#", ...) + if nargs == 0 then + -- Simplest version, no arguments. + function events.fire(event, ...) + local handlers = event_map[event] + if handlers then + for i = 1, #handlers do + local ret = _pack(handlers[i](...)) + if ret.n > 0 then + return _unpack(ret) + end + end + end + end + elseif nargs == 1 then + -- Common single-argument case: optimized. + local arg = _select(1, ...) + function events.fire(event, ...) + local handlers = event_map[event] + if handlers then + for i = 1, #handlers do + local ret = _pack(handlers[i](arg, ...)) + if ret.n > 0 then + return _unpack(ret) + end + end + end + end + else + -- Generic multi-argument case. + local args = _pack(...) + function events.fire(event, ...) + local handlers = event_map[event] + if handlers then + for i = 1, #handlers do + local ret = _pack(handlers[i](_expack(args, ...))) + if ret.n > 0 then + return _unpack(ret) + end + end + end + end + end + + return events +end diff --git a/test/eventable.lua b/test/eventable.lua new file mode 100644 index 0000000..2918548 --- /dev/null +++ b/test/eventable.lua @@ -0,0 +1,52 @@ +#! /usr/bin/env lua +-- +-- eventable.lua +-- Copyright (C) 2016 Adrian Perez +-- +-- Distributed under terms of the MIT license. +-- + +local eventable = require "matrix.eventable" + +do -- Simple event + local ev = assert(eventable()) + local flag = false + ev.hook("foo", function () flag = true end) + ev.fire("foo") + assert(flag) +end + +do -- Stop at first handler that returns some value + local ev = 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) + assert(flag1 == true) + assert(flag2 == true) + assert(flag3 == false) +end + +do -- Arguments to eventable() are passed to handlers + local obj = { answer = 42 } + local ev = assert(eventable(obj)) + ev.hook("foo", function (o) + assert(o == obj) + assert(o.answer == 42) + o.answer = 0 -- Mutate + end) + ev.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) + assert(a == 42) + assert(s == "bar") + assert(n == nil) + assert(o.v == 10) + end) + ev.fire("foo") +end