Skip to main content

GoodSignal

Batched Yield-Safe Signal Implementation

Lua-side duplication of the API of events on Roblox objects.

Signals are needed for to ensure that for local events objects are passed by reference rather than by value where possible, as the BindableEvent objects always pass signal arguments by value, meaning tables will be deep copied. Roblox's deep copy method parses to a non-lua table compatable format.

This class is designed to work both in deferred mode and in regular mode. It follows whatever mode is set.

local signal = Signal.new()

local arg = {}

signal:Connect(function(value)
	assert(arg == value, "Tables are preserved when firing a Signal")
end)

signal:Fire(arg)
info

Why this over a direct BindableEvent? Well, in this case, the signal prevents Roblox from trying to serialize and desialize each table reference fired through the BindableEvent.

This is a Signal class which has effectively identical behavior to a normal RBXScriptSignal, with the only difference being a couple extra stack frames at the bottom of the stack trace when an error is thrown This implementation caches runner coroutines, so the ability to yield in the signal handlers comes at minimal extra cost over a naive signal implementation that either always or never spawns a thread.

Author notes: stravant - July 31st, 2021 - Created the file. Quenty - Auguest 21st, 2023 - Modified to fit Nevermore contract, with Moonwave docs

Show raw api
{
    "functions": [],
    "properties": [],
    "types": [],
    "name": "GoodSignal",
    "desc": "Batched Yield-Safe Signal Implementation\n\nLua-side duplication of the [API of events on Roblox objects](https://create.roblox.com/docs/reference/engine/datatypes/RBXScriptSignal).\n\nSignals are needed for to ensure that for local events objects are passed by\nreference rather than by value where possible, as the BindableEvent objects\nalways pass signal arguments by value, meaning tables will be deep copied.\nRoblox's deep copy method parses to a non-lua table compatable format.\n\nThis class is designed to work both in deferred mode and in regular mode.\nIt follows whatever mode is set.\n\n```lua\nlocal signal = Signal.new()\n\nlocal arg = {}\n\nsignal:Connect(function(value)\n\tassert(arg == value, \"Tables are preserved when firing a Signal\")\nend)\n\nsignal:Fire(arg)\n```\n\n:::info\nWhy this over a direct [BindableEvent]? Well, in this case, the signal\nprevents Roblox from trying to serialize and desialize each table reference\nfired through the BindableEvent.\n:::\n\nThis is a Signal class which has effectively identical behavior to a\nnormal RBXScriptSignal, with the only difference being a couple extra\nstack frames at the bottom of the stack trace when an error is thrown\nThis implementation caches runner coroutines, so the ability to yield in\nthe signal handlers comes at minimal extra cost over a naive signal\nimplementation that either always or never spawns a thread.\n\nAuthor notes:\nstravant - July 31st, 2021 - Created the file.\nQuenty - Auguest 21st, 2023 - Modified to fit Nevermore contract, with Moonwave docs",
    "source": {
        "line": 45,
        "path": "src/signal/src/Shared/GoodSignal.lua"
    }
}