[Beta] Deferred Lua Event Handling

What about strategy games? I had an RTS nearing release, but I have to review all the code to get rid of undefined behavior now. A way to start would be searching for “:Fire” in all scripts, and examine the context.
I have more games affected by this update too.

Bindable events have been there for way more than a decade, and they have always worked so it’s safe to assume events fire instantly. This has never been reported in the documentation or elsewhere as bad practice. So a good portion of games that use events will be affected (not just “some developers”), and it’s not our fault.

But in the long term, as explained in an official reply, this is a good update, even if some games remain unplayable.
Unfortunately, this has usually been the trend with Roblox updates - they often improve stuff, but are occasionally destructive. I still miss some games which were broken by different Roblox updates, and the developer did not fix them because they were either inactive or tired of recoding their games over and over again. Anaminus’ Cruiser Hyperspace is the first to come to my mind, but there are many others.

And yes, it’s obvious their intention is not to break games, and this was a required change. We just have to adapt to them :smiley: :disappointed_relieved:

5 Likes

Are we always gonna be able to switch between Immediate and Deferred?

I’d really like it if this was the case.

1 Like

The platform did well for 13+ years.
If it breaks in 5 more years, it’s mostly not gonna be because you didn’t change the order of lua event handling.

I think you should explain in detail why this is necesarry as it’s a very important update with the possible risk of breaking most, if not every single game out there (espacially because of errors caused by CoreScripts), and us developers would like to get the full message that you guys do indeed know what you’re doing.
“three quick examples” just sounds like damage control to me.

1 Like

I was testing out this feature in studio just now and found out a problem that will break some of my game’s features. When a model is removed, its PrimaryPart is set to nil and its children are deleted after the ChildRemoved event is sent. So the following example will now act differently depending on if the immediate or deferred setting is being used:

local Model = game.Workspace.ContainerModel

Model.ChildRemoved:Connect(
	function(Child) -- with deferred behavior, Child is now an empty model
		print(Child.PrimaryPart, #Child:GetDescendants())
	end
)

Model.ChildModel:Destroy()

Workspace hierarchy:

image

Output with immediate behavior: Part 1
Output with deferred behavior: nil 0

The same issue appears with the DescendantRemoving event, where the argument will be an empty model. I think it is safe to say that the Child variable being an empty model here is undesired behavior.

11 Likes

I am unsure how so many people are affected to be honest. Is there common practice among developers (aside from the issue about input handling being delayed) where the immediate invocation of the event is required? Or to disconnect a connection the first time it is fired (and why not just use signal:Wait() in a coroutine as a replacement for that)?

This might be of interest:

3 Likes

This broke my game in ways I don’t fully understand. I can’t tell if it’s because this feature is incomplete or something, but it feels like certain things are firing completely out of order now, even where it should make sense either way.

8 Likes

I have a similar optimization situation on updating that can greatly be helped with the introduction of deferred events. Right now, I have a building that needs to keep track of parts that are connected to the ground, either directly or through another grounded part, and mark parts that are no longer connected and are floating in the air. Every time a part is removed from the building, it needs to run its ‘update’ function. The function is fast enough to update smoothly, but the problem lies when multiple parts are removed on the same frame, the building has to update IMMEDIATELY upon every part being removed so the function runs needlessly multiple times. With deferred events, multiple part removals can be deferred to be handled all at once and only require a single update call max per frame.

This image shows many updates happening on the same frame that could easily be condensed to 5 updates total with deferred events.

Overall, I’m excited for this update to get out of Beta and fully released for all to use. This thread has been packed with great information, some mixed emotions or confusion from some members but I think that the more you know about the update the more you will be excited as I am :cowboy_hat_face:

4 Likes

I don’t know if this is the right place to post glitches about differed events, so please let me know if I’m being disruptive to the thread.
I found a reliable way to reproduce this warning in studio (when signals are differed)

RunService warning

  1. Create a part and select it as either move, scale, or rotate

  2. Duplicate the part

  3. Change selection modes (I.E. from move to select, from rotate to scale, etc.)

You can definitely tell it’s a beta, but I’m excited to see all the creases ironed out as time goes on

Edit: Never mind the previous statement, it seems to happen completely at random when changing selection modes after selecting a new BasePart.

1 Like

This fundamentally changes Roblox’s event handling model and breaks any code that expects to be notified immediately when an event fires. Basically, most code that uses events.

Once your callback is called, it’s already too late. The caller has already sped way ahead of you and done other things, and you have to account for way more edge cases now. It is now impossible to use events to ensure that something is acted upon in a timely manner; you may as well use spawn.

Changes in Instance hierarchy are particularly dangerous examples of this, as scripts might want to know when an Instance they control is reparented/etc and act on it quickly, but now scripts that execute before the callback can see invalid/unintended states.

Things like the camera input being off by one frame appear out of thin air as a side effect of this. By the time scripts are notified of the events corresponding to user input, it’s already too late to change things in response before the next frame.

The fix is to notify scripts faster, which is what Roblox already does and has done since the beginning of time. I do not see why it’s required to break every script in Roblox that relies on events, even if you’re going to do it over the course of a few years. You can not expect every experience on Roblox to migrate, especially the ones that aren’t being maintained anymore, or whose developers have long left the platform. This will leave all the hidden gems of Roblox in an incredibly broken state.

If this is needed for Parallel Luau, keep the old system and simply use this new one only for events that are fired in parallel (so basically, events that are fired outside the Actor by code running desynchronized inside it). This will keep every existing script on Roblox working, while preparing for the future of new code running in parallel. Breaking so many things on Roblox outside of Parallel Luau is not required.

Parallel Luau is a beta feature and has no compatibility guarantees yet, and making that change to allow it to function is perfectly acceptable because running Luau in parallel is a new concept that requires some care. It’s expected that it’ll be different than regular scripting.

However forcing this change on existing code and the entire non-parallel Roblox ecosystem is not acceptable for me. I highly recommend that you backpedal a bit on this change and consider confining it only to actually concurrent systems like Parallel Luau. This is detrimental to my code and many others’ outside of parallel execution.

Firing an event is inviting other code to act on it instantly, that’s what events are for. Events should not be queued unless there is reason for not being able to act on it instantly, like in the case of Parallel Luau.

Of course nobody’s going to read my concerns, but I think it’s worth writing them down here. :/

16 Likes

I think I have an actually sensible idea for how this idea could be implemented into the engine, without breaking existing code or removing functionality, and while allowing for Parallel Luau.

Confine deferred behavior to crossing VM boundaries. When you fire an event, callbacks from the same VM will be called immediately as they are now, but callbacks from other VMs will be deferred.

This will preserve the immediate behavior for all current games and all code confined to one Actor, but have deferred behavior for certain things like Instance hierarchy changes propagating up past the Actor or desynchronized threads firing BindableEvents/etc.

It will avoid breaking existing code while allowing events to work in multithreaded code. All the effort towards setting up these defer queues won’t go to waste yet all games on Roblox will not cease to function randomly.

Only those who explicitly opt-in to using Actors or task.desynchronize will ever have to deal with deferred events. Everything else will be Immediate. Events firing inside the same Actor will be immediate.

Current behavior is preserved. Backwards compatibility is achieved. Yet it paves the road for Parallel Luau.

As far as I can tell, this is the only way to have your cake and eat it too. If you break every existing game, developers will be mad. Developers are currently mad. If you don’t defer events ever then Parallel Luau will take huge performance hits waiting for other VMs whenever you fire events. But if you use this hybrid approach then existing games will continue to function and Parallel Luau can zoom as fast as it wants.

I hope I’m not too late to propose this. I think there is still time to save this feature.

16 Likes

Hey @zeuxcg, a question if you don’t mind:

In regards to events being deferred, are all same events grouped up, or does the deferring not care for what event it is? What I mean is, if 10 different events are invoked for the same instance, would they be deferred? Or do events only get deferred when the same event is called in rapid succession, such as with BindableEvents?

1 Like

Events are currently always deferred. When you call an event, the callback is no longer invoked, and you will be able to do whatever you want. Then, eventually, at some point, the callback will be invoked, some time in the future. Right on time… (not)

Usually, “some time in the future” is one of the “checkpoints” in the current frame. Your code will still execute, it just won’t execute when you expect it to, and you will no longer be able to use events for efficiently dispatching instant messages.

You can still use it for dispatching messages that will eventually arrive, though. Just not time-sensitive ones.

1 Like

It’s difficult to test code with this when API events, especially .PlayerAdded, are completely or to some degree broken and don’t seem to fire at all or they do rarely. My new project becomes half-unplayable with the deferred signal behavior enabled as of now.

I am quite worried with this change, considering that apparently every event will now be deferred (definitely a bit afraid on how this can affect .AncestryChanged, .Changed, and such).

2 Likes

This. Not every event needs to be deferred. There is a solution.

1 Like

When I start my game it stalls for longer than normal and I see this. I have no idea where this is coming from. Is it possible to have this error attach at least some information about where it’s coming from?

image

2 Likes

Thanks for the report. As a first step we’re looking at linking these errors to the function that’s exceeding the re-entrancy limit. I will update you when this change goes live.

2 Likes

Why must every event be deferred, breaking nearly every script on Roblox all at once? Why not do this only for Parallel Luau?

zeuxcg’s first post in this topic should answer that question on why they need to do it for all events.

Were there any recent changes made to this?

When this was announced, I turned the setting on in a game that I’m working on just to test and see how my game handles it. It didn’t handle it very well (which is my fault, I’m doing some weird stuff with my code and I really need to patch it up).

Fast forward to today, I enabled the setting because I wanted to dig in to some of my scripts and make the game compatible with this change.

For whatever reason, everything seems to work perfectly now. I’m just wondering if this was something I did unintentionally (via making a lot of improvements to my replicator), or if maybe there were any changes made with this feature that might’ve suddenly improved the behavior of my game.