#Signal Certifications & Class-Grader
1 messages Β· Page 2 of 1
DisconnectAllMutations touches everything
sure some fields (priority, disconnect [depending on the layer]) might change but at max you will only see that the connections aren't connected anymore
Gotcha π€
New test, it doesn't test mixed yet (I gotta observe what the new handling of Layer 1 mutations for Nested Connections is like)
Your current version should be able to pass as it is
Slipped with my wording I see but I meant future nested connection
it does pass, so it means it's 101% good enough
You good if I have a copy of it? I'm gonna try to make up a few practical scenarios between Nested & non-nested connections to visualize what it'll be like working with it
Though your implementation is at a cost, firing cost to be exact
How expensive?
on my machine I failed every fire test except fire_none
π¨
GAH DAYUM
Forget my "optimization" LMAO
Your thread idea is better for speed, I just wanted to save on memory π (3 Bytes is 3 Bytes π€)
though, was it worth adding 1ms more to firing speeds just to have "correct" behavior when disconnecting
also, +69.6 noice
π€π€π€
this is RIGGED brah
what did you change π€
Gonna increase the margin for them mb
ctrl+c -> ctrl+v π
ah yes, repeat until good
which is to say, you copied connect code to once
Funny how most basic signals are 100 lines yet this almost reaches 600 lines
what have I made
Just more Signal to love dw
although what did you save bytes on
π€
okay so it varies, it might be 1.5 or ~3.5 (watch mobile device get like 50+)
references to anything are usually 4b while booleans are 1b
I might pivot the margin of acceptance speed to be percentage-based off of the baseline rather than fixed values so that the Certifications test can be used no matter how crappy the device is π₯
In the meantime if anybody asks, just put it on my soul that it passes the Speed Cert
we all know if you wanted to save on... memory you should use buffers
Hear me out...
but Signal should now be "finished" "finished" as it does what you ask it for + it no longer yields at everything (unless it's fire, but that also has a fail-safe)
these are my tests on client (I should see if not using threads while doing queue would be faster)
I believe that's where all the big bucks are going to waste
I screwed something up π
yeah okay it did no difference but I know that putting that there was redudant
That should read less than 2k, not 200k :D
there goes like ~500 mb of ram
Here are the TRUE times after the fix... Which only takes +1 MINUTE at 60 fps ;D
guess ill post this and boom this totally could be the last version and it'd last to who knows
I should go to sleep, it's almost 11 pm
Have a good night man π€
I'll be sure to give you 273 extra rewrite suggestions by tomorrow :D
Wtf is happening here π
CHANGELOG
Speed Certifications Change
- Fixed a CRTICAL bug to do with the
Fire_OneYield&Fire_ManyYieldtests. They now have a debounce between each sample so they won't create anymore threads.- Tweaked sample & connection amounts, less bench samples (1k -> 100) but more connections (100 -> 1k). Results are now a little more accurate to practical workloads and only take 10-15 seconds.
Absolute peak is happening :>
you mad man
-3 microseconds from before π€
Also I do have one issue...
what is it about
:Disconnect() called for one connection inside of another connection will be treated as a Layer 1 mutation (immediate) ;-;
now thats the issue with the fact that... WE DONT SEND IT BEFORE FIRING
ngl its better off to just have a signal flag about layer2
than each connection
Sounds good as long as it works with DisconnectAll π₯
it should?
nvm it does, I forgot we moved past how Nested connections should be handled (using DisconnectAllMutations)
in a sense that it happens after fire, but before processing queue
although this a benefit though
we save performance on... not setting booleans 10k times per second
try that approach
ye it should work, the setting boolean thing was for a problem that no longer exists
Now I just realized
this now not gonna work becsuse it'll them all equal π
wait, nvm
The flag should work though?
-- Flag not raised
signal:Connect(fn)
signal:DisconnectAll() -- Flag not raised, immediate
-- Flag raised
signal:Connect(function()
signal:DisconnectAll() -- Flag is raised, deferred
end)
signal:Fire() -- π
but I have a strange deja vu that it will not work
Same lmao, but dw, the problem no longer exists (we don't have to worry about mixed layering anymore, they're explicitly separate now)
Will do once the flag implementation is done π (send tomorrow* pls π)
Here are the tests with our current enlightenment of how it should work
Not today, even if its simple as changing 1 type and then fire function a bit
but ig ill see what I can do tomorrow
okay I realized I could use next and previous values to determine it I found a better idea
Okay I believe I got it right, but I can't really do that with :ChangePriority(), unless I add another value for that
it should be good now, unless there's some magic (let me know what I should fix)
tweaked Fire / PriorityFire to be more safely handled when DisconnectAllMutations is called
WTF KIND OF PERFORMANCE-ENHANCEMENT DRUGS DID YOU GIVE IT LMAO
I find that weird lol, on roblox servers doing once connection is actually slower, but rest is good enough
Okay, the FINAL step is to visualize how to deal with nested connections using this Signal (not to look for problems, but to know what it currently looks like when working with it)
It should be handled by default
No worries about the speed of connection creation, event dispatching (Fire_) is far more important
isInCreationQueue cough cough
Gotcha π₯
that's kinda how I figured out how to make deferred disconnect or instant disconnect
(all roads lead to queue though as then there'd be no guarantee that it'd be in order if I done that INSTANTLY)
also to anwser that question, I found that I made a 2nd thread for no reason
amazing
So pretty much just leaking threads
I was honestly fine with the "good enough" speeds that it had before purely because of what it offered, but to think that it actually was on-par with non-Class 3 Signals speeds this entire time despite having a MORE COMPLICATED STRUCTURE with EXTRA FEATURES is absolutely incredible
ts is the kind of thing I was talking about of anybody even remotely having a chance to surpass your Signal, I am actually certain now it'll remain the best for a long time
I still believe the only thing which can now screw you over is abusing priority connections
(it'll get expensive depending on how much you use)
Ballpark amount?
oh truly give or take 1000, worst case scenario it's ~100 micro seconds per new connect / change priority
not that bad for a few of its use-cases tbh
Even with the slower speed for sorting, I'd be fine with it as long as it's used sparingly or in clusters.
Priority clusters are grouping an entire signal's worth of connections as one priority.
Cluster example using signal wrapping:
local TickSignal_Stepper = signalMD.new()
local TickSignal_Damage = signalMD.new()
local TickSignal_Heal = signalMD.new()
local tickCN_Damage = TickSignal_Stepper:Connect(function()
TickSignal_Damage:Fire()
end, 1) -- Every connection in this Signal is considered as priority == 1 using this connection as a proxy
local tickCN_Heal = TickSignal_Stepper:Connect(function()
TickSignal_Heal:Fire()
end, 2) -- Every connection in this Signal is considered as priority == 2 using this connection as a proxy
TickSignal_Stepper:FirePriority()
Though I think the better change is that having nested connections doesn't yield threads anymore
^ so a free speed-up woo!
oh bet π₯
Though I still wonder what bugs could be lurking, at worst it'd be just that some connections connect after DisconnectAllMutations call because there was reentrancy fire blocking said connections
Gonna test that out π
that's a guaranteed behavior, you can't change that lol...
because if it's OK, then I guess this might aswell be the V2 as it brings lot of improvements lol
There's an issue with DisconnectAll, it'll disconnect Nested connections made after it
Which conflicts with another observation that DisconnectAll doesn't affect existing Nested connections (this part is expected, the one above ^ isn't)
Observations Test Script so far:
I thought we kinda went over this no?
That DisconnectAllMutations should be used for Nested Connections right?
The issue here is that DisconnectAll decides to affect Nested Connections based on ordering (contradictive behavior)
Nested Connection made before -> Unaffected (remains connected even after Fire is complete)
Nested Connection made after -> Disconnected
guess ill see what's up
unless it's simple as setting the queue connections Connected to false
actualy I think ill bench it myself just for "sake" of speed
It now defer-disconnects Nested Connections π
But seeing it disconnect future nested connections in practice, you might need to implement range-clearing instead
interesting that there's a loss of :once connection
It still connects, it just defer-disconnects because of DisconnectAll (so it'll be disconnected by the time the 2nd Nested Fire is called)
range-clearing (mein got so much to SCRIPT....)
No need to thank me π
so flawless code because it out raced
seems like it π
So im taking that as not a bug
unless you really want to wait for each :fire() to finish it's chores (even if it yields)
how would I implement that though,
queue on queue, literally
another disconnectall method called DisconnectAllInThisRange
I usually separate the current connections list from the signal if DisconnectAll is called (so that it's empty for the next connection insertion and it becomes the head), but I gotta look at your script to see if there's something specialized to be done
ah yes, let me just like create a segment
from index 1 to index n and then continue chain off that
oh wait a minute, this should be a "simple" fix
but code logic goes to hell for this
Actually I forgot what I was thinking about
Because im more thinking about, how would the range-clear even work
^ or should I say I have no concept of how it'd work
You could get the queue array length upon DisconnectAll being called here for DISCONNECT_CURRENT
So that by the time it reaches DISCONNECT_CURRENT, you can do i= 1, (cut-off length of queue) instead of the full length of the queue
RAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGH
like my idea is more of
just track what connections were made during firing, if disconnect all was called then store the segment of what it needs to disconnect lol
so a 2nd queue?
not really, it's be a array at that point lol...
with provided "slices"
actually dang it, I probably wouldn't be even be able to track that
you truly gave me a problem
hmmm
nah this shi finna make me cry
Okay I think I got it
although now DisconnectAll doesn't feel like DisconnectAll
(it just range-clears + currently connected ones)
I don't know how stable it is, but it's worth trying either way lol
So apparently that never connected to signal like ever
I bring unfortunate news π₯
Issue 1: Nested connections seem to not connect at all
Issue 2: Reentrant Fires don't run
^ May be a race condition issue, nested connections might be only made after Fires are done (asynchronous)
I feel like this problem is gonna be worse than I imagined
Do you have the prior version saved?
Yeah
Wouldn't be me if I didn't create a copy before I do something which breaks signal functionality
guess they got delayed in their arrival
So have I dug myself a grave? π
Possibly in the current version, gonna check its code
I bothered checking
that fire fires when said connection is not yet ready
oh gotcha
Regarding the current version: #1453290240641859604 message
looking into it, I don't think they're connecting at all considering the NestedConnections test waits 0.1 seconds (eternity to the task scheduler) before it runs the 2nd fire
Seems like whatever I made breaks a lot of things, like the last example just leaves a immortal once connetion π
(im guesisng because signal disconnecting everything in that span)
There's no DisconnectAll or DisconnectAllMutations in the test so I'm unsure what might be causing it if it is being disconnected, I'm leaning towards it possibly not being added in the first place
oh gotcha
okay nvm I can't figured it out for today
All good, you've worked hard π₯
Now my issue is like, do I just run out of frame budget to squeeze in more tasks
or is it more about that I have to wait for it to finish
The scheduler gives you as much budget as you need to the point it's willing to freeze the frame all to preserve guaranteed ordering before it moves onto the next frame π
print(1)
for i = 2, 1e7 - 1 do
task.spawn(function()
print(i)
end)
end
print(1e7)
-- 1
-- 2
-- ...
-- 9999999
-- 10000000 (1e7)
The only way to pass tasks across frames* is if they become asynchronous (yield or wait on something in any way)
task.wait()
workspace.Baseplate:GetPropertyChangedSignal("Name"):Wait()
coroutine.yield()
Race conditions are still possible only if you can't explicitly control the insertion order of tasks, but I believe it's possible here
^ Assuming this is what you're talking about
im more abotu how does cn2 never connect, it technically ran
and it's still provided what it needs
Ill think about it tomorrow (or now if I get enough info about how the whole range-clearing works and then ponder long enough on why cn2 just gives up at last fire)
I wonder if it has to do with queue
Oh wait, I could technically just yield the main thread until all fires are done?
but that feels cheap, because itd be a bandaid solution? (Ill give it a shot tomorrow)
@scarlet pecan Apparently it's about how im dispatching the waiting fire's lol (so I expect this is a issue with the way I schedule it)
fuckass coconut.jpg creepypasta LOL π
I tried older version, it doesn't have this problem
so it's like... waiting for connections in a queue is a "better" choice π
it's like... keeping order of what happened than just letting everything go their way just makes it in... order
but it fails the previous test XD
I can slowly but surely feel your sanity crumble before this challenge π
Gonna make a guide to hopefully help visualize what the end result should look like
though I found something interesting
the cn2 does connect, but it doesn't get called during the last reentrancy fire, which I find strange
guess I gotta sprinkle some tags
Yeah that's true, im kinda feel crushed
I was so successful that I created code so "clever" that by definition I can't debug it
Oh I found the issue
apparently refrence to tail gets nil'ed
while head doesn't
so it's a race condition?
It might be assuming this is what you mean:
- [Action A] tail -> nil
- [Action B] New Nested connection is inserted
- [Action A cont.] head -> nil
^ Therefore, the new Nested connection is removed along with the head, or it does become the head (then overwritten to nil)
Actually ima "select" wher ethe issue is
so once we get to that fire, signals tail is nil, but signals head has cn2
actually hold up, I changed disconectall logic a bit, ima revert it so it's more clear
Have you done?
print(cn2.Connected)
signal:Fire()
print(cn2.Connected)
^ To confirm if it's truly being nil'd or if it's just being disconnected by the :DisconnectAll() before the nested Fire runs (it would self-disconnect by the Fire if it did ran, but it isn't in this case)
You could also print every connection being disconnected during the DisconnectAll loop (I assume that's what you just did in the screenshot)
I tried it
before fire it's true, after fire it's false
so it is being disconnected by DisconnectAll?
Yeah, but it's still in the linked list
I only now realized how buggy it is π
wow wtf I fixed it
All tests??
Signal Certifier & MutationLayering??
I mean that's just old way of DisconnectAll π
still no range-clearing as I dunno how'd that work yet
Probably thanks to the mutation queue you made (ordered by time), so future connections will be ordered after it and won't be insta-disconnected
^ My idea of insta-disconnecting future nested connections within a Fire is dumb as hell in practice lmao, I might've messed up your original logic because of it.
yeah no I think what I just made can be a bandaid fix π
Let's see it π₯
100% intended permanent design trust
Promise would be proud
dang I broke it again
now that both head and tail is nil
I believe it was dumb on my part to not make when Wait/Once connection gets called it disconnects instantly
oh, that works too
This is how the end result is supposed to work right?
isFiring== false- Connections
:Connect():Once():Wait()- Immediate: Inserted to connections list & returned to immediately access properties & methods
Disconnect()- Immediate: Disconnection
DisconnectAll()- Immediate: Disconnection
:ChangePriority()- Immediate: Priority change & sorting
- Connections
isFiring== true- Nested Connections
:Connect():Once():Wait()- Immediate: Returned to immediately access properties & methods
- Deferred/Queued: Insert to the connections list.
:Disconnect()- Layer 1 (nested connection disconnected in the SAME Fire)
- Immediate: Disconnection
- Layer 2 (not nested connection or it's disconnected in a reentrant Fire)
- Deferred/Queued: Disconnection
- Layer 1 (nested connection disconnected in the SAME Fire)
:DisconnectAll()- Deferred/Queued: Disconnect all CURRENT (non-nested & nested) connections at the time it's called
:ChangePriority()- Deferred/Queued: Priority change & sorting
:DisconnectAllMutations()- Deferred: Cancels ALL mutations in the same Fire, includes new Nested Connections (they'll be disconnected). (Note: Reentrant Fires are not mutations, they house mutations)
- Nested Connections
:DisconnectAll() doesn't work like that fully, but rest is accurate
How does DisconnectAllMutations currently work exactly? (or intended to work?)
uhhh, rips all the tubes out and lets yielded fire threads go
Which is to say, I have no idea how you make signals (unless it's simple roblox ones)
I think DisconnectAllMutations should leave reentrant Fires alone since they're more of mutation boundaries (similar to consecutive Fires but synchronous to just 1) instead of mutations themselves.
Mutations just refers to any changes relating to connections
-- Consecutive Fires
signal:Fire()
signal:Fire()
-- Reentrant Fire
signal:Once(function()
signal:Fire()
end)
signal:Fire()
so it's to say, to just leave them be and continue their work?
Yup π
okay, done that
Thx π€
so it's slightly fixed, but it's still as is (at max I only changed fire functions as it got kinda confusing to jungle with it
oh and type errors (mostly there for debugging)
I think this is gg π₯
(everything is as expected & correct)
Gonna do 1 last test, immediate mass disconnection for Layer 1 Nested connections
unless it's the gloomy DisconnectAll (that just sets every connections Connected in queue to false as I dunno how to deal with this for now lol!!!!)
That works as intended dw (it only sets every current connection at the time .Connected to false)
that does give me a idea though...
But there's currently no way to immediately disconnect Nested connections (Layer 1 disconnection of current connections)
If it's fine, I think DisconnectAllMutations should be both immediate & deferred
- Immediate: Disconnects all nested connections currently present in the queue & cancels all current mutations (wipe the queue)
- Deferred: Does the same thing again but for "future mutations" (mutations that happened after
:DisconnectAllMutations()was called within the same Fire)
^ So that there's a way to immediately disconnect all Nested Connections at once (for Layer 1 disconnections)
Can't use :Disconnect() because then Once stops working
yeah it's getting ugly now
So basically :DisconnectAllMutations("deffered" | "immediate")?
Both at the same time, or maybe included as a third option
:DisconnectAllMutations() -> Immediate AND Deferred (does it twice but different timings, it won't repeat anything in the queue because it will be wiped both times)
:DisconnectAllMutations("deferred" | "immediate" | "both")
I went with the latter
Doing final verifications real quick...
π₯
I can die happy now :D
Honestly if I could get range-clearing idea working then pretty much it'd be "set"
because then I could just reject disconnected connections in the queue
I assume as an optimization? Or is this a precursor to another feature you would like to add?
you wanted me to add it (the range-clear DisconnectAll)
just that it worked 10% of the time
Ye as a precursor for :DisconnectAll() being able to differentiate between non-nested & nested connections so they can be deferred (non-nested) & instant (nested)
But with :DisconnectAllMutations("Instant") being a thing, that's no longer needed tbh
but then... disconnectall has a bug
guh!?
this one
oh yeah that's weird, have you checked cn3.Connected? (only cn1.Connected is printed)
It'll say it's disconnected, but in actuality? haha. NO
I could probably print signal._head == cn3 and it'd be true
actually ill just implement a "disconnectall" which just clears queue to a certain range (if oldest calls it, then say bye bye to like majority of the queue LOL)
So this probably still gonna be experimental, but atleast DisconnectAll should work as "intended" (clearing only connections before its call, doesn't account for when older connection calls it)
^ so it also fixes some disconnect all quirks yada yada, adds new one (yada yada, might get fixed)
Everything still seems to work π
I wanted to early return it, but it broke things
so we're just doing more work and then leaving it by the road side
wasting computing time π€
Hold on I spoke too early, :DisconnectAllMutations() affects non-nested connections
(Ignore comments in the script)
Yeah I was wondering if you meant disconnect all mutations should disconnect every connection, guess not lmao π
Just nested ones π
Oh wait one more feature suggestion that just occured to me π
DisconnectAll could have the same arguments as DisconnectAllMutations for extra versatility
:DisconnectAll( "deferred" | "instant" | "both" )
I think that would just screw up shit by a long shot
Later after release is fine π
actually, should disconnectall affect mutations
or should they be like their own seperate buddies
@scarlet pecan guess since you asked for it then im just gonna do it, it won't be pretty though
nvm that's kinda stupid question
ill just assume that you want to disconnect current connections + mutations before the call
dang it, now I have to make another state called both
:DisconnectAll()- All Non-nested Connections
- Current Nested Connections (at the time of being called)
:DisconnectAllMutations(), cancels the following, timing depending on the mode:- Queued
:Connect():Once():Wait()<- Disconnects & cancels the insertion to the connections list - Queued
:Disconnect() - Queued
:ChangePriority() - Queued
:DisconnectAll()
- Queued
Actually nvm about :DisconnectAll() having modes, it would be redundant & against its purpose of being deferred in the first place (while in a Fire)
instead of canceling queued disconnectall, it just overwrites it (then disconnectall can't change it)
It works π
(I haven't checked that one edge-case you have about cn3 persisting tho)
meh that's now intentional with way disconnectall is coded
π€«
I have one... final... change request... π
π
next change request would've been asking me to actually implementing range-clear (:DisconnectAll() working in it's selected connection range), but nice catch
thx for reminding me, allow for it to do my taxes too pls while you're at it :D
there is no AI for it
let me just create a http wrapper to chatGPT and then ask it to be your signal and tax payer
hmmmm I think it might be better offline so a custom-made model better than GPT 5.2 would be okay ig
less than 200 lines would be best ^
Aside from that, I think it might be perfect for release if you would like to
we gotta use buffers, we gonna have 1 bit llm's
people might complain about disconnectall, dunno....
You could implement modes for :DisconnectAll() for the crazy people that still want immediate disconnection in a snapshot-based event dispatcher, otherwise they'll need to simply adapt to a better event structure
I mostly meant range-clearing
you call :DisconnectAll() at the oldest connection and it's gonna rip your roof open
little do they know... that is what Wait/Once does under the hood
MUAHAHHAHAHAHAA
actualy yeah ill just implement range-clearing for disconnectall and it'd be all good I think
-# Little does he know of the 20 hours of debugging for every niche situation he'll have to go through next
I'll make sure you get the best Roblox programmer-themed coffin at your funeral π€
And I fixed that issue (I believe, unless torture testing can break it) [so this means... it's completed! hell yea!]
it fine, for how many features it has it's good enough
actually wait I think I screwed up smth
Contradiction found... π₯²
[7] - Shows that Nested Connections can be deferred-disconnected
[10] - But then it sometimes doesn't affect them at all?
(ignore "-- true" | "-- false" comments)
Added 2 scenarios for [7] to observe, idk if I sent it for [10] & [11] yet
Change the -- true | -- false comments to what you expect the results to be, the file I sent you isn't updated for [7]
I can make a proper tester (including niche situations) once we're satisfied with the observed behaviors/results
Dang it, that bug came back
wait a minute, that shou;dn'tr be a bug
I just now realized what your test does
it calls DIsconnectAll
after fixing, [10] works as intended
"scope-clear" thing
you need to do DisconnectAll in both of them or brutal way is to DisconnectAllMutations on deferred
I wonder if that's overkill or not
@scarlet pecan but yes, fix for [7] is coming it's way
Though a quick question: should disconnectall be as is, or should I seperate that feature as another function called "disconnectPreviousConnections"
Because I took this as the advice β€οΈ
I lowkey don't know anymore π
Asks me for so much features that its fundamentally breaking MaddestCat
price for class 3.141 signal π
If separated, what would be the differences between DisconnectAll and DisconnectPreviousConnections, would the latter only deal with non-nested connections or just all previous connections (including nested ones)?
But honestly, I think there should be as few disconnect methods as possible (ideally sticking with the 3 that we already have, 2 of which are for multiple)
:Disconnect(), :DisconnectAll(), & :DisconnectAllMutations
Some like that
Though, you would need to call that in said nest layer to do that
also thats why we got... 2 disconnect methods
truly
[7] fixed β
[10] now has different behavior (from [7] fix)
ill leave it at that for today, it's 11 pm
I highkey like the current behaviors of this, of Nested Connections disconnecting immediately upon :DisconnectAll() while keeping disconnecting non-nested connections deferred, feels more intuitive & in-line with the orignal mutation-layering concept.
There's always that 1 issue tho π
(cn1 is made before :DisconnectAll() is called but it's completely unaffected)
Not touched in the queue
im guessing you want disconnectall to touch all the mutations too?
erghh cough cough, all current*
All current Nested Connections too π
so abolishing the whole range-clear den
for a simple for loop in queue which kidnaps new connections
under the hood: in the queue
π₯
I wonder how itd look like, but im pretty sure itd cover that too
Like this, but it can catch nested connections made in older connections too π
local cn1 = signal:Connect(fn)
signal:Connect(function() -- [1]
cn2 = signal:Connect(fn) -- Made before :DisconnectAll() is called, caught by it
end)
signal:Connect(function() -- [2]
cn3 = signal:Connect(fn) -- Made before :DisconnectAll() is called, caught by it
signal:DisconnectAll()
print(cn1.Connected) -- true (deferred false)
print(cn2.Connected) -- false (immediate)
print(cn3.Connected) -- false (immediate)
end)
signal:Connect(function() -- [3]
cn4 = signal:Connect(fn) -- Made after :DisconnectAll() is called, so not caught by it
print(cn4.Connected) -- true
end)
signal:Fire()
print(cn1.Connected) -- false (deferred)
print(cn2.Connected) -- false
print(cn3.Connected) -- false
print(cn4.Connected) -- true (NOT CAUGHT BY DISCONNECTALL, BUT CAN BE BY DISCONNECTALLMUTATIONS)
- DisconnectAll
- Deferred: ALL non-nested connections
- Immediate: All CURRENT/PREVIOUS nested connections
^ Respects mutation layering, so it's mostly intuitive to use.
oh gotcha, so still... just basic for loop thru a queue
atleast firing speed improvement π€
so yeah, ill change it tomorrow
Loop through it immediately upon it being called (to disconnect current nested connections)
Then in the mutations processing step (after connections are ran), disconnect all non-nested connections.
- Fire [START]
- Run connections
- Create nested-connection A
- Create nested-connection B
:DisconnectAll()- Immediately disconnect all current/previous nested-connections in queue (A & B)
- Create nested-connection C (unaffected by
:DisconnectAll())
- Process mutations
DisconnectAll()has been called (flag raised), disconnect all non-nested connections, and don't disconnect any further nested-connections made afterDisconnectAll()was called
- Process reentrant Fire(s)
- Fire [END]
All good, have a good one π€
@scarlet pecan okay guess you can put it under stress again
(and I changed some things, ie. some mutations early return if the connection is disconnected)
^ Though, it will not be shattering any speeds as most of the things aren't inlined
Made the observations into a proper test (get rid of the other tests)
Only tests needed:
- Signal Certifications
- Behaviors Test (download here)
One final suggestion (fr this time.)
As a compatibility feature for the developers that want to use this Signal but still rely on forced immediate disconnection (for existing games or out of preference), perhaps forceImmediate (boolean) as an argument could be used in :Disconnect() and :DisconnectAll(). Even if it's an inferior behavior, it still has a few niche uses (less work to do them if this is implemented).
forceImmediate == nil | false, default/dynamic mode (the current behaviors)
local cn1 = signal:Connect(fn)
cn1:Disconnect() -- Immediate
signal:DisconnectAll() -- Immediate
signal:Connect(function()
cn1:Disconnect() -- If Nested & Layer 1? Immediate | Otherwise Deferred
signal:DisconnectAll() -- QUEUE (Nested & Layer 1): Immediate + EXISTING: Deferred
end)
signal:Fire()
forceImmediate == true, forces immediate-disconnection no matter what
local cn1 = signal:Connect(fn)
cn1:Disconnect(true) -- Forced Immediate
signal:DisconnectAll(true) -- Forced Immediate
signal:Connect(function()
cn1:Disconnect(true) -- Forced Immediate
signal:DisconnectAll(true) -- Forced Immediate
end)
signal:Fire()
^ The argument if true basically just skips the isFiring/Nested/Layering check
I feel like this could break some things in signal when abused, but ig ill try
Their problem if it does π
One change that you might need to do is check if a connection is still connected or not before running it in an event-dispatch (no longer safe to assume it is with this feature)
that's... what I did....
So I guess if they want "immediate" disconnectall, then it disconnects all nested + current connections RIGHT NOW
yup π
Applied to :Disconnect() too pls
so some like this
actually I just realized how redundant that disconnect is in not forceImmediate one
better
Now for the final torture test π
Passed so far π
Testing the new feature now
decided to change unneeded disconnect() in code for connection.Connected = false, behavior should be the same
RAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGH
This is fishy
OH RIGHT I REMEMBER
even if it is disconnectall, it keeps links
so I have to create another disconnectall which multilates them
okay so apparently that didn't work
guess we're boolean blocking that with isfiring
unless ill do that from the head side
holy I just realized how much of a flop this is π
okay no, this feature is just UGLY
which is to say: it just refuses what I say and does whatever it wants lmao
O right, I think I need my own logic for this...
yeah okay nvm
with how we do linked list, there's bound to 1 get loose and chain to the disconnected ones
@scarlet pecan
oops
small refinement
apparently the fix was to check if connection was connected before task.spawning it....
Is there any chance of memory leakage? (is it removed if found to be disconnected?)
there shouldn't be
because it unhooks itself from the linked list, but it still lets it bounce to the designated end
(ironically that being the issue why your favorite :Disconnect(true) didn't work LOL)
it literally bounced from that :Once() connection π
so no, it shouldn't cause memory leaks because it'll get GC'ed
I see π₯
With that feature implemented, there's literally nothing else I can think of that I or others would want
I was gonna break down from this feature but then I went
"do the dumb way"
HELL YEA BRANCH PREDICTION W
Glad it worked lmao
so I guess I can now sleep peacefully because me tinkering is done
But it was fun, can't lie
Absolutely generational achievement you've done, I'm proud π₯
I'm gonna update the file on your Signal on the devforum post if that's fine (& include more documentation)
yeah it's fine
even if github support still didn't respond back after initial 2 day wait + response
I think they got me on the ops bro
fr lmao.
I think it might be the inclusion of your username in the license, the moderation AI probably has the sensitivity of a twitter user π₯
they did clarify that it was my username
oh RIP
god forbid if someone mentions that their height is of 6 foot 9 inches
I really got that https://www.youtube.com/watch?v=Fp_KtImYf4c treatment
guess I can now rename SomeSignalTest to SomeSignal in me files
and 2 old backups, truly
It's all grown up now π₯Ή
I guess if you want to mimic ":DisconnectAll()" from roblox signals then
:DisconnectAll(true)
:DisconnectAllMutations("deferred")
- changed
DisconnectAlldescription a bit as it didn't make full sense
Updated π
Let me know what you think about the documentation
Signal Certifications & Classes [1] What are Signals? Itβs a custom module meant to improve-upon and replace BindableEvents as an event-dispatcher, they usually take up less memory and often include additional features for versatility such as :DisconnectAll(). Terminology Clarification Event-dispatcher = The Signal module itself Dispatch = :...
whoops forgot about DisconnectAllMutations
^ Added
@scarlet pecan Uhhh I didn't make it nil-able
unless you want me to do that.... Nvm
actually I can
No worries, I'll fix the documentation
Seems like "immediate" is the default, this makes more sense tbh considering :DisconnectAll() and :DisconnectAllMutations() are both immediate in layer 1.
It's your signal so it's your choice in the end lmao
Should I specify the default as deferred, immediate, or both?
I think this one should be deferred by default (because the name implies, Disconnect all mutations)
Sounds good π€
Updated π
is this zignal 2
Nah nothing will replace Zignal as the best BindableEvents replacement in its generation.
BUT,
SomeSignal is more of an evolution past BindableEvents & previous Signals, for superior versatile, consistent, & backwards compatible functionality and extra features.
Pretty much a long-awaited BindableEvents 2, a new generation and best among all generations.
Feel free to check it out (features & documentation at the bottom of the topic in the Class 3 dropdown section)
https://devforum.roblox.com/t/signal-certifications-classes-guide/4263792
Signal Certifications & Classes [1] What are Signals? Itβs a custom module meant to improve-upon and replace BindableEvents as an event-dispatcher, they usually take up less memory and often include additional features for versatility such as :DisconnectAll(). Terminology Clarification Event-dispatcher = The Signal module itself Dispatch = :...
i wonder if there are signals i can define like packets
similar to blinks networking
that would... come in handy for autocomplete
and it'd be all inlined too for better performance I think
That would be a pretty cool feature ngl, @stuck thorn what do you think?
I dont get it
wait that might actually be the fastest a signal module could be
blink beats out other networking modules in terms of performance because all the events are defined beforehand
because you use a plugin that generates the code
i wonder if signals would benefit in the same way
oh this isn't meant as a networking module, it's more of script -> script communication per environment (server -> server, client -> client)
no i know
but it still uses signals under the hood but those signals are remote events
if you get what i mean
because lets be fair... packet recommends like
return {
thisPacket = Packet("ThisPacket", Packet.String)
}
have you ever used blink?
I'll hop on my pc in a sec and show an example
No
this might be revolutionary
Blazingly fast, DX-oriented, buffer networking. 1.6-3.7x faster than ROBLOX 1000x less bandwidth than ROBLOX GitHub | Documentation | Releases About Blink is a powerful tool that completely changes how you do networking on ROBLOX. Blink has itβs own interface description language which is used to describe the networking events in your ga...
I haven't yet
yes
I believe not π
at best you can just INLINE every signal method so ti doesnt waste time
SHUT UP!!! this will cure cancer
You shush
sorry papa
Checking it out under the hood rn to see if there really are any benefits, on the surface it only seems good for networking (autofill, data serialization/deserialization for less CPU usage & lower bandwidth, and security).
The only thing that might be viable for Signals are autofill and security (so exploiters can't Fire false signals)
you know who else can compete....
making a remote wrapper in which you have to do your own encode/decode
truly
now that would tickle my pickle
this is taking so long
my pc is exploding
the idea is too good
UGH
okay there
these are some example packets
okay fuck it's hard to tell what i mean
basically just the "Data" fields are important
the text editor on the left is the plugin
and the code in the actual editor is the generated code
and now in a normal script i do something like
local Blink = require(path.to.blink.Client)
and i get autocomplete and the types etc.
Basically this?
signal:Connect(function(hasCheese: boolean, hasCrackers: boolean)
end)
signal:Fire() -- ( hasCheese: boolean, hasCrackers: boolean )
Blink.Character_Reset:Fire()
i suppose so but it's a lot more complicated under the hood
I think something similar to Packet's type specification could be done, with custom/static types also being possible.
We can't do Blinker's plugin approach because it does the heavy-lifting of hardcoding it for you (it requires a plugin or running code in command bar to do)
Encoding/decoding through buffers isn't practical for Signals, data compression is only really needed for bandwidth reduction or storage.
blink uses buffers and other absolutely wacky crazy shit
pretty much
here's the entire generated blink file
821 lines of pure goodness
wait what would happen if you used a single bindableevent and put all the fired connections and it's parameters into it once
similar to how blink does it's remote events
instead of a bindable for each event
or making it code based
and having only a single entry and output point without even using bindable events
oh batching
Batching isn't necessary for Signals because there's no network latency
You could create a custom batcher using Signals though if you really wanted
would the lower amount function calls or whatever somehow improve performance though?
so many possibilities
Only on a sub-microsecond level unfortunately, you'll need about 1 million connections batched together in one function for it to make a noticeable difference (of ~10 microseconds)
Engine instructions are surprisingly fast as hell
that answers my question.
jarvis, start batching the signals for me
if someone is mentally ill enough to implement the buffers system etc. then maybe this could even beat out zignal
SomeSignal already beats out Zignal π
Have a good one man π€
night night!
i guess we'll never see a blazingly fast idl compiler written in luau for roblox signals...
I mean there could be in the same way that the Blink Plugin works, but the only usage it'll provide over using the base module is more convenient argument typing...
Argument typing as you suggested is still a good idea though, but it'll probably only be possible in the way that Packet does instead (without a plugin).
Hardcoded security (same way that Blinker does) is also a good idea if it really is a big enough problem that developers face against exploiters. I know network manipulation & fraud through exploits is common, but I don't know how often it happens to BindableEvents/Signals.
types would be nice yeah
@scarlet pecan hmmm, I think there's that 1 mistake I haven't noticed
Decided 1 thing was a "bug": forceImmediate on DisconnectAll not working when DisconnectAllMutations was called before DisconnectAll with forceImmediate set to true
because I think this might have a use case for when you want to stop ALL current connections while at the same time stop all mutations
actually on another note, I should actually make DisconnectAll and Disconnect fit the type of DisconnectAllMutations lol, which is to say ( "default" | "immediate" )? π
Baa okay, I should've probably mentioned in DisconnectAllMutations that it's deferred
My neighbor woke me up with loud-ahh alarms
I've just realised I never tested how the queue system deals with multiple signals (from SomeSignal).
Because of that, you good to add deferred and both as well?
local cn1 = signal_A:Connect(fn)
signal_B:Connect(function()
cn1:ChangePriority(10)
local cn2 = signal_A:Connect(fn)
local cn3 = signal_B:Connect(fn) signal_B:DisconnectAllMutations("both") -- This will disconnect all current & future Nested Connections while also cancelling all mutations, regardless of what signal the mutations were for
signal_B:DisconnectAll("both") -- This will immediately disconnect all current connections and defer-disconnect future nested connections of this signal only.
print(cn2.Connected) -- (DisconnectAllMutations -> false) | (disconnectAll of signal_B -> true)
print(cn3.Connected) -- false
local cn4 = signal_B:Connect(fn) -- future nested connection
print(cn4.Connected) -- true, but deferred false
end)
Typed ts on mobile πͺ
Idk if default should be renamed to smart to prevent devs from assuming one of the other modes might be the default if they haven't read the documentation
Sounds good π
that'd just be ass to code ngl
if you want to mimic the behavior, it wouln't hurt to just call the method on both signals
It's currently not possible to DisconnectAllMutations on signal_B while keeping nested connections of signal_A connected (created in signal_B's Fire)
Even if not now, including deferred & both eventually would be awesome for that π
The queue would only exist in signal_B no? (Of mutations done in sognal_B's fire)
Oh wait
Oh
Nvm I see yhe problem
One solution I see is to set a global flag for the signal module of whichever signal is currently Firing, to then use that Signal's queue for all mutations
^ this is safe in CPU serial threading only (no CPU parallel threading allowed !)
I think I won't be adding that feature
it's gonna be more of a "do it yourself" solution πΆοΈ
All good, I'll give it a try later
anyway, final change
if you want to do that, then you would to manually overwrite _queue with a table of same address, then how queue is dispatched
Nothing about the queue will be changed, it'll just affect if mutations will use their own signal's queue or the queue of a signal currently Firing (this is possible because of Fire queueing)
You said
signal_B:DisconnectAllMutations("both") -- This will disconnect all current & future Nested Connections while also cancelling all mutations, regardless of what signal the mutations were for
I misunderstood at the time
if you wanted it to affect signal_A, both would need to share the same _queue table
OH
Ye to temporarily share signal_B's _queue table
ah yes, custom function of sewSignalsTogether()
you would probably need to change how disconnectallmutations does it (COUGH COUGH) and then queue dispatch in both fire and priorityfire
It's more like this (applied to all signal functions/methods that insert to a queue)
-- All within signal module
local firingSignal: Signal
SignalMeta.Connect = function(self: Signal, fn, priority)
local cn = connectfunction(...) -- Just assume this is the created connection not inserted yet
if firingSignal ~= self then
table.insert(firingSignal._queue, cn) -- I forgot what your insertion looked like, assume this is the cn's insertion function/process being queued
end
end
I'll look into DisconnectAll, I just remembered it raises flags I gotta consider (for future nested connections of its own signal)
Though if I think about it
you just turned DisconnectAll into DisconnectAllMutations mostly
just that it clears the non-nested connections while at it
Precisely
suprised that you now caught on about how about 2+ signals :thinking:
Doubt im gonna find a person who gonna need a super specific latte queue dispatch disconnectall connect wait disconnect in their code π
Me: π₯
what would you need that behavior though
Ye if signals are gonna chain together, one use-case is Firing a signal to shoot a bullet, then a connection in that will automatically Fire the signal for reloading if the mag is empty, possibly creating a new (temporary) connection in the process
Idk what that temp connection would be honestly (I'm not developing a gun system rn), but it would be nice to have it as an option
local cn1 = signal_A:Connect(fn)
signal_B:Connect(function()
cn1:ChangePriority(10)
local cn2 = signal_A:Connect(fn)
local cn3 = signal_B:Connect(fn)
-- check this out
signal_A:DisconnectAllMutations("instantly")
signal_B:DisconnectAllMutations("deferred")
signal_B:DisconnectAll("instantly")
print(cn2.Connected) -- false
print(cn3.Connected) -- false
local cn4 = signal_B:Connect(fn) -- future nested connection
print(cn4.Connected) -- true, but deferred false
end)
I decided to see how I would write it π
seems like the new features are paying OFF π€
So I guess update when you have the time, github support still dry on me π
Wait what if signal_A called for DisconnectAll in signal_B?
^ putting all niche applications aside, this one's common & important
depends on which mode lol
actuall nvm I see my issue
because if signal_A is not firing, then cn2 is instantly connected (so is cn1 being 10 priority)
made this signal, still doesn't know what's going on
this feature is ass π , blocked from suggesting anything ever again
I'm gonna come back to this later in the morning π
I think MaddestCat fell asleep on the wheel
his signal was deferred
Can't believe MaddestCat would task.defer(Signal.Fire, Signal, "1")
mb lmao, working on it now
SomeSignal Bug fixes
:DisconnectAllMutations()'sdeferredandbothmodes forgot to account for nested connection mutations made after it's called (to disconnect them)- Fixed by disconnecting them in the Fire function if
(state == DISCONNECT_MUTATIONS)is true.
- Fixed by disconnecting them in the Fire function if
:DisconnectAllMutations()did not cancel mutations of other signals within the same Fire.- Fixed by implementing
sharedQueuemutation()calls now passconnection._Signal(the signal it was made with) instead ofself(the firing Signal):DisconnectAll()(regarding queued connections) now disconnects queued connections from its own Signal instead of indiscriminately like before.
- Mutations queue didn't clear if
(state == DISCONNECT_MUTATIONS)is true after all connections are ran.- Fixed by clearing the queue table no matter what
- Consecutive reentrant Fire had incorrect timing
- Fixed by moving
isFiring = trueto the start of the Fire functions, andisFiring = falseto before resuming the next Fire.
Example of what the consecutive reentrant Fire timing looked like before the fix:
- Fixed by moving
signal:Connect(function(fireId:number)
print(fireId)
end)
signal:Once(function()
signal:Fire(2) -- 2 (End -> resume nil)
signal:Fire(3) -- [N/A], this won't run in this Fire until after Fire 4.
end)
signal:Fire(1) -- 1 (End -> resume Fire 2)
print("------")
signal:Fire(4) -- 4 (End -> resume Fire 3)
--[[
1
2
-----
4
3
]]
New behaviors test, now includes:
- [11] now also tests for future nested connections (
deferred&both) mode - [17] Multi-Signal mutations handling
- [18] Consecutive reentrant Fire
Before Speed (old version)
New Speed (new version)
^ Almost the same in terms of practicality
@stuck thorn any issues with it?
Ill check a bit later
changes look good to me, other than some coding style inconsistencies
mb lol, feel free to fix π
I also couldn't get the typing for sharedQueue & transferTable quite right, had to remove A... from <A...> to get the typesolver to stop screaming at me π₯
Im still wondering what was wrong about :Fire()
ig you aren't suppose to invoke fire in the same connection twice?
actually it'd be faste rif I just tested it with before fixes signal
okay makes sense π
Here's what it was doing before:
- Fire 1 starts
- Reentrant Fire 2 sees
isFiring == trueand is fire-queued - Fire 1 finishes running all connections & mutation processing, resumes the next Fire in
firingQueue - Fire 2 starts
- Fire 2 sees nothing next in the
firingQueue - Fire 2 ends
- The thread of the 'Once' connection continues after Fire 2 is completed, reentrant Fire 3 sees
isFiring == trueand is fire-queued - Fire 1 ends
- Fire 4 starts
- Fire 4 resumes the next Fire in
firingQueue - Fire 3 starts
- Fire 3 sees nothing next in the
firingQueue - Fire 3 ends
- Fire 4 ends
signal:Connect(function(fireId:number)
print(fireId)
end)
signal:Once(function()
signal:Fire(2) -- 2 (End -> resume nil)
signal:Fire(3) -- [N/A], this won't run in this Fire until after Fire 4.
end)
signal:Fire(1) -- 1 (End -> resume Fire 2)
print("------")
signal:Fire(4) -- 4 (End -> resume Fire 3)
--[[
1
2
-----
4
3
]]
Im guessing now if you frick around with 3 signals, this will get nasty quick lmao
Indeed lmao, but it hopefully should be fixed now
because it supports mangling with other signals
Guess ill clean up the code, then you maybe should put a freeze-dried sign saying "Did grinding off camera" and then once again update the forum
peak work
Thx my man π€
okay I decided to clean up the code a bit + made type error actually not be angry
it should work the same I believe
Hold up I forgot 1 last bug π₯²
Consecutive reentrant signal Firing is fixed when dealing with 1 signal, but it's still broken if this is done with multiple signals
This can be fixed by turning the _firingQueue into a dedicated table just like sharedQueue π
ah yes, put everything on shared tab
are you sure that it won't be headache to deal with later on?
I meant this* π
All you gotta replace is signal.__firingQueue with firingQueue π
ik
Nah since there's only 1 Fire that's being processed at a time (same reason why sharedQueue is possible)
no I mostly mean by like
Without it, Fires will be skipped entirely if they're queued in a different signal
signal_2:Connect(function(fireId: number)
print(fireId) -- This won't print because signal_2's Fire is not in the same _firingQueue as signal_1
end)
signal_1:Connect(function(fireId: number)
print(fireId) -- 1
signal_2:Fire(2) -- Queued to signal_2's _firingQueue
end)
signal_1:Fire(1) -- This'll only check signal_1's _firingQueue for the next Fire
--[[
Output:
1
]]
disconnectAllMutations only affects the current Fire (before the next Fire) π
this only matters if you want it RIGHT NOW or BOTH
yee even with deferred | both it'll still work π (still happens before the next Fire)
You have a robust as hell signal design π₯
oh
okay now I see
Nvm this might be concerning
but on second glance... NO
okay let te be the last change I gotta do
(more code cleaning)
That's why you need to clear the source table after each table.move
It's the fastest possible way to range-clear/range-move
I know since table.clear() makes the table retain the allocated memory
thus, speeding up the future insertions
So I guess it's good now
Range-Move implementation if you're interested:
local transferTable = {}
local ogTable = {}
for i = 1, 100 do
table.insert(ogTable, i)
end
local minMove, maxMove = 20, 58
table.move(ogTable, minMove, maxMove, 1, transferTable)
table.clear(ogTable)
table.move(transferTable, 1, #transferTable, 1, ogTable)
table.clear(transferTable)
print("-------------------------------")
print("Move Length:", (maxMove - minMove) + 1)
print(#ogTable, ogTable, #transferTable, transferTable)
print("-------------------------------")
Range-Clear is the same thing but outside of min & max instead of between (doing it twice)
because it's provided by ROBLOX it must mean it's FASTER than our implentation π₯
SO THIS MEANS... WE CRACKED THE KFC RECIPE??? π€ (made "correct" signal)
INDEED π€
There's literally nothing else to do for it, you've implemented EVERYTHING a developer would EVER need in a Signal.
-# (except custom argument typing for :Fire() but let's have that discussion another day)
what the hell is custom argument typing................
I think it'd be impossible with the types you are given lmao
I was gonna use packet as an example of it being possible but ig the new typesolver broke it lmao (this version of packet is like 6 months old*)
Yeah it might be impossible now.
I don't get it, because on old solver you still need to define what types you want
or Packet.any if it's too complex
Packet("handThemPacket", Packet.F64, Packet.String) unda the hood it's Packet.Packet<number, string>
Just that it does the buffering differently
ermmm it wasn't me who implemented everything, the last part was you idea and I accepted it
I only layed the foundation (+ with you tests help)
actualyl hell naw!!!!
im guessing you're going to have fun rewriting this now since there were ""major"" changes
-# (I think the video might've corrupted π₯)
Will do π
Hmmm, if SignalB is firing and then calls SignalA disconnectall, SignalA will have disconnect flag raised but will not disconnect current connections
oh ofc, if you immediate mode, then it'll work
^But ill fix that via, than isFiring stores which signal it is
- Changed
isFiringtosignalFiringwhich fixes some behavior issues withDisconnectAllandDisconnectAllMutations
Though if you are calling from B method DisconnectAllMutations while it's A which is firing, then DisconnectAllMutations will default to immediate behavior
10 hours later
Finally π
Imagine they take even longer just to respond to your reply...
I mean they are doing that
first was 2 days and then I got this after I replied
5 days later π
so now Signal shouldn't have any "bugs" im aware of
None more I could think of as well π₯
Only way to really know for sure is with more people using it in various real-world situations
Though, when would you use signals for I still have no idea
- General synchronized events between scripts/modules
- Custom Objects (metatable/table objects)
A few examples I can think of:
- Major use-case: Network replication send/receive events for scoped data (similar to
[instance]:GetPropertyChangedSignal()) - Systems that create objects
- NPC system (NPC objects)
- Gun system (gun objects)
- Vehicle system (vehicle objects)
- Interpolation system, basically TweenPlus (interpolation objects)
- Tick/Time stepper (custom time stepping)
- Combat system
- Weather system
- Minigame system
Signals are useful as hell for projects making complex stuff from scratch
np π€
fr lmao
The beauty of a time stepper is that you can organize events in whatever order you want, and can even speed up/slow down time.
Totally useful for TD games
Though, instead of stepping the whole thing, you only make DT bigger, which is ehhhhh
okay until certain speed up
^ which is to say Suphi way of doing this (ticks) does have a sense
Rather than delta-ticks, you can always force each and every tick to run one after another for accuracy π
(As long as network replication is forced into batches so you don't pull a DOS from the server-side lmao)
100_000 times speed, make that server CPU WORK 100% of its worth π€
*Makes roblox electricity bills higher
This Signal good, but once you got 58 signals with 200 connections each, those transfer costs gonna be diabolical LOL
I mean if it ever comes to that, then
if (#transferTable > 0) then
-- table.move(transferTable, 1, #transferTable, 1, sharedQueue)
-- table.clear(transferTable)
sharedQueue, transferTable = transferTable, sharedQueue
end
Okay decided to implement that because seems like a free speed boost later on
Updating the download on the devforum once I get home π₯
Im still suprised that I managed to point that out lmao π
I am indeed the compiler which bytecode was looking for
Yup
This is the latest version
Also check out the devforum for some documentation (class 3 drop-down at the bottom of the post), and feel free to ask us anything anytime π
https://devforum.roblox.com/t/signal-certifications-classes-guide/4263792
Signal Certifications & Classes [1] What are Signals? Itβs a custom module meant to improve-upon and replace BindableEvents as an event-dispatcher, they usually take up less memory and often include additional features for versatility such as :DisconnectAll(). Terminology Clarification Event-dispatcher = The Signal module itself Dispatch = :...
do you guys have a github?
also your response time is incredible
greatly appreciated
Not for the signal yet, the creator of SomeSignal (6inch9inch) is still appealing his ban on github
(he got banned for his username lmao)
π
wait
:DisconnectAllMutations()
it disconnects connections under connections
right?
atleast that's how i understood it from the documentation
and the function comment
It disconnects any signal-related action done inside of a Fire
The following are mutations:
:Connect, :Once, :Wait, :ChangePriority, :Disconnect, and :DisconnectAll
signal:Connect(fn)
signal:DisconnectAll()
signal:DisconnectAllMutations() -- Useless because it's not inside a Fire, does nothing
local cn1 = signal:Connect(function()
local cn2 = signal:Connect(fn)
signal:DisconnectAll() -- Immediately disconnects cn2 because it's a layer 1 disconnection (impossible to cancel), but cn1 is a layer 2 disconnection so it's deferred (after all connections are ran)
signal:DisconnectAllMutations() -- Inside a Fire, disconnects cn2 (if it wasn't already) & cancels DisconnectAll so cn1 remains connected.
end)
oh so if i did DisconnectAllMutations it would leave cn1?
but if i did DisconnectAll it would disconnect cn1 too?
Yup
oooooooooooooh
okay okay
i know mutations is probably the best name for it generally but
what if it was :DisconnectAllNested instead?
i like this function description
this one isn't quite as obvious though
i might just be a little dense though
It cancels stuff other than nested connections too, you're right that it's misleading though.
How about CancelAllMutations?
Brb srry
no worries!
i had a problem understanding the "mutations" part more than the disconnect part
even if the function description were updated by itself that would be a lot more clear
"Disconnects all nested connections and cancels any queued connections, deferred by default, leaves the main signal intact"
idk something like that
also happy birthday @stuck thorn
if it is your birthday today...
this is probably too simple to explain all the things it does under the hood though, definitely needs improvements
wait actually the current description might be fine...
i'm just... stupid...
All good, it's probably a bit tricky for most people to intuitively wrap their heads around this the first time. It took 6inch9inch & I several days to independently think of some of the concepts & determine that they're the best fit for Roblox development so however long you'll take, it'll be far faster than my understanding time lol.
If there's demand, I can make a suphi-kaner style video of how to use SomeSignal, explaining some of the concepts behind it, its features, several examples of it in practice, and usage comparisons between SomeSignal and other Signals/BindableEvents (to show exactly why it's superior for versatility & convenience)
And then you going to hit Suphi Kaner bugs
tank you
Happy cake day π₯π₯π₯
Thank you, but you know what portal teaches about the cake....
(which is to say I didn't have cake)
Good to see someone else other than me and maddestcat trying to use this signal
It could get better wording, but I still think it's good enough
Because I wouldn't really say it Disconnects per say, it just cancels all queued mutations
Actuall yeah you have a point
Any of these good?
:CancelAllMutations() -> Might be the most accurate because it also disconnects nested connections (the other ones only imply deferred mutations only)
:CancelDeferredMutations()
:CancelQueuedMutations()
CancelAllMutations("deferred" | "immediate" | "both")
truly
So I guess it's now called CancelAllMutations as it does exactly that
and it also has a more ""carefully"" worded comment
Feeling bad for the guy who wants to DisconnectAll without killing his nested connections
You could add a mode for that (and another just for nested connections without cancelling other mutations)
layer1&layer2? -> May require documentation understanding of what a "layer" is but it'll be accurate
surely people will understand 3 disconnectall methods
though should I give people option to defer disconnect nested connections
lol
True π₯
Could there be a 4th while we're at it? (For nested connections only without cancelling mutations)
I am doing that
hmmm, should I make it that if you call for immediate, it just ignores the CancelAllMutations flag
This?
signal:CancelAllMutations("both")
signal:DisconnectAll("immediate") -- Ignores Cancel flag
It would be consistent with this if you did (current behavior)
signal:DisconnectAll("immediate") -- Impossible to cancel afterwards
signal:CancelAllMutations("both")
and we got....
signal:CancelAllMutations("immediate")
signal:DisconnectAll("immediate") -- Ignores Cancel flag
so im just gonna fix that real quick....
Thx π₯
hmmm I see another optimization I can do...
@scarlet pecan Apparently you did a transferTable which got... canceled right after
Hmm should I make it that if you call CancelAllMutation it can't call itself until the previous CancelAllMutation is done
truly best idea
Actually nvm, that I shouldn't do
Woah a DisconnectAll update
Though, if you do DisconnectNestedConnections on deferred and then DisconnectCurrentConnections then DisconnectCurrentConnections will be the disconnectAll mode
I could... make it so callign them both causes a synergy
but that's DisconnectAll in disguise
Giving the new methods a try in a few minutes + will update the devforum download & documentation
the suphi kaner style video WOULD be cool but it shouldn't be a high priority thing
also each newer version that gets sent here isn't just like showcase code right?
this one for example
should i replace my existing version with this one?
Complete SomeSignal versions that can be used π
You can if you want, I'm going to test it first to check if there's any bugs & to determine how practical the new update feels (2 more disconnect methods)
Gonna do it now
Mb on the delay
Approved π
cn1 -> Current connection (created outside of Fire)
cn2 -> Nested connection created before disconnection is called
cn3 -> Nested connection created after disconnection is called
Ill probably add better comments
because disconnect nested connections will be immediate if the signal which called it is NOT the signal firing
- probably make it so calling disconnect nested connections and current connections on deferred makes it do both
as a cool old "DisconnectAll" which everyone remembers
Considering that the "immediate" mode for DisconnectAll now bypasses :CancelAllMutations() for the sake of consistency, should the same be applied to all other mutations under the same reasoning of more consistency?
cn1:Disconnect("immediate") -- Bypass?
signal:DisconnectCurrentConnections("immediate") -- Bypass?
signal:DisconnectNestedConnections("immediate") -- Bypass?
^ And in that case, should there be a new :CancelDeferredMutations() method for an extra layer of versatility?
Similar to why DisconnectCurrentMutations & DisconnectNestedConnections now exists
signal:CancelDeferredMutations() -- Deferred mutations will be cancelled, but "immediate" mode mutations will be able to bypass this
signal:CancellAllMutations() -- ALL mutations will be cancelled no matter what
^ It'll require implementing one extra flag that mutations will need to check.
local canBypassMutationCancel = true -- Remains true until :CancelAllMutations() is called, of if new "immediate" mutations can bypass the cancel.
I patched that out
sorry not sorry
wdym?
immediate DisconnectAll no longer bypasses CancelAllMutations
Makes sense π€
What do you think of the :CancelDeferredMutations() idea though?
Extra complexity
Precisely π€
which is to say, ill stick to this and itll might be the "final" update
as it got the things youd want
Also this project complex enough trust
~700 lines signal
while Zignal fits in ~100
Completely different weight classes dw π₯
I waged the options
I dont think its possible unless I bend the rules a bit, but then you a bit of a picke
we need... another boolean flag
Actually I could... but ill think about it
All good either way π
I assume the issue looks like this?
signal_1:Connect(function()
local cn = signal_2:Connect(fn)
signal_2:CancelAllMutations("deferred") -- immediate anyways?
signal_2:DisconnectNestedConnections("deferred") -- immediate anyways?
print(cn.Connected) -- false (supposed to remain true and deferred false)
end)
signal_1:Fire()
no worries and thank you!
yeah
but I could just add another if statement in the disconnect function (helper one) and boom
problem solved
Added as test 25
Tank you π
finally done that DisconnectNestedConnections on deferred and DisconnectCurrentConnections join together to disconnect everything
Actaully I should change how CancelAllMutations work, no matter from which signal you call it, it affects the firingSignal
should've also mentioned it works with disconnectall
(because under the hood disconnectall does a DISCONNECT_CURRENT)
notes should be taken that this only works on firing signal (it will have differnet behavior on a non firing signal)
could you make this a package? so it automatically updates?
Hmmm, actually yeah I can lol
nvm I see why nobody can't even do packages lol
guess it's gonna be... YOU job to make that package YOUSELF
Gonna explore that extensively π
so you instead get... https://create.roblox.com/store/asset/121749520423997/SomeSignal
update it once when you feel bored to see what it might break
Almost first try RAAAAAAAAAAAAAAAAAAAAAAGH
Is this a different version from the file you sent here?
Fixed the comment in the screenshot*
its the latest one
Its only like that because how am I suppose to know which one truly wants it deferred
π£οΈ
Use a table for the flag that β¨β¨β¨β¨β¨β¨β¨β¨β¨:DisconnectNestedConnections()β©β©β©β©β©β©β©β©β© uses, to store each signal as an index with a β¨β¨β¨β¨β¨β¨β¨β¨β¨true | nilβ©β©β©β©β©β©β©β©β© value if β¨β¨β¨β¨[signal]:DisconnectNestedConnections("deferred")β©β©β©β© is called.
^ BUT make it a new flag if it'll break the behavior of other existing signal methods
β¨β¨β¨β¨β¨β¨β¨β¨```lua
local disconnectNestedQueue = {}
disconnectNestedQueue[signal] = true
Well then, another "queue" it is
that's what I thought lmao π
just that im gonna do it differently
Sounds good π₯
so this gonna evaporate the whole _disconnectallstate on signals
amazing
Though a design choice now
should I make it so calling CancelAllMutations after CancelAllMutations just doesn't happen
Nope unless the mode is β¨β¨β¨β¨β¨β¨"deferred"β©β©β©β©β©β© or β¨β¨β¨β¨β¨β¨"both"β©β©β©β©β© on the first time it's calledβ©, but even then I think it's better to leave it for the sake of debuggability.
^ If you do plan on adding it, include a β¨β¨β¨β¨warn()β©β©β©β© that CancelAllMutations is already deferred pls.
β¨β¨β¨β¨β¨β¨```lua
signal:Connect(function()
cn1 = signal:Connect(fn)
signal:CancelAllMutations("immediate")
print(cn1.Connected) -- false
cn2 = signal:Connect(fn)
signal:CancelAllMutations("immediate")
print(cn2.Connected) -- false
end)
signal:Connect(function()
cn1 = signal:Connect(fn)
signal:CancelAllMutations("both")
print(cn1.Connected) -- false
cn2 = signal:Connect(fn)
print(cn2.Connected) -- true (deferred false)
signal:CancelAllMutations("immediate") -- If warn() is added: "CancelAllMutations is already deferred, no need for an immediate cancel afterwards."
print(cn2.Connected) -- false
end)
guess im not doing that
I just realized I never properly tested what would happen if β¨CancelAllMutationsβ© is called by non-firing signals, doing that real quick
I changed that real quickly
rahhhh
You should try te new version out
wsp?
Im calling this after I set cancel queue
so im effectively canceling the cancel queue
woah simple fix
π€
What does this mean exactly btw?
This?
β¨β¨```lua
signal:Connect(function()
cn1 = signal:Connect(fn)
signal:CancelAllMutations("deferred")
signal:CancelAllMutations("immediate") -- Cancels the one above & no mutations are cancelled?
print(cn1.Connected) -- true?
end)
Or is that just cleanup after the Fire is finished?
signal:CancelAllMutations("both") -- cancels itself
I think I'm even more confused now π
actualy wrong example ven
So in both mode it firstly set itself on CANCEL_QUEUE but then moments later clears the sharedDisconnectAllStates table, thus canceling itself out
So β¨bothβ© mode is now effectively just β¨immediateβ©? Because it'll insert the β¨CANCEL_QUEUEβ© to the β¨sharedDisconnectAllStatesβ© and do nothing with it before being cleared
I fixed that in 5 seconds okay
yuo got the fix here
gotcha
Though once again, I think this going to slow Signal firing speeds down once you got 28123 signals wanting their connections disconnected and needing to loop through the entire queue
π
Actually hah, no (because I no longer need to insert mutations)
because cmon, this was just stupid LOL
Peak performance trust
Updated the SomeSignal's devforum download and added the creator store link π
Final Speed
SomeSignal
Zignal
I'd say it's very good for the immense amount of features it offers for versatility & convenience
This is like if Eddie Hall or Tom Stoltman is able to nearly catch up to Usain Bolt in a sprint