Bindables should not recreate a table within the same VM stack

Currently, BindableEvent and BindableFunction objects will clone the shape of a table, but not inherit its metatable or memory location. This is ok behaviour if we’re sending the table to a different VM context (such as a CoreScript or Plugin), but if I’m operating within the same VM context, I’d expect the table to remain the same, keeping the metatable and memory location.

This creates extremely unusual behaviour when implementing events, and forces developers to use slower custom Signal implementations instead of RBXScriptSignals, since the bindable clones the table.

Repro script

local b = Instance.new("BindableEvent")

local t = {}

b.Event:Connect(function(d)
	print(tostring(d))
end)

print(tostring(t))
b:Fire(t)
from main func - table: 0xc8b41d57eaa68ec6
from bindable - table: 0xc034b9d71d976b66

Expected behavior

The table object to be the same table when listening to a signalled bindable in the same context.

4 Likes

I want to add that the Legacy Chat System looks like it ran into this exact problem because it doesn’t send the objects within its events (so I dont know how it wasn’t fixed internally?), it sends an identifier to the object.

I know that system isn’t being maintained anymore, but I thought it was interesting to add nethertheless.

Hello.

This is not a bug, it’s the intended behavior.

Information on BindableEvent/BindableFunction limitations can be found here: Custom Events and Callbacks | Documentation - Roblox Creator Hub

As a workaround, you can send your values wrapped in a function object (inside a single VM):

local b = Instance.new("BindableEvent")

local function wrap(t) return function() return t end end

local t = {}

b.Event:Connect(function(dwrap)
	local d = dwrap()
	print(tostring(d))
end)

print(tostring(t))
b:Fire(wrap(t))
1 Like

Lua signal implementations are actually faster than BindableEvents.

| FastSignal | GoodSignal | SimpleSignal | RobloxSignal | 
--------------------------------------------------------------------------------
CreateAndFire  
ConnectAndDisconnect  |  0.3μs     |  0.3μs     |  0.4μs       |  1.8μs       | 
FireWithNoConnections |  0.1μs     |  0.0μs     |  0.0μs       |  2.2μs       | 
Fire                  |  0.2μs     |  0.8μs     |  3.8μs       |  3.2μs       | 
FireManyArguments     |  0.2μs     |  0.8μs     |  2.0μs       |  3.5μs       | 
FireManyHandlers      |  0.2μs     |  4.4μs     |  15.2μs      |  6.0μs       | 
FireYieldingHandler   |  N/A       |  5.1μs     |  5.1μs       |  6.1μs       | 
WaitOnEvent           |  3.1μs     |  3.5μs     |  5.1μs       |  5.6μs       |

Statistics are taken from Lua Signal Class Comparison & Optimal `GoodSignal` Class