using System; using System.Collections.Generic; using UnityEngine.UI.Collections; namespace UnityEngine.UI { /// /// Values of 'update' called on a Canvas update. /// /// If modifying also modify m_CanvasUpdateProfilerStrings to match. public enum CanvasUpdate { /// /// Called before layout. /// Prelayout = 0, /// /// Called for layout. /// Layout = 1, /// /// Called after layout. /// PostLayout = 2, /// /// Called before rendering. /// PreRender = 3, /// /// Called late, before render. /// LatePreRender = 4, /// /// Max enum value. Always last. /// MaxUpdateValue = 5 } /// /// This is an element that can live on a Canvas. /// public interface ICanvasElement { /// /// Rebuild the element for the given stage. /// /// The current CanvasUpdate stage being rebuild. void Rebuild(CanvasUpdate executing); /// /// Get the transform associated with the ICanvasElement. /// Transform transform { get; } /// /// Callback sent when this ICanvasElement has completed layout. /// void LayoutComplete(); /// /// Callback sent when this ICanvasElement has completed Graphic rebuild. /// void GraphicUpdateComplete(); /// /// Used if the native representation has been destroyed. /// /// Return true if the element is considered destroyed. bool IsDestroyed(); } /// /// A place where CanvasElements can register themselves for rebuilding. /// public class CanvasUpdateRegistry { private static CanvasUpdateRegistry s_Instance; private bool m_PerformingLayoutUpdate; private bool m_PerformingGraphicUpdate; // This list matches the CanvasUpdate enum above. Keep in sync private string[] m_CanvasUpdateProfilerStrings = new string[] { "CanvasUpdate.Prelayout", "CanvasUpdate.Layout", "CanvasUpdate.PostLayout", "CanvasUpdate.PreRender", "CanvasUpdate.LatePreRender" }; private const string m_CullingUpdateProfilerString = "ClipperRegistry.Cull"; private readonly IndexedSet m_LayoutRebuildQueue = new IndexedSet(); private readonly IndexedSet m_GraphicRebuildQueue = new IndexedSet(); protected CanvasUpdateRegistry() { Canvas.willRenderCanvases += PerformUpdate; } /// /// Get the singleton registry instance. /// public static CanvasUpdateRegistry instance { get { if (s_Instance == null) s_Instance = new CanvasUpdateRegistry(); return s_Instance; } } private bool ObjectValidForUpdate(ICanvasElement element) { var valid = element != null; var isUnityObject = element is Object; if (isUnityObject) valid = (element as Object) != null; //Here we make use of the overloaded UnityEngine.Object == null, that checks if the native object is alive. return valid; } private void CleanInvalidItems() { // So MB's override the == operator for null equality, which checks // if they are destroyed. This is fine if you are looking at a concrete // mb, but in this case we are looking at a list of ICanvasElement // this won't forward the == operator to the MB, but just check if the // interface is null. IsDestroyed will return if the backend is destroyed. var layoutRebuildQueueCount = m_LayoutRebuildQueue.Count; for (int i = layoutRebuildQueueCount - 1; i >= 0; --i) { var item = m_LayoutRebuildQueue[i]; if (item == null) { m_LayoutRebuildQueue.RemoveAt(i); continue; } if (item.IsDestroyed()) { m_LayoutRebuildQueue.RemoveAt(i); item.LayoutComplete(); } } var graphicRebuildQueueCount = m_GraphicRebuildQueue.Count; for (int i = graphicRebuildQueueCount - 1; i >= 0; --i) { var item = m_GraphicRebuildQueue[i]; if (item == null) { m_GraphicRebuildQueue.RemoveAt(i); continue; } if (item.IsDestroyed()) { m_GraphicRebuildQueue.RemoveAt(i); item.GraphicUpdateComplete(); } } } private static readonly Comparison s_SortLayoutFunction = SortLayoutList; private void PerformUpdate() { UISystemProfilerApi.BeginSample(UISystemProfilerApi.SampleType.Layout); CleanInvalidItems(); m_PerformingLayoutUpdate = true; m_LayoutRebuildQueue.Sort(s_SortLayoutFunction); for (int i = 0; i <= (int)CanvasUpdate.PostLayout; i++) { UnityEngine.Profiling.Profiler.BeginSample(m_CanvasUpdateProfilerStrings[i]); for (int j = 0; j < m_LayoutRebuildQueue.Count; j++) { var rebuild = m_LayoutRebuildQueue[j]; try { if (ObjectValidForUpdate(rebuild)) rebuild.Rebuild((CanvasUpdate)i); } catch (Exception e) { Debug.LogException(e, rebuild.transform); } } UnityEngine.Profiling.Profiler.EndSample(); } for (int i = 0; i < m_LayoutRebuildQueue.Count; ++i) m_LayoutRebuildQueue[i].LayoutComplete(); m_LayoutRebuildQueue.Clear(); m_PerformingLayoutUpdate = false; UISystemProfilerApi.EndSample(UISystemProfilerApi.SampleType.Layout); UISystemProfilerApi.BeginSample(UISystemProfilerApi.SampleType.Render); // now layout is complete do culling... UnityEngine.Profiling.Profiler.BeginSample(m_CullingUpdateProfilerString); ClipperRegistry.instance.Cull(); UnityEngine.Profiling.Profiler.EndSample(); m_PerformingGraphicUpdate = true; for (var i = (int)CanvasUpdate.PreRender; i < (int)CanvasUpdate.MaxUpdateValue; i++) { UnityEngine.Profiling.Profiler.BeginSample(m_CanvasUpdateProfilerStrings[i]); for (var k = 0; k < m_GraphicRebuildQueue.Count; k++) { try { var element = m_GraphicRebuildQueue[k]; if (ObjectValidForUpdate(element)) element.Rebuild((CanvasUpdate)i); } catch (Exception e) { Debug.LogException(e, m_GraphicRebuildQueue[k].transform); } } UnityEngine.Profiling.Profiler.EndSample(); } for (int i = 0; i < m_GraphicRebuildQueue.Count; ++i) m_GraphicRebuildQueue[i].GraphicUpdateComplete(); m_GraphicRebuildQueue.Clear(); m_PerformingGraphicUpdate = false; UISystemProfilerApi.EndSample(UISystemProfilerApi.SampleType.Render); } private static int ParentCount(Transform child) { if (child == null) return 0; var parent = child.parent; int count = 0; while (parent != null) { count++; parent = parent.parent; } return count; } private static int SortLayoutList(ICanvasElement x, ICanvasElement y) { Transform t1 = x.transform; Transform t2 = y.transform; return ParentCount(t1) - ParentCount(t2); } /// /// Try and add the given element to the layout rebuild list. /// Will not return if successfully added. /// /// The element that is needing rebuilt. public static void RegisterCanvasElementForLayoutRebuild(ICanvasElement element) { instance.InternalRegisterCanvasElementForLayoutRebuild(element); } /// /// Try and add the given element to the layout rebuild list. /// /// The element that is needing rebuilt. /// /// True if the element was successfully added to the rebuilt list. /// False if either already inside a Graphic Update loop OR has already been added to the list. /// public static bool TryRegisterCanvasElementForLayoutRebuild(ICanvasElement element) { return instance.InternalRegisterCanvasElementForLayoutRebuild(element); } private bool InternalRegisterCanvasElementForLayoutRebuild(ICanvasElement element) { if (m_LayoutRebuildQueue.Contains(element)) return false; /* TODO: this likely should be here but causes the error to show just resizing the game view (case 739376) if (m_PerformingLayoutUpdate) { Debug.LogError(string.Format("Trying to add {0} for layout rebuild while we are already inside a layout rebuild loop. This is not supported.", element)); return false; }*/ return m_LayoutRebuildQueue.AddUnique(element); } /// /// Try and add the given element to the rebuild list. /// Will not return if successfully added. /// /// The element that is needing rebuilt. public static void RegisterCanvasElementForGraphicRebuild(ICanvasElement element) { instance.InternalRegisterCanvasElementForGraphicRebuild(element); } /// /// Try and add the given element to the rebuild list. /// /// The element that is needing rebuilt. /// /// True if the element was successfully added to the rebuilt list. /// False if either already inside a Graphic Update loop OR has already been added to the list. /// public static bool TryRegisterCanvasElementForGraphicRebuild(ICanvasElement element) { return instance.InternalRegisterCanvasElementForGraphicRebuild(element); } private bool InternalRegisterCanvasElementForGraphicRebuild(ICanvasElement element) { if (m_PerformingGraphicUpdate) { Debug.LogError(string.Format("Trying to add {0} for graphic rebuild while we are already inside a graphic rebuild loop. This is not supported.", element)); return false; } return m_GraphicRebuildQueue.AddUnique(element); } /// /// Remove the given element from both the graphic and the layout rebuild lists. /// /// public static void UnRegisterCanvasElementForRebuild(ICanvasElement element) { instance.InternalUnRegisterCanvasElementForLayoutRebuild(element); instance.InternalUnRegisterCanvasElementForGraphicRebuild(element); } /// /// Disable the given element from both the graphic and the layout rebuild lists. /// /// public static void DisableCanvasElementForRebuild(ICanvasElement element) { instance.InternalDisableCanvasElementForLayoutRebuild(element); instance.InternalDisableCanvasElementForGraphicRebuild(element); } private void InternalUnRegisterCanvasElementForLayoutRebuild(ICanvasElement element) { if (m_PerformingLayoutUpdate) { Debug.LogError(string.Format("Trying to remove {0} from rebuild list while we are already inside a rebuild loop. This is not supported.", element)); return; } element.LayoutComplete(); instance.m_LayoutRebuildQueue.Remove(element); } private void InternalUnRegisterCanvasElementForGraphicRebuild(ICanvasElement element) { if (m_PerformingGraphicUpdate) { Debug.LogError(string.Format("Trying to remove {0} from rebuild list while we are already inside a rebuild loop. This is not supported.", element)); return; } element.GraphicUpdateComplete(); instance.m_GraphicRebuildQueue.Remove(element); } private void InternalDisableCanvasElementForLayoutRebuild(ICanvasElement element) { if (m_PerformingLayoutUpdate) { Debug.LogError(string.Format("Trying to remove {0} from rebuild list while we are already inside a rebuild loop. This is not supported.", element)); return; } element.LayoutComplete(); instance.m_LayoutRebuildQueue.DisableItem(element); } private void InternalDisableCanvasElementForGraphicRebuild(ICanvasElement element) { if (m_PerformingGraphicUpdate) { Debug.LogError(string.Format("Trying to remove {0} from rebuild list while we are already inside a rebuild loop. This is not supported.", element)); return; } element.GraphicUpdateComplete(); instance.m_GraphicRebuildQueue.DisableItem(element); } /// /// Are graphics layouts currently being calculated.. /// /// True if the rebuild loop is CanvasUpdate.Prelayout, CanvasUpdate.Layout or CanvasUpdate.Postlayout public static bool IsRebuildingLayout() { return instance.m_PerformingLayoutUpdate; } /// /// Are graphics currently being rebuild. /// /// True if the rebuild loop is CanvasUpdate.PreRender or CanvasUpdate.Render public static bool IsRebuildingGraphics() { return instance.m_PerformingGraphicUpdate; } } }