Skip to content

Signal

local Signal = require("@game/ReplicatedStorage/Twill").Signal
local roundEnded = Signal.new<<(winner: Player, score: number) -> ()>>()
roundEnded:Connect(function(winner, score)
announce(winner, score)
end)
roundEnded:Fire(player, 12)

The type argument to Signal.new declares what the signal carries. Fire is checked against it, and listeners are typed without annotation. Luau passes an explicit type argument between double angle brackets.

local hit = Signal.new<<(target: Model, damage: number) -> ()>>()
hit:Fire(dummy, 25)
hit:Fire(dummy) -- refused by the type checker

Left out, the signal carries anything.

Listeners run in the order they connected.

Each one runs inside its own xpcall. A listener that raises is reported with its traceback and stepped over, so the listeners behind it still run.

Two rules govern changes made during a firing, and both match how engine signals behave:

  • A listener connected during a firing does not run in that firing. It runs in the next one.
  • A listener disconnected during a firing does not run, even if it had not been reached yet.

Firing a signal from inside one of its own listeners runs the new firing there and then. At 50 levels of that, Fire refuses with an error naming the cause, rather than running out of stack somewhere less obvious.

Wait parks the calling thread until the signal fires, and hands back what it was fired with.

A timeout is a number of seconds. When it runs out, Wait returns nothing rather than staying parked.

Destroy and DisconnectAll wake every parked thread with nothing. A thread waiting on a signal is never stranded by that signal going away.

[Server] | [Client]

Makes a signal with nothing listening to it yet.

function Signal.new<Called>(): Signal<Called>

Called is a function type describing what the signal carries.

[Server] | [Client]

Makes a signal that fires whenever an engine signal does, passing on its arguments.

function Signal.wrap<Called>(source: RBXScriptSignal): Signal<Called>

Destroying the wrapper disconnects it from the source. Throws when the value is not an RBXScriptSignal.

[Server] | [Client]

Answers whether a value is one of these signals.

function Signal.Is(value: any): boolean

[Server] | [Client]

Calls a function every time the signal fires, until the connection is let go.

function Signal:Connect(callback: Called): Connection

Throws on a destroyed signal, and when there is nothing callable to connect.

[Server] | [Client]

Calls a function on the next firing only, letting the connection go as it runs.

function Signal:Once(callback: Called): Connection

Throws on a destroyed signal, and when there is nothing callable to connect.

[Server] | [Client]

Parks the calling thread until the signal fires, or until it gives up waiting.

function Signal:Wait(timeout: number?): ...any

Returns

...any - What the signal fired with, or nothing when the wait ran out. Yields.

Throws on a destroyed signal, and when the timeout is not a number.

[Server] | [Client]

Calls every listener in the order they connected.

function Signal:Fire(...: any)

Throws on a destroyed signal, and when fired from inside its own listeners 50 levels deep.

[Server] | [Client]

Returns how many listeners the signal would call were it fired now.

function Signal:Count(): number

Useful in tests: a count that grows across rounds is a leak.

[Server] | [Client]

Answers whether nothing at all is listening.

function Signal:IsEmpty(): boolean

[Server] | [Client]

Answers whether the signal has been destroyed and can no longer be used.

function Signal:IsDestroyed(): boolean

[Server] | [Client]

Lets every listener go, and wakes anything waiting with nothing.

function Signal:DisconnectAll()

The signal stays usable.

[Server] | [Client]

Lets everything go and makes the signal unusable.

function Signal:Destroy()

Does nothing the second time.

FieldTypeMeaning
ConnectedbooleanWhether the signal would still call this listener.
SignalSignalThe signal it belongs to.

[Server] | [Client]

Lets the listener go, so the signal stops calling it.

function Connection:Disconnect()

Does nothing the second time.

[Server] | [Client]

Takes a let-go listener back on, at the end of the line.

function Connection:Reconnect()

Reconnecting puts the listener last, not back where it was. Throws when the signal has been destroyed.

LimitValue
Firing from inside its own listeners50 levels