SomeSignal - Mutation queue based signal with opt-in priority insertion

Get SomeSignal from creator store
Current release note

SomeSignal, some… mutation queue based signal with opt-in priority insertion
made by a bored person

SomeSignal shares a familarity with RBXScriptSignal and the :Fire() element of BindableEvents, but with additional features.
^ Like majority of signals, this signal is immediate rather than deferred

Works both on old and new type solver (You have to be careful on old solver as self type on signal is any)

Special thanks to @GohanDucis for helping with development of SomeSignal!

Differences to majority of signals

  • While creating signal, you can specify the behavior to be Async or Sync
    ^ By default it’s Async to keep behavior consistent to majority of signals
local signal = require("Path to SomeSignal")("Sync")

signal:Connect(function()
  print("Hello im synchronous, and im gonna wait for 2 seconds")
  task.wait(2)
end)

signal:Fire()
print("Back from waiting")
  • Run order being FIFO (First in First Out) instead of LIFO (Last in First out)
local bindable = Instance.new("BindableEvent")
local signal = require("Path to SomeSignal")()

for _, v in {"a", "b", "c", "d"} do
    local function printValue()
        print(v)
    end
    bindable.Event:Connect(printValue)
    signal:Connect(printValue)
end

print("SOMESIGNAL FIRE")
signal:Fire() -- a, b, c, d

print("BINDABLE FIRE")
bindable:Fire() -- d, c, b, a
  • Mutation queue which prevents undesired effects when trying to alter connection / signal state mid-fire, you can opt out by using methods with -Now suffix (provided that you didn’t call DiscardAllMutations before that)

  • Fires are queued when signal is firing, prevents having processed mutations from other fire altering the original fires mutations
    ^ But because it yields (because it needs to queue), there’s FireAsync method
    ^^ There’s FireNow incase you need to fire without it being queued, but runs on seperate mutation queue
    ^^^ Incase you do not need queued fires anymore, you can call DiscardQueuedFires method

  • When disconnecting Wait connection, thread gets resumes with no parameters

local signal = require("Path to SomeSignal")()

task.defer(signal.Fire, signal, "ran")
signal:Connect(function(...)
  print(...)
  signal:DisconnectConnectedNow()
end)

local data = signal:Wait()
print(data) -- nil, previous connection disconnected :Wait() connection
  • Opt-in priority insertion, lets you choose the order of which connections are ran in
    ^ By default it’s NormalInsert (which uses lowest priority in signal, or default priority when it’s the only connection), so is when connecting/reconnecting with -Now suffix
    ^^ Like FIFO run order, this runs from highest priority to lowest priority
local signal = require("Path to SomeSignal")()

signal:Connect(function()
  print("f")
end)

signal:Connect(function()
  print("a")
end, 3)

signal:Connect(function()
  print("b")
end, 4)

signal:Connect(function()
  print("c")
end)

signal:Connect(function()
  print("d")
end, -10)

signal:Connect(function()
  print("e")
end)

signal:Fire() -- b, a, f, c, d, e
  • There is no DisconnectAll, it’s been replaced by DisconnectConnected & DisconnectQueued
    ^ DisconnectConnected disconnects all currently connected connections
    ^^ DisconnectQueued disconnects queued connections which are waiting to be connected (ie. in mutation queue)
3 Likes

A new era of Signals has finally arrived…

If anyone wants a more technical explanation as to why SomeSignal is incredible as well as extra documentation for it, refer to this guide:

lie. The new era of signals is a high perfomance signal library, signals is about perfomance. So i created ZeroSignal for this, with SoA, NULL and swap-remove

how many signals do we need bru😭

14 Likes

Your signal doesn’t even qualify for the most basic certification in my guide (that ALL signals offering the standard syntax pass).


ZeroSignal.lua (6.2 KB)

BindableEvents are literal years behind what other programming languages & engines do, and nearly every Signal-Module is keen to mimic that same outdated technology for some reason. Meanwhile, SomeSignal and NamedSignal are the first and only Signals so far to actually offer something new to the table in event-dispatcher technology that developers on Roblox have been starving for.

Refer to the Signal Certifications & Classes Guide (link above) for an elaboration on exactly why.

2 Likes

because your module doing 1000 :Connect methods in Connect_Safety, also Zero Signal using other syntax, not basic. Im trying to use a syntax closer to simple luau and also I only measure raw tests with os.clock. Maybe your or my module throws exception, because i dont know what else could happen.

My Zero Signal doesn’t meet your standards, Zero Signal is about performance.

My module is not to blame, it works for the following signals flawlessly:

  • SignalPlus
  • SimpleSignal
  • DProSignal
  • FastSignal
  • GoodSignal
  • LemonSignal
  • MadSignal
  • Zignal
  • NamedSignal
  • SomeSignal
    ^ You can even test ALL of them yourself, the links are on my guide.

The fact that your signal can even run the Create & Connect test at all is proof enough that it at least follows most of the same basic syntax offered by ALL of the signals listed above.
But your signal is the only one amongst them that takes more than a millisecond to process any of the tests in the Scheduler Certification.
The test ZeroSignal got stuck on is Connect_Unit, which does create the 1000 connections in a single signal to then Fire all at once. If your signal takes multiple seconds to work at all and indefinitely longer for realistically-heavy workloads, then it’s not just “my standards”, ZeroSignal is just downright too slow to be viable.

No. It creates easily, here, i used lastest version 1.2.1:

local ZeroSignal = require(workspace.ZeroSignal)

local start = os.clock()
for i = 1, 100_000 do
	local Signal = ZeroSignal()
end
print(os.clock() - start)

log:

We are in different leagues and your standards are not suitable for ZeroSignal

Here:

local ZeroSignal = require(workspace.ZeroSignal)

local Signal = ZeroSignal()

local start = os.clock()
for i = 1, 1e5 do
	Signal:Connect(function()
		
	end)
end
print(os.clock() - start)

log:

Now attempt to use :Fire() on the signal with all of those connections

1 Fire??? or 1000 Fires???

Char limit

1 single fire, the unit test verifies that your signal will not skip a single connection even if it’s a realistically large amount (like 1000 connections).

local ZeroSignal = require(workspace.ZeroSignal)

local Signal = ZeroSignal()
local unitCount = 0
for i = 1, 1e5 do
	Signal:Connect(function()
		unitCount += 1
	end)
end

Signal:Fire()

print(unitCount)

All good i didnt see any problem:

One of the signal libraries will dies today! Guess Who!

Nevermind found the problem, your signal doesn’t even support connection asynchronicity (massive rookie mistake on your part).

local ZeroSignal = require(script.Signal.Value)

local Signal = ZeroSignal()
local unitCount = 0
for i = 1, 1e5 do
	Signal:Connect(function()
		unitCount += 1
		task.wait(1)
	end)
end

Signal:Fire()

print(unitCount)

Yours for not having a basic feature that even BindableEvents supports apparently.

No, Its not a Rookie mistake, its a architecture:

So your signal depends on developers to manually do their own async handling & thread recycling, a feature that all other signals offer at little-to-no compromise?
Well now I’ll do a custom build of the certifier to work with your vital-feature-lacking signal to see what it will really grade as if used correctly.

Its not a compromise, its a architecture lose. Sync handlers faster than Async like for ~80x.

Okay, I’ll be glad to see your tests

After removing the tests that require connection asynchronicity, it still fails almost immediately. Stalling indefinitely on the Wait_Safety test (Wait_Timeout is automatically skipped because :Wait() doesn’t have a timeout parameter).

Issues:

  • :Once() doesn’t return the exposed connection
  • :Wait() is indefinitely delayed* when called consecutively.
local ZeroSignal = require(workspace.ZeroSignal)
local signal = ZeroSignal()

local function fn() end

local connectCN = signal:Connect(fn)
print(connectCN) -- {...}

local onceCN = signal:Once(fn)
print(onceCN) -- nil

print("---------------------------------------")

print("Wait - BEFORE 1")
task.delay(.1, function()
	signal:Fire()
end)
signal:Wait()

print("Wait - AFTER 1")

print("Wait - BEFORE 2")

task.delay(.1, function()
	signal:Fire()
end)
signal:Wait()

print("Wait - AFTER 2") -- Never prints