#SomeSignal - Mutation queue based signal

1 messages · Page 1 of 1 (latest)

leaden widget
#

Creator Store
Dev Forum

SomeSignal, signal which attempts to fix issues with roblox signals.
With priority connection support allowing you to choose which connections are more important than others (Just remember that it’s like candy, bad for connecting speeds in large amounts)

SomeSignal shares a familarity with RBXScriptSignal and the :Fire() element of BindableEvents, but with additional features.

Main selling point of this signal:

  • Snapshot based dispatch (All connections are ran before applying mutations & resuming reentrant fire after)
  • Synchronous timing (Everything is ran before fire returns)
  • Priority ordering (You can customize which connection should run first, and which are ran last)

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

#

Differences compared to other signals:

  • Disconnect/DisconnectAll comes with a smart behavior by default when signal is firing (unless you specify the mode to be immediate), which defers current connection(s) to preserve snapshot behavior and immediately does nested connection(s)
local cn2 = nil
local cn = signal:Connect(function()
    cn2 = signal:Connect(function()
    end)
    print(cn.Connected) -- true
    print(cn2.Connected) -- true
    signal:DisconnectAll()
    print(cn.Connected) -- true, if it was immediate then it'd be false
    print(cn2.Connected) -- false
end)

signal:Fire()
print(cn.Connected) -- false
print(cn2.Connected) -- false
  • Each connect method has a direct way to set priority (2nd argument after self), or you can change connection priority with :ChangePriority(), this only has smart behavior
local cn = signal:Connect(function()
  print("Foo")
end), 2) -- connection with priority of 2
cn:ChangePriority(4) -- now its 4
cn:ChangePriority() -- now its 1, which is the default
local cn2 = signal:Connect(function()
  print("Bar")
end), 3)

signal:Fire() -- Output: "Foo"; "Bar"
signal:PriorityFire() -- Output: "Bar"; "Foo", which is to say from highest priority to lowest
  • :Wait() method has timeout and connectionPointer parameter, this gives more customization with the wait connection
-- This suffices
local thisConnection = {}:: SomeSignal.Connection<...any>
task.delay(3, function()
    thisConnection:ChangePriority(3)
end)
signal:Wait(12, nil, thisConnection)
  • There's more disconnect methods, :DisconnectNestedConnections(), :DisconnectCurrentConnections() and with ability to choose behavior you want (deferred, immediate) <- or both in :DisconnectNestedConnections() case
local nestedConnection = nil
local currentConnection = nil 
currentConnection = signal:Connect(function()
    nestedConnection = signal:Connect(function()
    end)
    signal:DisconnectCurrentConnections("immediate") -- its deferred by default, but lets play a game
    print(currentConnection.Connected) -- false
    print(nestedConnection.Connected) -- true
    signal:DisconnectNestedConnections("deferred") -- its immediate by default
    print(nestedConnection.Connected) -- true
end)

signal:Fire()
print(currentConnection.Connected) -- false
print(nestedConnection.Connected) -- false
  • :CancelAllMutations() lets you cancel all mutations done in the current Fire (except reentrant fires / immediate disconnects), it can either be deferred which is the default, immediate or both, useful for when you dont want mutations
local nestedConnection = nil
local currentConnection = nil
currentConnection = signal:Connect(function()
  nestedConnection = signal:Connect(function()
    while (true) do
      print("BOO!")
      signal:Fire()
    end
  end)
  currentConnection:ChangePriority(3)
  currentConnection:ChangePriority(54)
  currentConnection:ChangePriority(29)
  signal:DisconnectAll()
  -- Oh man, I wish to not run these mutations...
  signal:CancelAllMutations("immediate")
  signal:Fire()
end)

signal:Fire()
print(currentConnection.Priority) -- 1
print(currentConnection.Connected) -- true
print(nestedConnection.Connected) -- false
  • :SyncFire() and :SyncPriorityFire() lets you dispatch connections the fastest, but at cost of it not being yield safe, doesn’t respect if signal is firing and makes mutations immediate if fired outside of :Fire() (this is very important because changing priority / adding new connection during :SyncPriorityFire() can be devastating)

Special thanks to @silk parrot for helping with development of SomeSignal and releasing a Signal Certifications Guide <- Extra documentation can also be found there

leaden widget
#

It might not be good because it's first ever post, but hella proud

leaden widget
#

oh and I need to add some tism flavor image

leaden widget
#

There might be version 2.4.2.3 of SomeSignal and change will be something in the lines of:

  • Clarified PriorityFire/Fire method comment, additional comment will be in the lines of: it yields the current thread if its a reentrancy fire, as it could leave users confused
    Yeah, I forgot that myself lmao
leaden widget
#

Somesignal 2.4.3

  • Clarified that reentrant fires yield the thread in Fire and FirePriority comment

  • Removed metatables from signal as to keep the style consistent

  • Renamed a function name inside the module as it didn't accurately describe what it did

You can get the updated version from Creator Store

leaden widget
#

I think the next action will be actually releasing this on devforum

leaden widget
#

Hmm, for QoL I should export the Connection type (for :Wait() connectionPointer param)

leaden widget
#

Actually I forgot I even did that LOL (Connection type is already exported)

#

💔

#

I think for Dev forum this posts contence should be sufficient

#

(unless people would need elaboration on other things)

silk parrot
leaden widget
#

Until person comes around which doesn't understand Signals

#

💔

leaden widget
#

I wonder if I should name the title just "SomeSignal" or "SomeSignal - Different approach on roblox signals"

silk parrot
leaden widget
#

clickbaiting people 💔

silk parrot
leaden widget
#

I mean like sure, it is different approach

#

but it leads back to the signal road

leaden widget
#

@silk parrot Actually to not leave the readers blind, roblox docs!

#

(actually ill remove BindableEvents, I wouldn't agree with that fully), is it good now?

leaden widget
#
leaden widget
#

I did not feel like writing documentation for it as it's quite literally familiar to RBXScriptSignal and BindableEvent :Fire()

#

(and gave like very simplified code examples of the new features)

silk parrot
# leaden widget (and gave like very simplified code examples of the new features)

Also include usage of :PriorityFire() & a specification that greater = earlier

local cn = signal:Connect(function()
end), 2) -- connection with priority of 2
cn:ChangePriority(4) -- now its 4
cn:ChangePriority() -- now its 1, which is the default

signal:Fire() -- Doesn't care about priority, runs in oldest -> newest
signal:PriorityFire() -- Cares about priority in greatest -> least, otherwise oldest -> newest if tied
leaden widget
#

yeah I was gonna comment on that part

#

I wonder if this could suffice

local cn = signal:Connect(function()
  print("Foo")
end), 2) -- connection with priority of 2
cn:ChangePriority(4) -- now its 4
cn:ChangePriority() -- now its 1, which is the default
local cn2 = signal:Connect(function()
  print("Bar")
end), 3)

signal:Fire() -- Output: "Foo"; "Bar"
signal:PriorityFire() -- Output: "Bar"; "Foo", which is to say from highest priority to lowest
#

Okay I decided to refresh this post a bit and mostly because I reached 4000 char limit, now it should be okay

#

now it's time to sit back and relax

#

SomeSignal - Different approach on roblox signals

silk parrot
#

Ngl SomeSignal might need better clickbait 💀
How about something like:
SomeSignal | The new top standard
SomeSignal | The best versatile signal on Roblox (PROVEN)

Anything to sell its GENUINE + PROVEABLE utility superiority

leaden widget
#

Honestly id rather if it spread by the word of mouth

#

(Or give it some time)

silk parrot
#

Alrighty, if anybody asks me, I'll glaze the hell out of this (USING NOTHING BUT LITERAL UNIRONIC FACTS)

leaden widget
#

Even if it doesn't blow up, I can sleep knowing that signal wont let me down

#

wow, that was a speedrun

#

(first reply of someone promoting their own signal)

#

Also I think the only thing which defers people is that small label for priority connections (that too much is bad for connecting speeds, like CANDY)

silk parrot
leaden widget
#

because is true thougu, no candy = very fast

silk parrot
#

Even with candy it's more than fast enough 😭

leaden widget
leaden widget
#

for what it additionally provides

#

Whatever, I should not be up for whatever is going to happen in the dev forum this night

silk parrot
silk parrot
#

Mb on the reply-clutter, I can delete mine and hopefully he does the same because the discussion is irrelevant to SomeSignal.
But basically, his "next-gen" signal (ZeroSignal) actually has more problems than SimpleSignal despite having less (removed) features, his connections don't even use threads at all except for Wait so developers need to MANUALLY do thread recycling... 💀

sacred gull
#

w signal

#

doesnt work like it should yet advertises like its the best

faint kraken
silk parrot
faint kraken
#

Roblox Dev community gotta be the most contradicting community out there

leaden widget
#

I like how I already got ratio'd

silk parrot
#

Lowkey just gonna gatekeep this atp, there's no convincing them...

leaden widget
#

I am joking that these are the Yarik apprentices

#

W speed, although don't feed the trolls

silk parrot
# leaden widget W speed, although don't feed the trolls

I love how people are quick to scream out & overuse "bloat!" the second they see a script with more than 200 lines & not even skim through it, ts tells me they've never worked with other programmers on live games with a pre-existing codebase lmao.

Wait until they see what's in Roblox's PlayerModule hierarchy, about half of that is true bloat 😭

leaden widget
#

see less than ideal solution? BLOATTTTTTTTTTTTTTTTT

leaden widget
leaden widget
#

ngl I realized that this sometimes fails on my machine because once past OneYield it just a consistent 0.5 microseconds lmao

#

💔

faint kraken
leaden widget
#

is it going to be 100 lines of code and 6900 lines of comments which has imaginary code logic?

faint kraken
#

yes

charred cliff
#

yippee yippee!!!

#

oh you guys are catching a lot of flak in the comments

silk parrot
# charred cliff oh you guys are catching a lot of flak in the comments

Yeah part of it is just a mob-hate mentality because of the replies near the top lmao.
The other portion of replies are people confused/concerned on if the extra features of SomeSignal are actually useful or not, I'm trying to explain to them why and how it is lol.

TLDR: SomeSignal is designed to be a true all-purpose signal (in general AND most niches) even if half of the features on it won’t frequently or ever be used by most developers, it’s still a very powerful tool for the devs that actually do want them.

leaden widget
#

So if I think about it, It wasn't my proudest moment to upload this on dev forum, but it's already there so... it might aswell stay, who knows, it might help someone

charred cliff
#

his was like the slowest or whatever and yet he still argued about performance or something

#

funny stuff

leaden widget
chrome torrent
#

Imo, signals should not be fast. They are meant to be fired in case of some occasions, not every frame (well, it's rare situation where it should be fired every frame)

#

So making fast signal is weird for me 🫤

leaden widget
#

I don't get the whole signal needs fast speeds thing (I'd only found this reasonable had it been BLATANTLY slow)

#

(because this just depends on the whole if you used metatable or not, have you got additional features than X signal?)

leaden widget
#

😭

#

that's metatable overhead at that point

leaden widget
#

you are quite literally bound by the whole task.spawn function 💔

silk parrot
# leaden widget So if I think about it, It wasn't my proudest moment to upload this on dev forum...

Sorry to hear, even if it's publicly humiliating, just know that the majority of people flaming you have no idea what they're even talking about.
I would personally worry more about the opinions of people that actually matter, those who can actually hold a proper debate and actually have the capacity to understand the various purposes of this signal and be able to point out technical problems/bugs if there are any. Not a single person in that devforum thread have pointed out a single valid technical issue, they're just saying:
"The base API is completely unoriginal!" -> Correct, but what about complete mutation control & the extra features?
"Specialized features can only be used for specialized purposes so they're point less in general!" -> Correct, they just described what an extra/specialized feature is... But that doesn't mean SomeSignal can't be used in general and in specialized purposes all at once...

#

And the ImSoKolako guy is just originally mad about how SomeSignal is advertised lol

leaden widget
#

We are quite literally having beef over ~7 fps with void functions on a machine from ~14 years ago (my cpu is AMD FX 6100) 💔

leaden widget
#

even if some thing sounded deranged

leaden widget
leaden widget
#

to that I say, you got mutation queue, that's what (and if you think Linked List is lame, then you're welcome to try making this with arrays)

#

Albeit, with arrays you could do Fire() which just directly calls the function (that probably has some specialized use)

#

Enough yapping lol 😭

silk parrot
#

All good lol, I'll stop with my yapping too on the devforum (it's pointless to try 🥀)

leaden widget
#

V2.5 update

  • Added SyncFire, SyncPriorityFire, these fires will run the fastest, but won't respect snapshot behavior nor signal firing (So avoiding doing anything that changes priority of connection or adds new connection when doing synchronous priority fire okay?), and if they encounter a yield, the whole function yields.
#

(ill elaborate, if needed)

leaden widget
#

@silk parrot I just realized how touchy priority gets LOL 😭

#

Its only safe to sync fire / disconnect, anything else would probably break smth

silk parrot
leaden widget
#

This will do nothing

silk parrot
#

Mostly to see if any errors would be thrown

leaden widget
#

I made sure that it doesn't

silk parrot
#

nvm then*

leaden widget
#

You can try if you want

#

That's just my assumption

silk parrot
leaden widget
#

V2.5.1 patch

  • fixed a bug with SyncFire, SyncPriorityFire
leaden widget
#

100k connection SyncFire on server (wit a counter)

#

@silk parrot now the hardest part will be on sugarcoating this on dev forum

leaden widget
#

Though, tank you for suggesting this, it will surely increase performance by ten fold

leaden widget
#

I was thinking about it, same conclusion but different

#

Apply mutations currently in the queue via reentrant fire, then run reentrant fire to the end, apply its mutations, and return to og fire

#

But that would fundamentally break something (and wouldn't solve the reentrant fire issue)

leaden widget
leaden widget
#

Maybe it could be possible, just that reentrant fires would be fired in a column (unlike in a row)

leaden widget
#

Holy I just realized something

#

This might need closures

silk parrot
silk parrot
silk parrot
leaden widget
# silk parrot wdym?

I was having a idea, if signal is firing, then it advances to the next connection (to finish its stuff) and then does its control flow

#

OHHH HELL YEA LOL

leaden widget
#

But if I let my mind simmer for a bit, then we are returning to queued fires

leaden widget
leaden widget
leaden widget
silk parrot
leaden widget
#

RAHHHHH

#

@silk parrot actually, idk if itd be good idea to trust SyncFire with it respecting (normal) fire

#

(Because to be fair, if you doing complex mutations, then it shouldn't)

silk parrot
# leaden widget (Because to be fair, if you doing complex mutations, then it shouldn't)

SyncFire shouldn't process deferred mutations at all for that reason (unsafe & difficult to deal with in practice due to deferred mutations being processed too early)

local cn
signal:Once(function()
  cn:Disconnect() -- Layer 2, deferred
  signal:SyncFire(2) -- Processes mutations early before all connections of the og Fire run
end)
cn = signal:Connect(function(fireId: number)
  print("FireId: " .. fireId) -- If SyncFire processes mutations before running connections, this will never print. Otherwise, it'll be printed only through SyncFire instead of Fire, "FireId: 2" instead of "FireId: 1".
end
signal:Fire(1)
leaden widget
#

What I meant mostly was, should SyncFire respect signal firing

leaden widget
silk parrot
# leaden widget What I meant mostly was, should SyncFire respect signal firing

I think it might be fine for as long as SyncFire just doesn't process deferred mutations, using layer 1 reentrancy with either Fire or SyncFire will be fine.

But multiple reentrancy layers is where things might get a bit tricky, here's what (I THINK) a Fire inside of a SyncFire inside of a Fire would behave like:

local cn1 = signal:Connect(function(fireId: number)
  print("FireId: " .. fireId)
end)
local cn2 = signal:Once(function()
  signal:Once(function()
    signal:Fire(3) -- This will be enqueued (yield), will resume after Fire(1) is done
  end
  signal:SyncFire(2) -- This will remain yielded until the reentrant Fire(3) is done
  print("SyncFired!")
end)
signal:Fire(1)
--[[
OUTPUT (if cn1 is before cn2)
FireId: 1
FireId: 2
FireId: 3
SyncFired!

OUTPUT (if cn1 is after cn2)
FireId: 2
FireId: 1
FireId: 3
SyncFired!
]]

^ Although it's weird, I think it makes sense?

leaden widget
#

It shouldn't mess with mutations because there is a firing Signal (though, SyncFire doesn't respect firing signal)

silk parrot
#

It makes sense if you think of it as calling as just a function that calls other functions (connections)

silk parrot
leaden widget
#

This does give a idea though lmao

#

Normal Signal fire over sync fire (idea short lived 💔)

leaden widget
#

Woah I solved the problem

leaden widget
leaden widget
#

I forgot that if once/wait connection gets called, it's force disconnected (something, something I think it was made to make it immune to CancelAllMutation)

#

So Ironically if you check its connected state while signal is currently on that connection, it'd show false

leaden widget
# leaden widget

if you think this is not a valid behavior (or to give explaination in the Once/Wait method), lmk

leaden widget
#

SomeSignal - Mutation queue based signal

#

I think that's a better title tbh

charred cliff
#

guys i... i can't use this..... the queue is fucking me over..........................

#

is there like a manyfire or something along those lines now

#

i haven't updated the module in a bit

silk parrot
# charred cliff guys i... i can't use this..... the queue is fucking me over.......................

You gotta use the immediate mode for mutations then

Example that uses mutation queue

local cn
signal:Connect(function()
  cn:Disconnect()
  print(cn.Connected) -- true, deferred false (after all connections run but before Fire returns)
end)
cn = signal:Connect(function()
  print("Hi") -- "Hi"
end)
signal:Fire()
print(cn.Connected) -- false
--[[
OUTPUT
true
"Hi"
false
]]

Example that only does immediate mutations (no queue)

local cn
signal:Connect(function()
  cn:Disconnect("immediate")
  print(cn.Connected) -- false
end)
cn = signal:Connect(function()
  print("Hi")
end)
signal:Fire()
print(cn.Connected) -- false
--[[
OUTPUT
false
false
]]
leaden widget
leaden widget
#

Oh, the SyncFire and SyncPriorityFire...

leaden widget
#

Though tis don't prove anything

charred cliff
#

maybe i should say what i'm doing first

#

basically

#

IAS wrapper that supports toggle, press and hold for bool action types

#

actually wait i'll post this in questions or something to not clutter this forum

silk parrot
# leaden widget if you think this is not a valid behavior (or to give explaination in the Once/W...

I think it is valid because it's more informative & safer

  • More informative because if it's running at all then you know for sure it used to be connected, and now you know it's properly disconnected.
  • It's safer too because of immediate reentrant SyncFire, helperUnlink needs to be before the Once connection's function otherwise it would run the connection again.
    • I don't think it should count as a "mutation" (able to be deferred) anyways because it's an expected (built-in) state change.
vital wasp
#

doesn't this already exist

leaden widget
#

you need to read a bit

vital wasp
#

oh mb

#

srry

leaden widget
#

Lowkey I thought about it, it might be possible to make this via array, but it would need sentinel values + O(n) space

leaden widget
#

V2.5.2

  • Fixed a small oversight with SyncPriorityFire
#

This so unused that I only found out once I got bored to input my code into Claude

#

Obviously, this doesn't excuse the questionable code I wrote for it, cough cough

silk parrot
leaden widget
#

My account is still flagged on github, kinda shame

faint kraken
leaden widget
leaden widget
#

I might port this over to new class syntax when it becomes available

#

(so people don't roaring knight me for not keeping up with trends)

faint kraken
leaden widget
#

I shouldn't have called it new class syntax, my bad

leaden widget
#

If V3 ever comes:

  • there will be a version without priority addon
  • "experimental" array instead of linked list
#

Actually that wouldn't even be V3 "in a sense", something along the lines of V2.6.0 if it's priority addon and V3 if it's not

#

But this all depends if I change method names, which I could