A simple signal implementation

Xignal

Xignal.rbxm (10.7 KB)


Notice: Signal:Wait() must be called inside a thread/coroutine (e.g. task.spawn). Calling it on the main thread will throw an error upon yielding.

Notice: Signal:Fire() runs synchronously for maximum performance. If a callback yields (task.wait()), it will block the firing thread.

Notice: Always call :Destroy() on wrapped signals (Xignal.wrap()) to disconnect the underlying RBXScriptConnection.


API Reference

Xignal.new<T...>() -> Signal<T...>

Creates a new Signal instance.

Xignal.wrap(rbxSignal: RBXScriptSignal) -> Signal

Wraps a native Roblox RBXScriptSignal. Always call :Destroy() when finished to prevent memory leaks.

Signal:Connect(callback: (T...) -> ()) -> Connection

Connects a callback function that executes every time the signal is fired.

Signal:Once(callback: (T...) -> ()) -> Connection

Connects a callback function that automatically disconnects after firing once.

Signal:Wait() -> T...

Yields the calling thread until the signal is fired, returning the arguments passed to :Fire().

Signal:Fire(...: T...)

Synchronously calls all connected callbacks with the provided arguments.

Signal:DisconnectAll()

Disconnects all active listeners and resumes/closes waiting threads. The signal remains reusable.

Signal:Destroy()

Permanently destroys the signal. Calling :Connect(), :Once(), or :Wait() afterwards will error. :Fire() becomes a safe no-op.

Connection:Disconnect()

Disconnects this connection from its parent Signal.

Why

This is kinda not how signals are meant to work

What is the point of this??? What encourages me to wrap a perfectly okay signal in your module, especially given the fact that your signal module exposes ZERO additional functionality, except the negative of it being written in Luau and not C++?