Skip to content

Instantly share code, notes, and snippets.

@builder-main
Created February 17, 2024 14:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save builder-main/680cc14cdaabb1cc2a7ec58992bdad29 to your computer and use it in GitHub Desktop.
Save builder-main/680cc14cdaabb1cc2a7ec58992bdad29 to your computer and use it in GitHub Desktop.
Fix for unity layout groups dirting prefabs and having bad perf.
[TypeInfoBox("Help managing layouts and avoid dirty prefabs as well as runtime performance." +
"Add any rectTransform at Awake any content size fitter and layout group will be found from the filters." +
"You can also force offline search with 'PopulateManual' for prefab workflow.")]
public class LayoutGroupManualUpdater : MonoBehaviour
{
/// <summary>
/// Layouts to enable / disable for layout updates and performances
/// </summary>
[SerializeField]
public List<LayoutGroup> AllEnabledLayouts = new List<LayoutGroup>();
/// <summary>
/// Content size fitter to enable/ disable on layout updates
/// </summary>
[SerializeField]
public List<ContentSizeFitter> AllContentSizeFitters = new List<ContentSizeFitter>();
[SerializeField]
public List<RectTransform> _roots = new List<RectTransform>();
[Tooltip("If sequential will wait a frame between each root")]
public bool sequentialUpdate = false;
[Title("filter")]
public bool activeInHierarchy = true;
public bool isEnabled = true;
public void Awake()
{
foreach (var rectTransform in _roots)
{
AddAll(rectTransform);
}
}
[HorizontalGroup]
[Tooltip("Update all layouts and disable them at editor time, also populates the Layout and Content size fitter lists.")]
[Sirenix.OdinInspector.Button]
public void PopulateManual()
{
Awake();
DisableAll();
}
[HorizontalGroup]
[Tooltip("Restore to initial state")]
[Sirenix.OdinInspector.Button]
public void ClearManual()
{
EnableAll();
AllEnabledLayouts.Clear();
AllContentSizeFitters.Clear();
}
[HorizontalGroup]
[Tooltip("If root is a prefab variant restore all layouts / fitter / child transform to parent prefab value")]
[Sirenix.OdinInspector.Button]
public void RevertVariant()
{
#if UNITY_EDITOR
var stage = PrefabStageUtility.GetCurrentPrefabStage();
if (!stage)
{
IndusLogger.Error("LayoutGroupManualUpdater.RevertVariant()", $"Only works in Prefab Variant edit mode");
return;
}
var nonVariant = AllEnabledLayouts.Where(l => stage.IsPartOfPrefabContents(l.gameObject).Not()).ToArray();
if (nonVariant.Any())
{
IndusLogger.Error("LayoutGroupManualUpdater.RevertVariant()",
$"Security check : Some layouts are not from prefab variant and we cant revert {nonVariant.ToDelimitedString(";")}");
return;
}
var nonVariantFitters = AllContentSizeFitters.Where(l => stage.IsPartOfPrefabContents(l.gameObject).Not()).ToArray();
if (nonVariantFitters.Any())
{
IndusLogger.Error("LayoutGroupManualUpdater.RevertVariant()",
$"Security check : Some fitters are not from prefab variant and we cant revert {nonVariant.ToDelimitedString(";")}");
return;
}
var revert = UnityEditor.EditorUtility.DisplayDialog("Warning", "This will revert the prefab variant values of ContentSizeFitter.enabled, LayoutGroup.enabled, LayoutGroup's rectTransform, and LayoutGroups' direct child's RectTransform", "Proceed", "Cancel");
if (!revert)
{
IndusLogger.Debug("LayoutGroupManualUpdater.RevertVariant()",
$"Canceled");
return;
}
UnityEditor.Undo.RegisterFullObjectHierarchyUndo(gameObject, "Undo Revert of child layouts");
foreach (var layout in AllEnabledLayouts)
{
RevertEnabled(layout);
for (var i = 0; i < layout.transform.childCount; i++)
{
var rectT = layout
.GetComponent<RectTransform>();
if (rectT) UnityEditor.PrefabUtility.RevertObjectOverride(rectT, UnityEditor.InteractionMode.AutomatedAction);
layout.transform
.GetChild(i)
.AsOptional<RectTransform>()
.IfPresent(c => UnityEditor.PrefabUtility.RevertObjectOverride(c, UnityEditor.InteractionMode.AutomatedAction));
}
}
foreach (var fitter in AllContentSizeFitters)
{
RevertEnabled(fitter);
}
void RevertEnabled(Behaviour comp)
{
var layoutSerialized = new UnityEditor.SerializedObject(comp);
var enabledProp = layoutSerialized.FindProperty("m_Enabled" /*Mandatory magic string*/);
UnityEditor.PrefabUtility.RevertPropertyOverride(enabledProp, UnityEditor.InteractionMode.AutomatedAction);
}
#endif
}
/// <summary>
/// Dynamically Add all enabled layout group and content size fitters from given root
/// </summary>
/// <param name="root"></param>
public void AddAll(RectTransform root)
{
if(_roots.Contains(root).Not()) _roots.Add(root);
IndusLogger.Debug("LayoutGroupManualUpdater.AddAll()", $"Looking layouts in {root}");
var toAddLayout = root.GetComponentsInChildren<LayoutGroup>(includeInactive: !activeInHierarchy)
.Where(l => !isEnabled || l.enabled)
.Where(l => AllEnabledLayouts.Contains(l).Not());
AllEnabledLayouts.AddRange(toAddLayout);
var toAddFitters = root.GetComponentsInChildren<ContentSizeFitter>(includeInactive: !activeInHierarchy)
.Where(l => !isEnabled || l.enabled)
.Where(l => AllContentSizeFitters.Contains(l).Not());
AllContentSizeFitters.AddRange(toAddFitters);
}
[Sirenix.OdinInspector.Button]
public void UpdateAsync()
{
if(Application.isPlaying.Not()) return;
if(gameObject.activeInHierarchy) StartCoroutine(UpdateCoroutine());
}
private IEnumerator UpdateCoroutine()
{
EnableAll();
foreach (var rectTransform in _roots)
{
LayoutRebuilder.MarkLayoutForRebuild(rectTransform);
if (sequentialUpdate) yield return null;
}
yield return null;
DisableAll();
}
/// <summary>
/// Disable all layouts through API
/// </summary>
[HorizontalGroup]
[Tooltip("Disable all elements")]
[Sirenix.OdinInspector.Button]
public void DisableAll()
{
SetEnableElements(false);
}
/// <summary>
/// Enable all layouts through API. Useful if a continuous updating is needed such as in tween or animations.
/// </summary>
[HorizontalGroup]
[Tooltip("Restore to initial state")]
[Sirenix.OdinInspector.Button]
public void EnableAll()
{
SetEnableElements(true);
}
private void SetEnableElements(bool isEnabled)
{
foreach (var sizeFitter in AllContentSizeFitters)
{
if(sizeFitter == null) continue;
sizeFitter.enabled = isEnabled;
}
foreach (var layout in AllEnabledLayouts)
{
if(layout == null) continue;
layout.enabled = isEnabled;
}
}
//
// public LayoutGroupManualUpdater(List<LayoutGroup> allEnabledLayouts, List<ContentSizeFitter> allContentSizeFitters)
// {
// AllEnabledLayouts = allEnabledLayouts;
// AllContentSizeFitters = allContentSizeFitters;
// }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment