@Tomarty and @BAUER102 pointed out that bindables, like remotes, will serialize and deserialize tables.
Is there any reason this needs to exist, or was it just made like this to keep some internal OOP inheritance structure (its weird because on the wiki it shows that both remotes and bindables inherit directly from the “Instance” class, which is why I say internal).
It would be most ideal if it would just act as any other function and pass the table as a reference to memory (or whatever the proper terminology is) instead of risk losing data in the process and add inefficiency.
Edit:
I saw this thread: Don't JSONify arguments passed to BindableEvents
But even with einsteink’s point that it might be a security flaw, corescripts that reference bindables which pass tables can use rawget to avoid running bad code etc.
If I am remembering correctly, Bindables were originally implemented as a kind of prototype for Remotes; that’s why they have this behavior (because it’s necessary for RemoteEvents and RemoteFunctions to serialize/deserialize objects).
I think if you need to pass more complicated objects, you should just be using ModuleScripts instead. I don’t think there’s really a situation where BindableFunctions are superior to ModuleScripts. ModuleScripts are less of a replacement for BindableEvents, but the only thing you really need from those is their ability to :Wait() safely.
I think that bindables implement their own custom :Wait() that is more efficient than doing something like
local flag = false
-- one thread
-- do some random stuff: e.g:
wait(5)
flag = true
-- other thread
while not flag do
wait()
end
print("ghetto event fired")
So because of that, bindables would work much better from an efficiency standpoint - and a convenient one as well.
The behavior with serializing can be very misleading for people as well and cause a lot of confusion.
Also past usage/prototyping shouldn’t be something that holds them back from updating (sunken cost fallacy) (im not saying that you are advocating for that - I appreciate the history you provided, but I just want to refute all points against not changing because I feel it is that important)
Personally, I think BindableFunctions don’t really have a place in Roblox now that we have ModuleScripts. Modifying them in a way that is a breaking change seems like really unnecessary work that won’t help anyone. I’m curious if someone actually does find BindableFunctions to be useful, but I’m guessing it’s rare.
I think BindableEvents are also somewhat awkward; ModuleScripts are a better form of encapsulation than an instance sitting in the world. However, right now BindableEvents are the only way to create signals that can be fired directly by scripts. Maybe Roblox should instead add a way to directly create Signals that are not really instances, with Signal.new() having an additional :Fire(args...) method.
The only way I use BindableEvents is to implement waiting; I don’t put the event in the world, and I don’t actually pass values through it; I just use :Fire() to wake up a sleeping :Wait()er that gets its values from an appropriate table. I wonder how else BindableEvents are used?
I agree that ROBLOX should make abstract Signal classes, but I don’t think they should get rid of signals entirely because they are such a common thing and (at least I hope) their implementation is somewhat more efficient than could be done by us using the limitations we have in rbxlua.
Also sometimes I’ll use :Connect() to avoid an infinite require call (if two modules are be dependent on each other in a framework - probably bad practice but OWELL)
I don’t think it’s implemented like mine. I’m sure it uses some internal event system. In fact, a better way to write my Wait method would be to instantiate a BindableEvent object, then fire its Wait method instead. I will probably change my code to do that.
Here’s a possible rewrite (untested):
function Event:Wait()
local be = Instance.new("BindableEvent")
local c
local args = nil
c = self:Connect(function(...)
c:Disconnect()
args = {...}
be:Fire()
end)
be.Event:Wait()
return unpack(args)
end
I put my hands up on this, and let me say that I personally think that BindableFunctions are pretty useful as a way of scripts to communicate with eachother (in my case, within the client):
I have a system that allows the user to change the controls. So here’s how it is done:
We have one main script that is “initialized” by asking the server for the control scheme (said scheme is a table saved on a Data Store)
When some button related to control editing is pressed, that script fires a BindableEvent to an auxilliary script under a different ScreenGui (Due to behaviour with the Active property) - I use the fire and set up the :Wait() approach since it seems the BindableFunction cannot yield or will instantly return nil - strange;
But anyways, that event passes the current scheme; When the user changes the control scheme of a function, the auxiliary script I mentioned above invokes a BindableFunction on the main script so that we can check if the new input value is already assigned to a different game control. This way, the user can have a notion if he is going to overwrite something, and what is that something.
When done, the auxiliary script fires another event the main script is listening to so that it can rewrite the control scheme.
This ends up to create a dynamic semiclosed cycle (as there are some ways out like saving and reloading schemes, but that end up belonging to another “loop” where this one is inside). Since we are using dynamic information, ModuleScripts aren’t the best approach for this case, in my view. I also could pack all the source in a single script but that could hurt the readibility of it. (and having to use long chains of script.Parent.Parent(...) isn’t a good idea either )
Probably using _G will do it as a workaround to the mentioned issue:
-- script 1; will fire the events
local x = {"hi", "mr. noob"}
print(x) --output: 2FDF970C
_G.noob = x
print(_G.noob) --output: 2FDF970C
script.Parent.PassTable:Fire(x)
script.Parent.PassGReference:Fire("noob")
-- script 2; will recieve the signals and print references
script.Parent.PassTable.Event:Connect(function(t)
print(t) -- output when fired: 2FC64C94, different reference
end)
script.Parent.PassGReference.Event:Connect(function(r)
print(_G[r]) -- output when fired: 2FDF970C, same reference
end)
It’s too easy (well, for decent scripters, otherwise just copy them) to write a decent workaround for all this. See all the other replies for that solution.
I’m here again with the CoreScript argument: CoreScripts actually run in another VM, which makes it they can’t just keep the same exact table. If they did, there would still be security issues, and all corescripts using BindableEvents would have to be thoroughly inspected (and it’s more than just “use rawget”)
Why would core scripts stop this? Even if it’s a different environment, can’t you pass a pointer to the memory location and copy from there? There has to be some intermediary script, either core or normal that does this in the first place (I would assume the core script because iirc each connection gets a different copy of the table) so if that’s true can’t Roblox just copy there? (If rawget wouldn’t work, why wouldn’t this?) also why wouldn’t rawget work.
Is there any way to script your own bindable and avoid using Roblox bindable for the :Wait() function (even if you don’t pass arguements through them) while at the same type not doing a while not flag do wait() end loop?
While this is an old thread, I think that a refactor of how bindables work would significantly improve the way my roblox code is structured, especially with the new scripting practices I’ve adopted that relies heavily on Scripts, LocalScripts, and endpoints between them. Currently I am using a packaged module, and abstractions that wrap bindable events/functions. Unfortunately, the exact methods for interfacing endpoints becomes unclear, as you always have to go through the abstraction first.
Even if bindables were re-written to internally wrap fire parameters in a function call that returns the raw object reference (which is what my abstractions do currently), they would be a million times more useful than they are currently.
It would be nice for re-usability and portability’s sake if I didn’t have to go through this abstraction in order for my bindables to function properly.
Example
Current behavior:
local event = Instance.new("BindableEvent")
event.Event:Connect(function(...)
print(...)
end)
local myTable= {}
print(myTable) -- table: 000001F8425EFED0
event:Fire(myTable) -- table: 000001F8425EF930
Expected behavior (usually achieved through some abstraction):
local function createPointer(...)
local args = {...}
return function()
return unpack(args)
end
end
local event = Instance.new("BindableEvent")
event.Event:Connect(function(pointer)
print(pointer())
end)
local myTable = {}
print(myTable) -- table: 000001F8425EE580
event:Fire(createPointer(myTable)) -- table: 000001F8425EE580 (x2)