- What is relayout?
- Forcing relayout from a script
- Layout in UnityUI vs UIToolkit vs Flexbox4Unity
What is Relayout, and why does it matter?
There are two visual parts to any UI framework: rendering (drawing the components) and layouting (automatically sizing and positioning them). Traditionally Unity’s UnityUI framework has a layout routine that is not only slow but also makes a lot of mistakes – e.g. multiple bugs that make your components suddenly 0 pixels wide and 0 pixels high, forcing you to delete and recreate them, or manually re-position everything.
Ideally we want Layout to happen in a lot of different places/times:
- When the UI is first displayed on screen, we want one Layout call to make sure everything is correctly sized and positioned
- When you add a new component (while building a UI), we want to re-do the existing layout to fit-in the new Button/Label/etc
- When you add a new component (from a script), because the player changed something – e.g. they added a new item to their character’s Inventory, and you want it to appear in the Inventory window
- When an existing component changes its size, we want to relayout other components that might need to expand to fill the spare space, or shrink to make room for it
- When existing components disappear because you used Unity’s “SetActive( false )” – or vice versa: they re-appear (“SetActive( true )”)
- When you move some piece of UI to somewhere else – e.g. if you allow the player to drag/drop reposition parts of the UI
Because it happens in so many places even a small reduction in Layout/Relayout performance will have a big effect on your game/app.
Equally: if the UI framework tries to be too conservative, and doesn’t call Relayout often enough, your players will see a UI that appears to be mis-sized, “out of date”, or glitching/flashing/flickering.
We need a Layout algorithm that decides the positions and sizes of everything – but we also need a Relayout algorithm that decides “when to re-run the Layout algorithm?”
How to force Relayout from a script?
Flexbox4Unity tries to detect all the moments when a Layout is needed, and automatically do it without any input from you. But sometimes you want to disable that (e.g. because you’re making lots of changes in script during a single frame, and you want Relayout to pause until you’ve finished your changes), or you want to force extra Relayouts (because you find a situation where Flexbox fails to notice an automatica Relayout was needed).
In Flexbox4Unity, every LayoutAlgorithm also contains a RelayoutAlgorithm inside it – the design of one fundamentally changes the design of the other, so I package both up at once. To trigger a Relayout, we first need a reference to the current LayoutAlgorithm.
Getting the current LayoutAlgorithm
v2.0 onwards: there is one global LayoutAlgorithm shared by all Flexbox components in your project. If you change it anywhere, it changes everywhere.
v4.0 onwards: (experimental) individual Flexbox-contexts may use different layout algorithms; but all components within a single context (everything inside a single RootFlexContainer) has to use the same algorithm.
Either way, you start by getting a reference to a FlexContainer, and requesting the global Settings object that it’s using:
FlexContainer container = ... // the container you want to Relayout var settings = container.settings;
…and then you can request the layout algorithm currently being used:
var settings = ... // settings object from above var algorithm = settings.currentLayoutAlgorithm;
However: each Layout Algorithm has a different design, a different interface, different API calls. A v4 Layout Algorithm has features that a v3 Layout Algorithm may not – and vice versa. So we want a clean, typesafe, way in C# to have the compiler check our code and ensure we never try to make v3 method calls on a v4 algorithm, and vice versa.
Here is the standard pattern for this in C#, together with the official default methods for each version of Flexbox4Unity to trigger a Relayout of a FlexContainer:
if( settings.currentLayoutAlgorithm is ILayoutAlgorithmV2 v2Algorithm ) v2Algorithm.ReLayout( this, RefreshLayoutMode.SELF_AND_DESCENDENTS_ONLY ); else if( settings.currentLayoutAlgorithm is ILayoutAlgorithmV3 v3Algorithm ) v3Algorithm.ReLayout( this ); else if( settings.currentLayoutAlgorithm is ILayoutAlgorithmV4 v4Algorithm ) v4Algorithm.InvalidateSizeAndTriggerRelayout( this ); else throw new Exception( "Not implemented yet: versions greater than 4.0!" );
In the code snippet above notice that the different versions of algorithm not only use different names for the Relayout call – but they have different parameters too. Also notice that some of them give you more or less control – the v2 algorithms allowed you to selectively Relayout subsets of the screen (this isn’t needed in v3 and v4 – they are smarter at scanning for changes first).
Relayout in UnityUI vs UIToolkit vs Flexbox4Unity
For reference, here’s a quick view of the different designs of Relayout in Unity vs Flexbox.
- Every time something changes, Layout is done twice (sounds like a waste? Yes, it is!), once ‘horizontal’, and once ‘vertical’. This is never necessary, and it causes a lot of bugs, but Unity’s original design for their Layout algorithm was poor.
- On a Layout, every single item everywhere is asked to attempt a full relayout, even if it hasn’t changed, or cannot change
- On each Layout, some information is cached from the previous Layout, and can corrupt the new Layout call (this is the source of a lot of UnityUI bugs)
- Every time a GameObject is enabled, a full layout happens
- Every time (many different things) happen in the Editor, a full layout happens
- Layout is reasonably quick for basic cases, but very slow for complex cases
- Layout is only done once ever, and never again; to Relayout, you have to manually destroy and recreate the UI
- … except for “display: none” / “display: flex”: if you change this attrigute on a UIToolkit component then a Relayout will be triggered with the item disabled/enabled. But this doesn’t allow you to add or remove items, or rearrange them – you can only turn on/off ones that you had placed in the original, first, Layout
- In simple cases you can dynamically change the components, size, position, order, etc, but this is officially unsupported by Unity, and frequently corrupts both the rendering and the logic of Buttons, clicks, etc. If you discover bugs in this situation Unity probably won’t fix them.
- Layout is noticeably slow even for basic cases, but only a little slower in complex cases – overall the speed is much more predictable than for UnityUI, and for complex cases it is much faster than UnityUI.
- If you must do a Relayout then it’s often slower than UnityUI. If you must do a very small piece of Relayout – changing just one item – then it’s massively slower than UnityUI.
- Not being able to do Relayout is a major problem with all UIToolkit/UIElements UI’s, and prevents a lot of nice features and plugins/AssetStore features that we’ve become used to in Unity over the years.
- v2: Uses UnityUI’s frequent layouts
- …but ignores every other Relayout (the duplicated one)
- ignores many cases where Layout is obviously not needed.
- requires the developer to specify ‘how much’ you want to Relayout: just this item? This item and its parent? This item nad its children? This item, all its parents, and all their children?
- v3: Uses Unity UI’s frequent layouts, but also hooks into RectTransform and MonoBehaviour callbacks for “things that imply a Relayout is needed”
- There are a lot of times that UnityUI’s layout is needed but isn’t reliably invoked by the Unity Editor; v3 tries to detect these and intelligently Relayout on them
- The choices made by developers are simplified to just “Relayout this Container + its children” or “Relayout everything on the screen”
- v4: Ignores all Unity layout, runs a standalone Relayout system
- NB: requires Unity 2019.4.10 or later — there are multiple core bugs in Unity itself that weren’t fixed until recently, and Unity’s callbacks didn’t work correctly in earlier versions
- Takes the ideas in v3, and extends them: all Relayout only happens due to callbacks from the UnityEngine/Editor
- Many cases where UnityEditor spams updates are detected and automatically silenced (increases performance)
- Many cases where UnityEditor fails to send an update are detected and artificial calls are injected to trigger the required Relayout
- The interface for developers is simplified to just one call “invalidate( container or item )”: whenever it detects an invalid item, a Relayout is automatically scheduled
- Primary goals for all versions:
- Raw Layout (first-time layout) performance is considerably faster than UnityUI layout
- Raw Relayout (changes made after the initial layout) performance is considerably faster than UIToolkit
- Relayout is supported at all times, in all places, with no caveats
- Components don’t become corrupt: changing data is re-evaluated from a clean slate on each Relayout call.
…of course: there are bugs! There are situations where performance should be lightning-fast but drops through the floor – if you encounter one of these then please reach out to me on our Discord channel, or email the support address on this site, and we’ll try to diagnose and fix it ASAP.