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 <psychon@znc.in>
This commit is contained in:
Uli Schlachter 2015-06-14 14:21:23 +02:00 committed by Daniel Hahler
parent 38840c911b
commit 089ed0e8dd
2 changed files with 97 additions and 10 deletions

View File

@ -38,7 +38,10 @@ function object:add_signal(name)
check(self) check(self)
assert(type(name) == "string", "name must be a string, got: " .. type(name)) assert(type(name) == "string", "name must be a string, got: " .. type(name))
if not self._signals[name] then if not self._signals[name] then
self._signals[name] = {} self._signals[name] = {
strong = {},
weak = setmetatable({}, { __mode = "k" })
}
end end
end end
@ -48,7 +51,19 @@ end
function object:connect_signal(name, func) function object:connect_signal(name, func)
assert(type(func) == "function", "callback must be a function, got: " .. type(func)) assert(type(func) == "function", "callback must be a function, got: " .. type(func))
local sig = find_signal(self, name, "connect to") 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 end
--- Disonnect to a signal --- Disonnect to a signal
@ -56,7 +71,8 @@ end
-- @param func The callback that should be disconnected -- @param func The callback that should be disconnected
function object:disconnect_signal(name, func) function object:disconnect_signal(name, func)
local sig = find_signal(self, name, "disconnect from") local sig = find_signal(self, name, "disconnect from")
sig[func] = nil sig.weak[func] = nil
sig.strong[func] = nil
end end
--- Emit a signal --- Emit a signal
@ -67,7 +83,10 @@ end
-- that are given to emit_signal() -- that are given to emit_signal()
function object:emit_signal(name, ...) function object:emit_signal(name, ...)
local sig = find_signal(self, name, "emit") 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, ...) func(self, ...)
end end
end end

View File

@ -12,13 +12,19 @@ describe("gears.object", function()
obj:add_signal("signal") obj:add_signal("signal")
end) end)
it("connect non-existent signal", function() it("strong connect non-existent signal", function()
assert.has.errors(function() assert.has.errors(function()
obj:connect_signal("foo", function() end) obj:connect_signal("foo", function() end)
end) 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() assert.has.errors(function()
obj:disconnect_signal("foo", function() end) obj:disconnect_signal("foo", function() end)
end) end)
@ -30,16 +36,27 @@ describe("gears.object", function()
end) end)
end) end)
it("connecting and emitting signal", function() it("strong connecting and emitting signal", function()
local called = false local called = false
obj:connect_signal("signal", function() local function cb()
called = true called = true
end) end
obj:connect_signal("signal", cb)
obj:emit_signal("signal") obj:emit_signal("signal")
assert.is_true(called) assert.is_true(called)
end) 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 called = false
local function cb() local function cb()
called = true called = true
@ -50,13 +67,64 @@ describe("gears.object", function()
assert.is_false(called) assert.is_false(called)
end) 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() it("arguments to signal", function()
obj:connect_signal("signal", function(arg1, arg2) obj:connect_signal("signal", function(arg1, arg2)
assert.is.equal(obj, arg1) assert.is.equal(obj, arg1)
assert.is.same(42, arg2) assert.is.same(42, arg2)
end) 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) obj:emit_signal("signal", 42)
end) 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) end)
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80