From 089ed0e8dd63fca4b91a6b2d88801c8d45d6b85d Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sun, 14 Jun 2015 14:21:23 +0200 Subject: [PATCH] gears.object: Add :weak_connect_signal() Connecting to a signal weakly has the same effect as connecting to it strongly, but it allows the garbage collector to disconnect the signal in case nothing else references this function. Signed-off-by: Uli Schlachter --- lib/gears/object.lua | 27 +++++++++++-- spec/gears/object_spec.lua | 80 +++++++++++++++++++++++++++++++++++--- 2 files changed, 97 insertions(+), 10 deletions(-) diff --git a/lib/gears/object.lua b/lib/gears/object.lua index 2fc899f3..7e3d62b2 100644 --- a/lib/gears/object.lua +++ b/lib/gears/object.lua @@ -38,7 +38,10 @@ function object:add_signal(name) check(self) assert(type(name) == "string", "name must be a string, got: " .. type(name)) if not self._signals[name] then - self._signals[name] = {} + self._signals[name] = { + strong = {}, + weak = setmetatable({}, { __mode = "k" }) + } end end @@ -48,7 +51,19 @@ end function object:connect_signal(name, func) assert(type(func) == "function", "callback must be a function, got: " .. type(func)) local sig = find_signal(self, name, "connect to") - sig[func] = func + assert(sig.weak[func] == nil, "Trying to connect a strong callback which is already connected weakly") + sig.strong[func] = true +end + +--- Connect to a signal weakly. This allows the callback function to be garbage +-- collected and automatically disconnects the signal when that happens. +-- @param name The name of the signal +-- @param func The callback to call when the signal is emitted +function object:weak_connect_signal(name, func) + assert(type(func) == "function", "callback must be a function, got: " .. type(func)) + local sig = find_signal(self, name, "connect to") + assert(sig.strong[func] == nil, "Trying to connect a weak callback which is already connected strongly") + sig.weak[func] = true end --- Disonnect to a signal @@ -56,7 +71,8 @@ end -- @param func The callback that should be disconnected function object:disconnect_signal(name, func) local sig = find_signal(self, name, "disconnect from") - sig[func] = nil + sig.weak[func] = nil + sig.strong[func] = nil end --- Emit a signal @@ -67,7 +83,10 @@ end -- that are given to emit_signal() function object:emit_signal(name, ...) local sig = find_signal(self, name, "emit") - for func in pairs(sig) do + for func in pairs(sig.strong) do + func(self, ...) + end + for func in pairs(sig.weak) do func(self, ...) end end diff --git a/spec/gears/object_spec.lua b/spec/gears/object_spec.lua index 45fc6df7..692e9fd1 100644 --- a/spec/gears/object_spec.lua +++ b/spec/gears/object_spec.lua @@ -12,13 +12,19 @@ describe("gears.object", function() obj:add_signal("signal") end) - it("connect non-existent signal", function() + it("strong connect non-existent signal", function() assert.has.errors(function() obj:connect_signal("foo", function() end) end) end) - it("disconnect non-existent signal", function() + it("weak connect non-existent signal", function() + assert.has.errors(function() + obj:weak_connect_signal("foo", function() end) + end) + end) + + it("strong disconnect non-existent signal", function() assert.has.errors(function() obj:disconnect_signal("foo", function() end) end) @@ -30,16 +36,27 @@ describe("gears.object", function() end) end) - it("connecting and emitting signal", function() + it("strong connecting and emitting signal", function() local called = false - obj:connect_signal("signal", function() + local function cb() called = true - end) + end + obj:connect_signal("signal", cb) obj:emit_signal("signal") assert.is_true(called) end) - it("connecting, disconnecting and emitting signal", function() + it("weak connecting and emitting signal", function() + local called = false + local function cb() + called = true + end + obj:weak_connect_signal("signal", cb) + obj:emit_signal("signal") + assert.is_true(called) + end) + + it("strong connecting, disconnecting and emitting signal", function() local called = false local function cb() called = true @@ -50,13 +67,64 @@ describe("gears.object", function() assert.is_false(called) end) + it("weak connecting, disconnecting and emitting signal", function() + local called = false + local function cb() + called = true + end + obj:weak_connect_signal("signal", cb) + obj:disconnect_signal("signal", cb) + obj:emit_signal("signal") + assert.is_false(called) + end) + it("arguments to signal", function() obj:connect_signal("signal", function(arg1, arg2) assert.is.equal(obj, arg1) assert.is.same(42, arg2) end) + obj:weak_connect_signal("signal", function(arg1, arg2) + assert.is.equal(obj, arg1) + assert.is.same(42, arg2) + end) obj:emit_signal("signal", 42) end) + + it("strong non-auto disconnect", function() + local called = false + obj:connect_signal("signal", function() + called = true + end) + collectgarbage("collect") + obj:emit_signal("signal") + assert.is_true(called) + end) + + it("weak auto disconnect", function() + local called = false + obj:weak_connect_signal("signal", function() + called = true + end) + collectgarbage("collect") + obj:emit_signal("signal") + assert.is_false(called) + end) + + it("strong connect after weak connect", function() + local function cb() end + obj:weak_connect_signal("signal", cb) + assert.has.errors(function() + obj:connect_signal("signal", cb) + end) + end) + + it("weak connect after strong connect", function() + local function cb() end + obj:connect_signal("signal", cb) + assert.has.errors(function() + obj:weak_connect_signal("signal", cb) + end) + end) end) -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80