[Beta] Deferred Lua Event Handling

Wouldn’t you need to call :Destroy() for nested remotes in order to prevent the memory being eaten up by instances?

Edit: Nevermind, I guess as soon as you call :OnInvoke on a BindableFunction, the thread starts to leak. Good catch! for now, I guess I’ll use BindableEvents and keep my event behavior to immediate until task.spawn and task.defer are released.

Experimentally, no. As long as there are no lingering connections or references, it seems to be freed from memory via garbage collection.

local t = setmetatable({}, {__mode = 'v'})
do
    local bf = Instance.new'BindableFunction'
    bf.OnInvoke = function() end
    table.insert(t, bf)
end
for i = 1, 3 do
    print(next(t))
    wait(3)
end

The only thing I can see happening here is if the nested-fastspawned-thread never exists. In this case, you only get one extra BindableFunction per persistent thread, so having leaking bindables would only be caused if you have leaking threads in the first place.

1 Like

i’ve noticed that PlayerAdded events dont fire correctly anymore, previously it would index a players’ data for example 100% of the time, but now it fails roughly 80% of that

Update: upon further inspection i found out that every event that does anything is handled in an inappropriate manner, there’s no more respawning, no more properly loading in

1 Like

Not sure if anyone actually asked this before but,

I have had some issues with this update here and there, overall I just had to change the order of some stuff and do some extra checks, but… I wanted to ask this for a long time, because no one does…? I’m not sure if it’s a stupid question that was already answered…

But, would you guys add a Signal constructor? Something like Signal.new that allows people to just create events without requiring us to have a separate Signal library inside every single separate library we use…?

I heard that there was already one in the past, but that it was deprecated, but to this day, I have never seen anyone else talk about this except for Kampfkarren and I’m unsure if that was a mistake or if there actually was one.

It’s really annoying to have to update a bunch of models once a change like this happens,
we don’t have an actual way of making events on Roblox, the closest thing we have is RemoveEvents, but they’re extremely inconvenient. They pass all arguments as deep-copied, that’s really annoying for me and I think for a lot of people.

In my code, I have a lot of internal events that are used to decrease RunService.Heartbeat / Stepped usage because of performance, and pretty much 99% of them are built with the fact that a event is fired always with live references to the arguments.

I had to re-write my Signal library a bunch of times at this point, I wish I didn’t actually need to depend on it.

In fact, with Deferred events, I can’t even clean-up them arguments correctly. I just clean them up on :Destroy now. I store them inside the Signal table, and if I remove them from there after the code is :Fire'd, then it will break, because the connections are fired afterwards.

Having a Signal class inside Roblox would be so great, it would be so easy to use, + it would mean that we could use functions that are better optimized.


Also, it’s not just “there’s no way to do it officially” but, it’s hard to find one that’s actually good, one that doesn’t use more memory then what needed, something that respects actual Lua Event behaviour, doesn’t use more CPU than needed. I have never found a single one I’m comfortable using.

The best out of the bunch I’ve seen is loleris’s one, but I don’t like how you create events and it’s really confusing to understand what’s going on + there’s no :Wait method which I also don’t know how to add safely.

Interestingly, this didn’t bite my code, but it bit a bunch of Roblox-written Tools-- When script.Parent
was used, those scripts errored-- Clearly should not be happening.

2 Likes

I’ve looked at this in the past. One of the biggest issues with this is actually PSA: Connections can memory leak Instances! which is a very difficult thing to solve outright.

When you’re using a Signal class you have to be acutely aware of that behavior to not get yourself in a lot of trouble, because there’s no :Destroy() get out of jail free card to mostly keep you safe. Given that many people do not know about this behavior adding such a Signal class would inevitably lead to significantly more memory leaks out there in games.

A pure Lua signal implementation is actually beneficial in that way, because it does not suffer from the same issue.

1 Like

Wouldn’t that be implementable? :thinking: At least something like :DisconnectAll()?

Yes, but you’d still have to go out of your way to call it, because the Signal class would not live in the object hierarchy. As is, you can just do one :Destroy() on your top level object and disconnect all the connections in the whole hierarchy, and this is done automatically for players / characters. You wouldn’t get things for free in the same with with a Signal object.

2 Likes

This isn’t the way you should be using events. Since you expect the connections to run before your code continues, it sounds like you just want a function, or continuing it inside the event callback itself? The rest of your code should not depend on whether anything connected to that event.

As you mentioned that this will be fixed, I’d just like to make something clear - would GetPlayers() always be empty, or can it lead to cases where a onPlayerAdded callback is called both within the for-loop and the PlayerAdded event, thus leading to two calls for the same player?

I don’t think it would call it twice :thinking:

Let’s say we have Player1 who loaded before we started the for loop, then it would run the onPlayerAdded for Player1 and wouldn’t fire the PlayerAdded because they were already in game before the event was connected. And on the contrary, when we call Players:GetPlayers(), it may be empty but then we would connect the .PlayerAdded leading to furter connections on the server from there running once.

I prefer this method over the basic .PlayerAdded as I sometimes run code before join.

for _, plr in ipairs(Players:GetPlayers()) do
    coroutine.wrap(onPlayerAdded)(plr)
end;game.Players.PlayerAdded:Connect(onPlayerAdded);
1 Like

It’s more the other way around. The code connected to the event expects to be called in a timely fashion, rather than at a random time after the fact.

The signal behaviour seems to be set to Deferred in real game, despite being Immediate in Studio. This has introduced considerable latency for my games, as user input events are fired very slightly late in the actual game rather on the studio. While deferred events aren’t the default at the time being (soon will be), please alter this behaviour for the time being.

When will this fix be included internally as @zeuxcg mentioned? It’s very painful to write this check for every single event that I connect. Instead, this check should be done internally before the callbacks are handled, the developer shouldn’t handle edge cases like these unnecessarily.

I’ve noticed a lot of bool workspace properties despite being set to one thing in studio, revert to a different setting come real game.

1 Like

I suspect there’s something else going on here. If events are set to immediate in studio then they will not be deferred in-game. If you believe this is a bug however then be sure to submit a bug-report with as much detail as possible and we can take a closer look into it.

For the specific example, we don’t have an immediate ‘fix’. I’ll be sure to update you when we do.

1 Like

Not sure if this has already been mentioned here or if I’m missing something, but from what it seems to me, it’s possible to “miss” certain property changes if events like Changed and those returned by GetPropertyChangedSignal are deferred. Because apparently these events don’t pass the new value of the property as a parameter to connected callbacks, you have to manually check their value, and if in the meantime the property has changed again, you’ll get the new value and not the one that it had when the event was fired.

Part.Changed:Connect(function()
	print(Part.Transparency)
end)
Part.Transparency = 1
Part.Transparency = 0
-- On Immediate mode, will print "1" followed by "0"
-- On Deferred mode, will print "0" twice

Of course this example is an unrealistic scenario, but who knows what can happen in a game with dozens of events and functions connected to them?

12 Likes

I was referring specifically to an hypothetical scenario when you absolutely need to listen to all property changes, even when one occurs immediately after the other, in which case it wouldn’t be redundant (“immediately” as in before the deferred callbacks are run, not necessarily on the next line of code).

Honestly, at the moment I can’t think of any situation where this could happen, and it’s probably not a very efficient or useful idea to change the same property twice so quickly, but it’s a thing to keep in mind if your code happens to rely on this sort of behavior.

2 Likes

When I set SignalBehavior and PhysicsSteppingMethod to true, it makes my character move very slighty (but still noticeably) faster if I have shiftlock enabled or I’m in first person.

Below is a video of my switching from non-shiftlock to shiftlock enabled, you can see by the baseplate studs that I’m moving faster. I originally noticed this because with those two settings enabled, my anticheat would have more false positives when a player uses shift lock.

You can reproduce this by having these settings in an empty baseplate:

image

another vid of the bug

The rubberbanding is caused by my anticheat, after the rubberbanding it takes away NetworkOwnership which is why my character doesn’t speed up anymore when I enable shiftlock again. There are no scripts modifying walkspeed, especially none that are binded to the shiftlock button.


image

My game genuinely breaks with these two properties. There’s problem when I’m shifting while jumping (it pauses me in the air?) as well as equipping tools that have no mass (massless set to true) that somehow cause blips in physics.

Really hoping these aren’t intentional, because it creates a multitude of new physic cheats/horrendous behavior. If there’s anything I can provide to get these issues fixed, please let me know.

3 Likes

Not sure if this was discussed before, but this method has a problem, it’s not obvious, but anyhow it can lead to some really annoying confusion.

All connections’ .Connected property turns false once the Instance that RBXScriptSignal belongs to is destroyed, the Signal essentially gets destroyed, and new connections have it turned false immediately.

What this paradigm should mean is, “Run this function only once, when the event is fired next time”,
but this might not be achieved, because let’s say you :Fire and :Destroy that signal back to back?

  1. Fire gets called, scheduling the functions to be ran later.
  2. Destroy gets right after, making connections’ .Connected property as false.
  3. Connections run, paradigm believes the functions have ran already, and doesn’t run them at all, as .Connected is set to false.

This is a small issue, most people won’t actually be using signals that are being destroyed at all that often, still, I believe it’s better to have these examples to be using a Connection == nil comparasion instead so people don’t get annoyed with that.

local Connection = nil
Connection = Event:Connect(function()
    if Connection == nil then
        return
    end

    Connection:Disconnect()
    Connection = nil

    -- Do something
end

Also, Deferred Events have been disabled for a while in live games, I know there are still quite a few core scripts broken, but is there an ETA on it being possible to use them again on experiences? I’m fully commited to Deferred Events personally, everything I make has deferred behaviour in mind now.

3 Likes

Hello and thank u for the information

2 Likes