Skip to main content

Signal

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

Functions

new

Signal.new() → Signal<T>

Constructs a new signal.

isSignal

Signal.isSignal(valueany) → boolean

Returns whether a class is a signal

Destroy

Signal.Destroy() → ()

Alias for [DisconnectAll]

Connect

Signal:Connect(
fn(...T) → ()--

Function handler called when :Fire(...) is called

) → RBXScriptConnection

Connect a new handler to the event. Returns a connection object that can be disconnected.

DisconnectAll

Signal:DisconnectAll() → ()

Disconnects all connected events to the signal.

info

Disconnect all handlers. Since we use a linked list it suffices to clear the reference to the head handler.

Fire

Signal:Fire(
...T--

Variable arguments to pass to handler

) → ()

Fire the event with the given arguments. All handlers will be invoked. Handlers follow

::: info Signal:Fire(...) is implemented by running the handler functions on the coRunnerThread, and any time the resulting thread yielded without returning to us, that means that it yielded to the Roblox scheduler and has been taken over by Roblox scheduling, meaning we have to make a new coroutine runner. :::

Wait

This is a yielding function. When called, it will pause the Lua thread that called the function until a result is ready to be returned, without interrupting other scripts. Yields
Signal:Wait() → T

Wait for fire to be called, and return the arguments it was given.

::: info Signal:Wait() is implemented in terms of a temporary connection using a Signal:Connect() which disconnects itself. :::

Once

Signal:Once(
fn(...T) → ()--

One-time function handler called when :Fire(...) is called

) → RBXScriptConnection

Connect a new, one-time handler to the event. Returns a connection object that can be disconnected.

::: info -- Implement Signal:Once() in terms of a connection which disconnects -- itself before running the handler. :::

Show raw api
{
    "functions": [
        {
            "name": "new",
            "desc": "Constructs a new signal.",
            "params": [],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "Signal<T>"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 130,
                "path": "src/shared/modules/Signal.lua"
            }
        },
        {
            "name": "isSignal",
            "desc": "Returns whether a class is a signal",
            "params": [
                {
                    "name": "value",
                    "desc": "",
                    "lua_type": "any"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "boolean"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 142,
                "path": "src/shared/modules/Signal.lua"
            }
        },
        {
            "name": "Connect",
            "desc": "Connect a new handler to the event. Returns a connection object that can be disconnected.",
            "params": [
                {
                    "name": "fn",
                    "desc": "Function handler called when `:Fire(...)` is called",
                    "lua_type": "(... T) -> ()"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "RBXScriptConnection"
                }
            ],
            "function_type": "method",
            "source": {
                "line": 153,
                "path": "src/shared/modules/Signal.lua"
            }
        },
        {
            "name": "DisconnectAll",
            "desc": "Disconnects all connected events to the signal.\n\n:::info\nDisconnect all handlers. Since we use a linked list it suffices to clear the\nreference to the head handler.\n:::",
            "params": [],
            "returns": [],
            "function_type": "method",
            "source": {
                "line": 172,
                "path": "src/shared/modules/Signal.lua"
            }
        },
        {
            "name": "Fire",
            "desc": "Fire the event with the given arguments. All handlers will be invoked. Handlers follow\n\n::: info\nSignal:Fire(...) is implemented by running the handler functions on the\ncoRunnerThread, and any time the resulting thread yielded without returning\nto us, that means that it yielded to the Roblox scheduler and has been taken\nover by Roblox scheduling, meaning we have to make a new coroutine runner.\n:::",
            "params": [
                {
                    "name": "...",
                    "desc": "Variable arguments to pass to handler",
                    "lua_type": "T"
                }
            ],
            "returns": [],
            "function_type": "method",
            "source": {
                "line": 188,
                "path": "src/shared/modules/Signal.lua"
            }
        },
        {
            "name": "Wait",
            "desc": "Wait for fire to be called, and return the arguments it was given.\n\n::: info\nSignal:Wait() is implemented in terms of a temporary connection using\na Signal:Connect() which disconnects itself.\n:::",
            "params": [],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "T"
                }
            ],
            "function_type": "method",
            "yields": true,
            "source": {
                "line": 214,
                "path": "src/shared/modules/Signal.lua"
            }
        },
        {
            "name": "Once",
            "desc": "Connect a new, one-time handler to the event. Returns a connection object that can be disconnected.\n\n::: info\n-- Implement Signal:Once() in terms of a connection which disconnects\n-- itself before running the handler.\n:::",
            "params": [
                {
                    "name": "fn",
                    "desc": "One-time function handler called when `:Fire(...)` is called",
                    "lua_type": "(... T) -> ()"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "RBXScriptConnection"
                }
            ],
            "function_type": "method",
            "source": {
                "line": 235,
                "path": "src/shared/modules/Signal.lua"
            }
        },
        {
            "name": "Destroy",
            "desc": "Alias for [DisconnectAll]",
            "params": [],
            "returns": [],
            "function_type": "static",
            "source": {
                "line": 252,
                "path": "src/shared/modules/Signal.lua"
            }
        }
    ],
    "properties": [],
    "types": [],
    "name": "Signal",
    "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/shared/modules/Signal.lua"
    }
}