RBXScriptSignal type controls

As a developer, when I use RBXScriptSignal I have no control over its type. This is especially problematic when I’m working with custom events from BindableEvents or RemoteEvents. Consumers of these events have to just know what they return. It would be great if these were templates that let me just input what I want as the parameters.

3 Likes

No, they don’t.


Bindables are sort of a discouraged API to use, because in cases like these it is impossible for them to have their type inferred.

Instead, you should use a callback system, which not only guarantees strong typing but also avoids creating two separate callbacks that may cause race conditions.

-- Module
local PlayerData = {
	OnDataLoaded = nil :: (Player: Player) -> ()?
}

local function LoadData()
	local Callback = PlayerData.OnDataLoaded
	
	if Callback then
		for Index, Player in Players:GetPlayers() do
			Callback(Player)
		end
	end
end

-- Consumer
local function OnDataLoaded(Player: Player)
end

PlayerData.OnDataLoaded = OnDataLoaded -- Properly Checked

As for Remotes, the types they may return are completely deterministic. You should always type every argument as unknown and do some runtime checking (unless its Server → Client, where you can guarantee the types)

1 Like

image
?

1 Like

I was able to use it fine a while back:

image

Just ignore my previous replies

1 Like

Heads up, they’re kind of working against typing for network callbacks (RemoteEvents and RemoteFunctions) with the unknown type:

, i.e., unknown has to be narrowed down and is specifically intended for data sent “over the wire”, and it appears to have been enabled with the new type solver (albeit with a bug where the guaranteed parameter is incorrectly unknown and not Player):

I’ve forked stravant’s GoodSignal implementation to support types. There’s a pretty wide range of benefits for using pure-Lua signals rather than RBXScriptSignals, so if you plan to switch to that, you could use this:

GoodSignalFork.lua (7.7 KB)

Example usage:

local GoodSignal = require(path.to.GoodSignal)

-- create the signal, and the public signal with no :Fire method
local privateSignal = GoodSignal.new()
local publicSignal = privateSignal:GetEventOnlySignal() :: GoodSignal.Signal<number, string>

-- connect to the public signal
publicSignal:Connect(function(a, b) -- intellisense: a is a number, b is a string
    print(a, b)
end)

-- fire the signal from both the private and public objects
privateSignal:Fire(2, "wow")

publicSignal._Signal:Fire(3, "This also works")
1 Like