I never coded anything with the idea Engine Events were immediate, now I’m glad I did. If you have experience coding in old game engines, it was well known to never code a game with the expectation of any events or signals or objects being about to fire, access object data or functions immediately, etc. You always planned things out to avoid such headaches. I’m surprised Roblox was running this way for so many years, but better late than never in catching up.
Been using deferred events since I started working on my new game and didn’t run into any issues yet and I didn’t have to change anything about the way I code stuff, great work!
Just found an issue in my game, I wrote a small code to allow me to iterate through a table that can take a long time to execute and yield until the next frame if is taking too long to get out of the iteration. Very simple, I count the time is taking each iteration and if is going higher than 0.005 Seconds I yield until the next frame, to continue iterating through the table. Focasds why are you doing this? I do this since it allows other parts of my code to run and doesn’t cause a freeze.
With Deferred Events this code is affected since it can iterate easily through the table and not account for what’s coming the next frame which is a spam of events happening non-stop with no control whatsoever.
What can I do to mitigate this issue? Parallel Luau can’t be used for this since changing properties is what takes most of the time, because of some events or something else internally.
Conflicting Code I Found So Far:
In-Line resumption.
local success = false
Bindable.Event:Connect(function ()
success = true
end)
Bindable:Fire() -- Causes `Event` to fire
return success
In my game I do not use events as a way to perform this kind of tasks, but I have rare occurrences where I do rely on their previous behavior. Bindable:Fire being included in this deferred behavior is kind of a pain, I wish we had the ability to at least disable that one.
The example about In-Line Resumption opens a bag of issues. Once again, I tend to rely on BindableEvents to trigger automatically to Enable some stuff in the game. Then some other scripts can check the changes, which will not occur until the next frame…
Glad to see optimization to the internal side of things.
If you have a game you actively work on or update. Then it’s probably good practice to keep up with the updates pushed out by Roblox. Most of those workspace settings are great optimization settings if your game is coded for it.
I’m not saying your lying (hell I welcome this update) but can you please provide where you are getting those statistics? Everytime a big update like this comes out y’all provide some random statistic but dont show how you got it…
Relying on immediate events are race conditions. I can’t remake my entire game to use deferred events just to get a small performance boost.
In this example you can fix this if you have the success boolean inside of a table and return the table instead, so you have a live state of what you’re modifying instead of just copying it’s value.
Actual Game Example: I have a BoolValue and a BindableEvent. I currently trigger the BindableEvent to enable the BoolValue and create some extra tasks. After one of those tasks is completed the BoolValue goes to false again.
If I trigger this BindableEvent now, and some parts of the game later in the same frame checks for players who have this BoolValue enabled will run into a false value.
Custom Signal can be used to circumvent this issue, but this just means that BindableEvent becomes useless in this kind of situation, or I just change it to something else.
But once again, this is the issue everyone is talking about which is the need to change multiple parts of your code that were working fine with immediate to allow for a tiny boost in optimization and creates this weird behavior where you change a property, and its respective event triggers the next frame. Many things can happen under the same frame that could check for things that those events change.
I understand why it’s a problem but ultimately I’d encourage you to design future code to work with newer patterns. Indirect state is risky, and value objects suck anyway.
You should have some kind of module storing these tasks so you can very discretely index the state of whatever work you’re doing. It goes a long way in making code more maintainable and easier to assess when issues arise. Imagine someone else having to work with your code without guidance from you, would they understand the control flow?
My general solution to preventing stuff from breaking or to make maintenance easier is to just have 1 code library that I use across projects.
All I should have to do is update some functions in my library and everything should be good to go.
I also generally recommend not relying on highly specific things like the order in which things happen.
I just assume that order is never guaranteed.
If you do end up relying on a certain feature or behavior that is highly specific, make sure you generally always have at least one safeguard in place.
As far as I know, this deferred event update doesn’t change a whole lot.
The only thing I’ve seen this update affect so far is scripts that rely on objects being destroyed or parented to nil to perform a clean-up function.
If most of your game code is just .Touched
events, buttons being clicked, user input, etc then all should be fine really.
You can still check if objects are being relocated or parented to something else like normal.
You can still check for value changes as normal.
The only thing I’ve really seen being affected by this is object destruction.
But I also saw that Roblox added a Instance.Destroying event.
I think this event is supposed to be a more reliable replacement for scripts that check for objects being parented to nil
.
Oh and before you worry, just in case, create a back-up of your game and enable behavior settings one by one just to see if it breaks anything at all.
I see some people panic about game breaking changes, which I fully understand.
But before we speculate or assume, I think one step forward would be to perhaps first enable settings and see if it breaks anything at all, play test some, check the output for potential errors.
After that you can pretty much confirm it, worst case scenario maybe some things do end up not functioning correctly but in that case reverting the settings is just a few mouse clicks.
I hope this is helpful to you and others reading this post.
Also FWIW I made this mistake too when I introduced deferred updates into Mad City’s interior culling system. I had to change the code so it used a state observer instead of passing a copy of the boolean.
I am adapting my game as much as I can with the new features, but this one bothers me a lot because of how it works. Events were something that from the very beginning, at least in Roblox, would trigger as soon as you make a change.
Since Roblox worked in this way, everyone developed with that in mind. For most uses like changing an Instance’s properties is understandable and I believe less users used it as a way to makes changes.
But BindableEvents could have been used as a way to trigger state changes and the same for every other event.
If you run the following code:
workspace.Baseplate:GetPropertyChangedSignal("CanTouch"):Connect(function()
print(workspace.Baseplate.CanTouch)
end)
for i = 1, 50 do
workspace.Baseplate.CanTouch = not workspace.Baseplate.CanTouch
end
You will get all trues, when in reality the State of this property changed to False multiple times. I am honestly against this change.
I agree they should be providing the new value in the changed event, but ultimately it’s better not to rely on property changes for immediate dispatching.
If you really need an immediate dispatch, you need to do it with a task.spawn
call directly from whatever code is changing the state. Deferred observation on the task scheduler’s heartbeat produces much less redundant work and can make a huge difference for things that rapidly change, as long as you respect the current observed value.
I hope Roblox can produce some kind of automated solution to detect these coding patterns and help fix them in the future.
So with deferred events queuing things, does that mean if I was previously sending one remote events update with a bunch of data per frame instead of multiple fires per frame, I should keep doing that in every case, or does roblox’s changes make a difference?
This nuance is a huge deal! Glad to see this was addressed.
I am using deferred signal behavior in new projects. There was an issue with the Knit library but I was able to work with Sleitnik to get that resolved. Haven’t seen any noticeable gameplay issues at this point now. I would recommend the skeptical try enabling it in your experience and seeing what happens.
We made some changes but without more information it is difficult to know if they resolved your issue. Could you include an example of something that’s not working as you expect?
It is a continuous process, there are some performance benefits that we can provide now but many others that we can’t do until all experiences are using deferred events.
Event ordering is preserved however event execution is deferred to later in the frame. Even though the event was triggered, the function you passed to Connect, Once, or Wait won’t run immediately.
We process the deferral queue until it’s empty so any events that get triggered will be resumed at the end of the current resumption cycle rather than the next.
Plugins are a tricky one to solve. It’s not that easy to do one thing for plugins and another for scripts in an experience. Our hope is that most plugins can be updated to support deferred events. If any creators are interested in doing this but finding it difficult for one reason or another then feel free to reach out to me and I’ll be happy to help with this.
Oh then I love this change, im sure waiting a tick or 2 isn’t gonna cause a massive yield or slowdown in code + you guys said it improves performance so this is amazing, we dont even need to change anything.
In fact I am going to enable this update right now in my game.
So, if I understand it better, this means that after all coroutines that were yielding by the Task Scheduler are resumed, then comes the events?
Hypothetical Example:
Coroutine 1 → Triggers 2 Events
Coroutine 2 → Triggers 3 Events
Coroutine 3 → Triggers 1 Event
The 6 Events are triggered then after everything is resumed and finished.
So is like another order basically?
Maybe it should only be applied to new games?
Edit just read the reply on that, nevermind!