1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
|
using System;
using System.Collections.Generic;
using UnityEngine.UI.Collections;
namespace UnityEngine.UI
{
// canvas的不同阶段,用来设置layout、graphic做不同的事情,具体看CanvasUpdateRegistry
// prelayout -> layout -> postlayout -> prerender -> laterender =>=>=> render
public enum CanvasUpdate
{
Prelayout = 0,
Layout = 1,
PostLayout = 2,
PreRender = 3,
LatePreRender = 4,
MaxUpdateValue = 5
}
public interface ICanvasElement
{
Transform transform { get; }
void Rebuild(CanvasUpdate executing);
void LayoutComplete();
void GraphicUpdateComplete();
// due to unity overriding null check
// we need this as something may not be null
// but may be destroyed
bool IsDestroyed();
}
// 需要注意整个游戏只有一个单例
public class CanvasUpdateRegistry
{
private static CanvasUpdateRegistry s_Instance;
private bool m_PerformingLayoutUpdate;
private bool m_PerformingGraphicUpdate;
private readonly IndexedSet<ICanvasElement> m_LayoutRebuildQueue = new IndexedSet<ICanvasElement>();
private readonly IndexedSet<ICanvasElement> m_GraphicRebuildQueue = new IndexedSet<ICanvasElement>();
protected CanvasUpdateRegistry()
{
// willRenderCanvases在渲染canvas之前调用,执行PerformUpdate来Rebuild处理m_LayoutRebuildQueue和m_GraphicRebuildQueue
Canvas.willRenderCanvases += PerformUpdate;
}
// 简单单例
public static CanvasUpdateRegistry instance
{
get
{
if (s_Instance == null)
s_Instance = new CanvasUpdateRegistry();
return s_Instance;
}
}
// 检查一下实现了ICanvasElement的对象合法性,实现了ICanvasElement接口的对象必须是Unity Object,
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.
for (int i = m_LayoutRebuildQueue.Count - 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();
}
}
for (int i = m_GraphicRebuildQueue.Count - 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<ICanvasElement> s_SortLayoutFunction = SortLayoutList;
// 在渲染canvas之前对canvas下的元素进行rebuild
// 进行 prelayout -> layout -> postlayout -> prerender -> LatePreRender 流程
private void PerformUpdate()
{
UISystemProfilerApi.BeginSample(UISystemProfilerApi.SampleType.Layout);
CleanInvalidItems();
m_PerformingLayoutUpdate = true;
// 重建layout
// 根据父节点从少到多排序,先布局父节点少的,由少到多,由内到外
m_LayoutRebuildQueue.Sort(s_SortLayoutFunction);
// prelayout -> layout -> postlayout
for (int i = 0; i <= (int)CanvasUpdate.PostLayout; i++) // 不同的阶段
{
for (int j = 0; j < m_LayoutRebuildQueue.Count; j++)
{
var rebuild = instance.m_LayoutRebuildQueue[j];
try
{
if (ObjectValidForUpdate(rebuild))
rebuild.Rebuild((CanvasUpdate)i);
}
catch (Exception e)
{
Debug.LogException(e, rebuild.transform);
}
}
}
// 布局完成
for (int i = 0; i < m_LayoutRebuildQueue.Count; ++i)
m_LayoutRebuildQueue[i].LayoutComplete();
// 清空队列,重置状态
instance.m_LayoutRebuildQueue.Clear();
m_PerformingLayoutUpdate = false;
// 做剔除+裁剪
// now layout is complete do culling...
ClipperRegistry.instance.Cull();
// 重建graphic
m_PerformingGraphicUpdate = true;
// prerender -> lateprerender
for (var i = (int)CanvasUpdate.PreRender; i < (int)CanvasUpdate.MaxUpdateValue; i++)
{
for (var k = 0; k < instance.m_GraphicRebuildQueue.Count; k++)
{
try
{
var element = instance.m_GraphicRebuildQueue[k];
if (ObjectValidForUpdate(element))
element.Rebuild((CanvasUpdate)i);
}
catch (Exception e)
{
Debug.LogException(e, instance.m_GraphicRebuildQueue[k].transform);
}
}
}
// graphic设置mesh和材质完成
for (int i = 0; i < m_GraphicRebuildQueue.Count; ++i)
m_GraphicRebuildQueue[i].GraphicUpdateComplete();
// 清空队列,重置状态
instance.m_GraphicRebuildQueue.Clear();
m_PerformingGraphicUpdate = false;
// 到了这里布局和网格、材质都设置完毕,后面canvas会渲染
// profiler不用管
UISystemProfilerApi.EndSample(UISystemProfilerApi.SampleType.Layout);
}
// 统计父节点个数
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);
}
public static bool IsRebuildingLayout()
{
return instance.m_PerformingLayoutUpdate;
}
public static bool IsRebuildingGraphics()
{
return instance.m_PerformingGraphicUpdate;
}
#region 将canvas elements注册到队列里的方法
public static void RegisterCanvasElementForLayoutRebuild(ICanvasElement element)
{
instance.InternalRegisterCanvasElementForLayoutRebuild(element);
}
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);
}
public static void RegisterCanvasElementForGraphicRebuild(ICanvasElement element)
{
instance.InternalRegisterCanvasElementForGraphicRebuild(element);
}
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);
}
public static void UnRegisterCanvasElementForRebuild(ICanvasElement element)
{
instance.InternalUnRegisterCanvasElementForLayoutRebuild(element);
instance.InternalUnRegisterCanvasElementForGraphicRebuild(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);
}
#endregion
}
}
|