For almost a year, we’ve been working on a rewrite of the system which handles the sizing and positioning of UI elements. Here’s a few of the reasons why:
- The new system is 2x faster at recalculating the size and position of UI objects, such as when scrolling. Parenting UI objects into a hierarchy is significantly faster now as well.
- Properties like AbsoluteContentSize and AbsolutePosition are now up to date when you read them from Lua, instead of being stale until the end of the frame.
- Many bugs that were almost impossible to fix in the old system are fixed in the new system.
- We made the new UI system possible to unit test (the old one wasn’t), and wrote a suite of unit tests to make sure we don’t ship regressions.
- We want to ship fewer UI bugs and more UI features going forward.
- We want to eventually add a feature to size GUIs based on their contents, which was impossible in the old system.
We’ll try to turn on the new system for the first time next week. We may have to revert it, and we may turn it on or off for specific games. If you run into any issues, please post on the dev forums or private message me so that we can fix them.
If you haven’t already, you may want to create a test place using a copy of your existing games and get yourself added to the beta available here:
We’ve already done a lot of testing internally - we extensively tested the top 50 games and have so far fixed over 40 issues caused by the new code.
Your UIs should look the same under the new system. We’ve been very careful to ensure this - we’ve even gone out of our way to reimplement certain behaviors that we consider to be bugs (bug compatibility), because we found several games were relying on them.
The majority of real-world UI code is unaffected. However, the new system behaves differently in how property changes work compared to the old system, which means that some code patterns will no longer behave the same.
In the old system, when an object is not influenced by a UILayout, changing Size or Position will cause AbsoluteSize or AbsolutePosition to fire immediately - the code that set the Size won’t return until the changed event handler has run.
In the old system, when an object is influenced by a UILayout, changes that can cause AbsolutePosition to change, like changing the Size of another object in the layout, will not cause AbsolutePosition to fire until the end of the frame, meaning that the value will be out of date when read by Lua until the end of the frame.
With the new system, it doesn’t matter whether an object is influenced by a UILayout: In both cases, AbsoluteSize and AbsolutePosition will not fire until the value is observed. Observing can happen in one of two ways: Reading the property from Lua, or by the UI being rendered to the screen at the end of the frame. This means that reading a property from Lua can trigger a changed event to fire before returning to the code that requested the value.
This isn’t an exhaustive list, these are just some patterns we found that break.
Expecting changed events to fire immediately while setting properties
local SomeGui = ... -- a TextLabel in this example local HeldLock = false local function Update() HeldLock = true SomeGui.Size = UDim2.new(1, 0, 0, 1000) local bounds = SomeGui.TextBounds SomeGui.Size = UDim2.new(1, 0, 0, bounds.Y) -- In the old system, changed event would fire here. HeldLock = false -- In the new system, it will fire at the end of the frame, long after releasing the HeldLock -- if we added a line like this before setting HeldLock to false: -- local _ = SomeGui.AbsolutePosition -- Then the changed event would be triggered on that line in the new system. Please don't do this, though. end SomeGui:GetPropertyChangedSignal("AbsoluteSize"):Connect(function() if not HeldLock then Update() end end)
This is similar to a snippet we found in the in-game chat code. If you’ve forked the in-game chat, you should update your fork to get the fix we did for this.
This exact snippet is an easy fix: Just use
TextService:GetTextSize() instead of
What happened with this code is that, because AbsoluteSize doesn’t fire immediately during setting the Size property,
HeldLock would be set to
false, and the Changed event would fire at the end of the frame because it wasn’t observed in any other way. This means that at the end of the frame, the changed handler will run and then cause the update function to run all over again - causing the
Update() function to run every frame when it wouldn’t have before.
Certain ways of sizing GUIs to children
Most developers don’t do this, and the few that do have wildly varying implementations.Most simpler implementations - like simply setting the CanvasSize based on AbsoluteContentSize - should have no problem.
Some more complex implementations rely on behavior that changes under the new system. If you’ve written something like this, you may want to check to see if your code will be affected.
- The internal codename of the new system is “Quantum Gui”, because the layout state is undefined until you observe it.
- I’m the main engineer that worked on this.
- If you know C++ and have experience with systems like this, we’re hiring!