Bindable: An extremely lightweight & fast RBXScriptSignal implementation


Get   Repo

Bindable is an extremely lightweight & fast implementation of RBXScriptSignal.

Why Bindable?

  • Almost fully replicates RBXScriptSignal’s behavior
  • Lightweight & fast
  • Open-source
  • 4X faster than Roblox’s BindableEvent

Usage

Connection Example:

local Bindable = require(game.Path.To.Bindable)
local HelloPlayer = Bindable.new()

HelloPlayer:Connect(function(Player)
    print("Hello, " .. Player.Name .. "!")
end)

game.Players.PlayerAdded:Connect(function(Player)
    HelloPlayer:Fire(Player) -- Hello, Username!
end)

Multi-Connections Example:

local Bindable = require(game.Path.To.Bindable)
local HelloPlayer = Bindable.new()

HelloPlayer:Connect(function(Player)
    print("Hello, " .. Player.Name .. "!")
end)

HelloPlayer:Connect(function(Player)
    print("Hello, " .. Player.DisplayName.. "!")
end)

game.Players.PlayerAdded:Connect(function(Player)
    HelloPlayer:Fire(Player) -- Hello, Username!, Hello, DisplayName!
end)

Once Example:

local Bindable = require(game.Path.To.Bindable)
local HelloPlayer = Bindable.new()

HelloPlayer:Once(function(Player)
    print("Hello, " .. Player.Name .. "!")
end)

game.Players.PlayerAdded:Connect(function(Player)
    HelloPlayer:Fire(Player) -- Hello, Username!
    task.wait(2)
    HelloPlayer:Fire(Player) -- Nothing
end)

Wrap Example:

local Bindable = require(game.Path.To.Bindable)
local Part = workspace.Part
local onNameChanged = Bindable.Wrap(Part:GetPropertyChangedSignal("Name"))

onNameChanged:Connect(function()
    print("Name of Part was changed to: " .. Part.Name)
end)

Part.Name = "Bindable"

If you’ve found any bugs - report them here!

Credits:
stravant’s GoodSignal (Inspired by)

8 Likes

There’s the missing :Wait() too, so if you could add that I’d be thankful.
Going to search for bugs, if any

1 Like

Oh thank you for reminding me, i just forgot to add it lol

v1.1.0

Added

  • Bindable:Wait()

v1.2.0

Changed

  • Bindable is now a Package.

Why would you even need signals?
It’s just restricting yourself for the sake of it
Arrays and coroutine.yield() are all you need
No need for signal dogma nonsense
You don’t need a library to reinvent arrays

If you want spaghetti in your code, you should absolutely use manual arrays and coroutines
However, most developers prefer clean, maintainable architecture, which is why they use BindableEvents or custom RBXScriptSignal implementations :slight_smile:

Furthermore, my library is literally what you just described, but supercharged using buffers for extreme optimization and performance

To be honest, the choice is yours: you can choose modularity and convenience, or you can choose spaghetti code

P.s why would you even need remotes? modules are all you need..

5 Likes

Opposite
BindableEvents is used for cross-VM communication (like Actor instances, except also working cross-sandbox), not your “signals”
Next:
Since when

local stack = {}::{(...:any)->any}

is somehow spaghetti compared to

local signal = require(library)
local event_create = signal.new(10)

If anything, it’s the opposite, and using “libraries” requires a gigantic setup, especially the more libraries you have; the more spaghetti it actually becomes
you are strawmanning the argument as hell here.

No generic library can “use buffers” because each game’s architecture is unique
Next:
Buffers in Luau cannot store function references; you just use it as a buzzword here, don’t you?
Read how to actually use buffers, actually

Can you define what spaghetti code is?
Or do you intentionally keep it opaque to later strawman the argument?
So fatigued of this nonsense already
And where did you even bring modularity balkanization here?

No thanks, I’m not sacrificing my specialized logic for a generalized library that wastes performance and lies to me

Maybe a little harsh, but the normalization of opaque claims went to a point that I have no choice
Don’t interpret it as anything personal to a library as since it applies to all libraries in luau

1 Like

you’ve only provided an example of declaring these techniques, lets see how they are actually used in practice.

because i have a feeling one is going to be a mess compared to the other.

2 Likes

A single table.insert or table.remove is all you need
Im not going to be teaching you how to use arrays here

Well, projects such as ByteNet and Zap think otherwise
How do they use buffers if each game’s architecture is unique?

So this is what you call spaghetti code.

Instead of doing local bindingIndex = table.insert(stack, binding), then table.remove(stack, bindingIndex), and writing a for loop to spawn them, you can just do event_create:Once(binding), can’t you?

Not to mention your manual table.remove will shift the array indices and skip listeners if a callback disconnects itself mid-fire.

3 Likes

Interesting. How can you build a game without using libraries?

Libraries are literally modules. Without them, your game will be an unmaintainable mess of endless separate scripts and local scripts

1 Like

v2.0.0

Removed

  • buffer library

true monolithic game structure: one single script, no modules, forcing your game to be either fully serverside or fully clientside, i am truly ahead of my time by lightyears

Yes
They are incredibly unoptimized middleware that reinvents the wheel too

That’s how you have to implement it anyway
It’s just that with your case, you offer and pretend that it’s not somehow while drowning in middleware runtime tax

Very wrong claim.
Not all module scripts are a library
ModuleScript can be a self-contained logic exposing closures to edit upvalues
Many more examples I can tell
Never have I found a case where I need to share some middleware unless you manually force it in
I always focus on writing self-contained logic when possible

There’s an issue, creating two or more connections to then disconnect at least one or more will force the connection insertion order to be no longer reliable. If this is intentional, you should note it in your Multi-Connections example pls

local Bindable = require(game:GetService("ServerStorage").Signals.Bindable)

local signal = Bindable.new()

local cn1 = signal:Connect(function() end)
local cn2 = signal:Connect(function() end)
cn1:Disconnect()
cn2:Disconnect()

signal:Once(function()
	print("1. Once")
end)
signal:Connect(function()
	print("2. Connect")
end)
task.spawn(function()
	signal:Wait()
	print("3. Wait")
end)

signal:Fire()

image

^ Although this is true for creating connections and firing one connection, it doesn’t apply to all categories. I recommend taking a look at SomeSignal’s or NamedSignal’s code if you would like to shatter the 1μs speed barrier for firing one and many connections that contain yields (Fire_OneYield & Fire_ManyYield)

Bindable-Module Results

SomeSignal Results

NamedSignal 1.2.0 (older version) Results

NamedSignal 2.3.2 (latest version) Results

Signal testing suite if you’re interested:

Aside from the ordering issue, I consider your module a solid option as a BindableEvent replacement :+1:

2 Likes


SomeSignal isn’t the only one :wink:
(sorry for the self insert)

2 Likes

Nah you’re good, your signal is generally the fastest amongst all signals I’ve retested just now :+1:

EDIT: Nvm, the bench was done on an older version of NamedSignal mb*. Still very fast for what it offers :+1:

Old (1.2.0)

New (2.3.2)

The dispatching technique that 2.3.x uses optimizes for multiple connections but is a bit slower with 1 connection, something worth considering.