-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathrdrfw_async_rendering_model.html
More file actions
466 lines (447 loc) · 41.2 KB
/
rdrfw_async_rendering_model.html
File metadata and controls
466 lines (447 loc) · 41.2 KB
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
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
<!DOCTYPE html>
<html class="writer-html5" lang="zh-CN" >
<head>
<meta charset="utf-8" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>4. Glamor 异步渲染模型 — Cocoa 文档</title>
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
<link rel="stylesheet" href="_static/css/theme.css" type="text/css" />
<link rel="stylesheet" href="_static/graphviz.css" type="text/css" />
<link rel="stylesheet" href="_static/styles.css" type="text/css" />
<!--[if lt IE 9]>
<script src="_static/js/html5shiv.min.js"></script>
<![endif]-->
<script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
<script src="_static/jquery.js"></script>
<script src="_static/underscore.js"></script>
<script src="_static/_sphinx_javascript_frameworks_compat.js"></script>
<script src="_static/doctools.js"></script>
<script src="_static/sphinx_highlight.js"></script>
<script src="_static/translations.js"></script>
<script src="_static/js/theme.js"></script>
<link rel="index" title="索引" href="genindex.html" />
<link rel="search" title="搜索" href="search.html" />
<link rel="next" title="5. 窗口系统与平台抽象" href="rdrfw_platform_abstraction.html" />
<link rel="prev" title="3. Glamor 渲染器简介" href="rdrfw_glamor_intro.html" />
</head>
<body class="wy-body-for-nav">
<div class="wy-grid-for-nav">
<nav data-toggle="wy-nav-shift" class="wy-nav-side">
<div class="wy-side-scroll">
<div class="wy-side-nav-search" >
<a href="index.html" class="icon icon-home"> Cocoa
</a>
<div role="search">
<form id="rtd-search-form" class="wy-form" action="search.html" method="get">
<input type="text" name="q" placeholder="在文档中搜索" />
<input type="hidden" name="check_keywords" value="yes" />
<input type="hidden" name="area" value="default" />
</form>
</div>
</div><div class="wy-menu wy-menu-vertical" data-spy="affix" role="navigation" aria-label="Navigation menu">
<p class="caption" role="heading"><span class="caption-text">Introduction</span></p>
<ul>
<li class="toctree-l1"><a class="reference internal" href="intro_introduction.html">1. Cocoa 项目简介</a></li>
<li class="toctree-l1"><a class="reference internal" href="intro_about_this.html">2. About(关于)</a></li>
</ul>
<p class="caption" role="heading"><span class="caption-text">JavaScript Runtime Environment</span></p>
<ul>
<li class="toctree-l1"><a class="reference internal" href="jsrt_es_module.html">1. ECMAScript 模块</a></li>
</ul>
<p class="caption" role="heading"><span class="caption-text">Rendering Framework</span></p>
<ul class="current">
<li class="toctree-l1"><a class="reference internal" href="rdrfw_overview.html">1. 渲染框架概览</a></li>
<li class="toctree-l1"><a class="reference internal" href="rdrfw_canvaskit_rendering_api.html">2. CanvasKit 绘图 API</a></li>
<li class="toctree-l1"><a class="reference internal" href="rdrfw_glamor_intro.html">3. Glamor 渲染器简介</a></li>
<li class="toctree-l1 current"><a class="current reference internal" href="#">4. Glamor 异步渲染模型</a><ul>
<li class="toctree-l2"><a class="reference internal" href="#id1">4.1. 概览</a></li>
<li class="toctree-l2"><a class="reference internal" href="#id2">4.2. 理解异步函数调用和信号槽</a><ul>
<li class="toctree-l3"><a class="reference internal" href="#concepts">4.2.1. Concepts</a></li>
<li class="toctree-l3"><a class="reference internal" href="#id3">4.2.2. 原生层实现</a></li>
<li class="toctree-l3"><a class="reference internal" href="#javascript">4.2.3. JavaScript 中的异步调用和信号槽</a></li>
</ul>
</li>
<li class="toctree-l2"><a class="reference internal" href="#api">4.3. 渲染线程控制相关 API</a></li>
<li class="toctree-l2"><a class="reference internal" href="#id4">4.4. 追踪渲染线程上的消息传递</a></li>
<li class="toctree-l2"><a class="reference internal" href="#inspectobject">4.5. 可异步调用对象上的 <code class="docutils literal notranslate"><span class="pre">inspectObject</span></code> 方法</a></li>
<li class="toctree-l2"><a class="reference internal" href="#id5">4.6. 线程池中的异步任务</a></li>
</ul>
</li>
<li class="toctree-l1"><a class="reference internal" href="rdrfw_platform_abstraction.html">5. 窗口系统与平台抽象</a></li>
</ul>
</div>
</div>
</nav>
<section data-toggle="wy-nav-shift" class="wy-nav-content-wrap"><nav class="wy-nav-top" aria-label="Mobile navigation menu" >
<i data-toggle="wy-nav-top" class="fa fa-bars"></i>
<a href="index.html">Cocoa</a>
</nav>
<div class="wy-nav-content">
<div class="rst-content">
<div role="navigation" aria-label="Page navigation">
<ul class="wy-breadcrumbs">
<li><a href="index.html" class="icon icon-home"></a> »</li>
<li><span class="section-number">4. </span>Glamor 异步渲染模型</li>
<li class="wy-breadcrumbs-aside">
<a href="_sources/rdrfw_async_rendering_model.rst.txt" rel="nofollow"> 查看页面源码</a>
</li>
</ul>
<hr/>
</div>
<div role="main" class="document" itemscope="itemscope" itemtype="http://schema.org/Article">
<div itemprop="articleBody">
<section id="glamor">
<h1><span class="section-number">4. </span>Glamor 异步渲染模型<a class="headerlink" href="#glamor" title="此标题的永久链接"></a></h1>
<section id="id1">
<h2><span class="section-number">4.1. </span>概览<a class="headerlink" href="#id1" title="此标题的永久链接"></a></h2>
<p>Cocoa 是一个多线程程序,除了由 libuv 启动的线程池外,Cocoa 中最为重要的线程可能就是
<em>渲染线程(Rendering Thread)</em> 了。
渲染线程负责了所有图形资源的获取、管理和释放,并实现了一系列异步渲染 API 和信号槽机制。
在 Cocoa 中,一切渲染相关的任务都由渲染线程异步执行,主线程无须等待渲染过程结束,同时,
所有用户输入事件都由渲染线程处理,并以信号量(并非 POSIX 中定义的信号量)的形式通知主线程。</p>
<p>总而言之,渲染线程实现了两个功能,异步渲染 API 和图形资源管理。</p>
</section>
<section id="id2">
<h2><span class="section-number">4.2. </span>理解异步函数调用和信号槽<a class="headerlink" href="#id2" title="此标题的永久链接"></a></h2>
<section id="concepts">
<h3><span class="section-number">4.2.1. </span>Concepts<a class="headerlink" href="#concepts" title="此标题的永久链接"></a></h3>
<p>我们首先介绍一些概念,在后文中会使用到:</p>
<dl class="simple">
<dt><em>操作(Operation)</em></dt><dd><p>指由主线程发起的函数调用,该调用并不会立刻执行,而是被提交到消息队列中,
在合适的时候会被渲染线程执行。操作的执行结果也会放回消息队列中,在合适的时候被主线程读取。</p>
</dd>
<dt><em>信号(Signal)和槽(Slot)</em></dt><dd><p>操作是由主线程主动发起的函数调用,而信号则是由渲染线程发起的一种通知机制。
函数对象可以连接(Connect)到一个信号上,这些函数就称为槽。当一个信号被发出(Emit)时,
所有连接到该信号的槽都会被调用,顺序不作保证,我们仅保证槽会在信号被接受的那个线程上被调用(通常是主线程)。</p>
</dd>
<dt><em>RenderHost</em></dt><dd><p>主线程的一方称为 <strong>RenderHost</strong> ,相对应的,渲染线程的一方称为 <strong>RenderClient</strong>.</p>
</dd>
</dl>
</section>
<section id="id3">
<h3><span class="section-number">4.2.2. </span>原生层实现<a class="headerlink" href="#id3" title="此标题的永久链接"></a></h3>
<div class="admonition note">
<p class="admonition-title">备注</p>
<p>没有 C++ 经验和多线程开发经验的读者可以跳过此部分,不影响后续的阅读。</p>
</div>
<p>实现异步 API 的基础是可靠的线程间通讯机制。GLAMOR 使用了 libuv 的 async handle
配合消息队列来实现线程间通讯。这一套机制比较复杂,下图基本阐明了它的工作原理:</p>
<img alt="_images/async_rendering_thread_model.svg" src="_images/async_rendering_thread_model.svg" /><p>如图所示,渲染线程和主线程都分别有自己的事件循环和消息队列。红色字体标记的是可以引起整个流程的操作,
分别是主线程进行异步函数调用的 <code class="docutils literal notranslate"><span class="pre">Invoke</span></code> 函数和渲染线程发出信号的 <code class="docutils literal notranslate"><span class="pre">Emit</span></code> 函数。</p>
<p>当 <code class="docutils literal notranslate"><span class="pre">Invoke</span></code> 函数被调用时,在主线程上将调用相关的所有信息包装为 <code class="docutils literal notranslate"><span class="pre">RenderHostInvocation</span></code> 对象,
并将其推入渲染线程的消息队列,接着立刻通知渲染线程的事件循环。完成这些工作后,<code class="docutils literal notranslate"><span class="pre">Invoke</span></code> 函数返回。
这是异步函数调用的调用阶段。</p>
<p>在渲染线程中,事件循环负责调度来自主线程的通知,在合适的时候会触发渲染线程上的处理函数 <code class="docutils literal notranslate"><span class="pre">OnInvocationFromHost</span></code>,
接着该函数从消息队列中取出 <code class="docutils literal notranslate"><span class="pre">RenderHostInvocation</span></code> 对象,根据其中传递来的信息调用真正的处理函数,
处理函数完成后,<code class="docutils literal notranslate"><span class="pre">RenderHostInvocation</span></code> 的状态被更新(存入返回状态和返回值),
通过 <code class="docutils literal notranslate"><span class="pre">WakeupHost</span></code> 函数将更新后的对象推入主线程的消息队列(此时 <code class="docutils literal notranslate"><span class="pre">RenderHostInvocation</span></code> 被
转换为基类类型 <code class="docutils literal notranslate"><span class="pre">RenderClientTransfer</span></code>),并立即通知主线程的事件循环。
这是异步函数调用的返回阶段,也称为响应(Response)。</p>
<p>对于信号量,也遵循类似的过程,由 <code class="docutils literal notranslate"><span class="pre">Emit</span></code> 函数将有关信号的所有信息包装为 <code class="docutils literal notranslate"><span class="pre">RenderClientSignalEmit</span></code>
对象,然后同样由 <code class="docutils literal notranslate"><span class="pre">WakeupHost</span></code> 函数将该对象推入主线程的消息队列,并立即通知主线程的事件循环。</p>
<p>同样,主线程的事件循环也会在合适的时机调用对应的处理函数 <code class="docutils literal notranslate"><span class="pre">OnResponseFromClient</span></code>,
该函数将判断从消息队列取出的 <code class="docutils literal notranslate"><span class="pre">RenderClientTransfer</span></code> 对象的类型,并分别调用对应的函数来处理。</p>
<p>对于异步函数调用的返回数据,将从 <code class="docutils literal notranslate"><span class="pre">RenderClientTransfer</span></code> 中解出数据,转移到 <code class="docutils literal notranslate"><span class="pre">RenderHostCallbackInfo</span></code>
中,该对象将直接被传递给用户在调用时注册的用于接收返回值的回调函数。</p>
<p>对于信号量,将从 <code class="docutils literal notranslate"><span class="pre">RenderClientTransfer</span></code> 中解出数据,转移到 <code class="docutils literal notranslate"><span class="pre">RenderHostSlotCallbackInfo</span></code> 中,
该对象将直接被传递给用户注册的槽函数。</p>
</section>
<section id="javascript">
<h3><span class="section-number">4.2.3. </span>JavaScript 中的异步调用和信号槽<a class="headerlink" href="#javascript" title="此标题的永久链接"></a></h3>
<p>在异步函数调用和信号槽这两大异步机制中,有两个主要角色:RenderHost 和 RenderClient,
RenderHost 是对主线程(JavaScript 线程)的抽象,而 RenderClient 则是对渲染线程的抽象。</p>
<p>RenderHost 可以对 RenderClient 发起函数调用,但是该函数调用并不实际在 RenderHost 上执行,
而是在 RenderClient 上执行,这是一种「远程」调用,而 RenderHost 可以等待 RenderClient 执行完毕,
并像普通函数调用那样取得结果。在 JavaScript 中,这表现为该函数调用以 <code class="docutils literal notranslate"><span class="pre">Promise</span></code> 对象的形式返回,
用户可以使用 <code class="docutils literal notranslate"><span class="pre">await</span></code> 或 <code class="docutils literal notranslate"><span class="pre">then</span></code> 来处理返回值。如果函数调用中发生错误,上一小节中提到的 <code class="docutils literal notranslate"><span class="pre">RenderClientTransfer</span></code>
对象会携带着错误信息一同返回给主线程,在 JavaScript 中表现为 promise 的 reject。</p>
<p>该实例演示了一个最简单的异步函数调用(忽略错误处理):</p>
<div class="highlight-javascript notranslate"><div class="highlight"><pre><span></span><span class="c1">// 假设 Adder.add 是一个异步函数调用,它返回两个数相加的结果</span><span class="w"></span>
<span class="kd">const</span><span class="w"> </span><span class="nx">result</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">await</span><span class="w"> </span><span class="nx">Adder</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="mf">1</span><span class="p">,</span><span class="w"> </span><span class="mf">2</span><span class="p">);</span><span class="w"></span>
<span class="nx">std</span><span class="p">.</span><span class="nx">print</span><span class="p">(</span><span class="sb">`Result is </span><span class="si">${</span><span class="nx">result</span><span class="si">}</span><span class="sb">\n`</span><span class="p">);</span><span class="w"></span>
<span class="c1">// 或者使用 then 来处理:</span><span class="w"></span>
<span class="nx">Adder</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="mf">1</span><span class="p">,</span><span class="w"> </span><span class="mf">2</span><span class="p">).</span><span class="nx">then</span><span class="p">((</span><span class="nx">value</span><span class="p">)</span><span class="w"> </span><span class="p">=></span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="nx">std</span><span class="p">.</span><span class="nx">print</span><span class="p">(</span><span class="sb">`Result is </span><span class="si">${</span><span class="nx">value</span><span class="si">}</span><span class="sb">\n`</span><span class="p">);</span><span class="w"></span>
<span class="p">});</span><span class="w"></span>
</pre></div>
</div>
<p>有一些异步函数调用没有返回值,在 JavaScript 中表现为它返回一个 <code class="docutils literal notranslate"><span class="pre">Promise<void></span></code> 对象,
这种情况下,该 promise 的 resolve 表示异步函数调用已成功完成。</p>
<p>对于信号量而言,它用于 RenderClient 主动地通知 RenderHost,
在 JavaScript 中表现为回调函数被主动调用。</p>
<p>在 JavaScript 中,信号量是依托于对象存在的,如果一个对象继承自 <code class="docutils literal notranslate"><span class="pre">RenderClientObject</span></code> 对象,
则它就可以使用信号槽机制。在这些对象上,都有一个 <code class="docutils literal notranslate"><span class="pre">connect</span></code> 和 <code class="docutils literal notranslate"><span class="pre">disconnect</span></code> 方法,
前者用于将回调函数(槽)连接到一个特定的信号,后者用于断开连接:</p>
<div class="highlight-javascript notranslate"><div class="highlight"><pre><span></span><span class="c1">// 创建一个对象,这体现了信号槽机制是依托于对象存在的</span><span class="w"></span>
<span class="c1">// (仅作为示例,实际 Cocoa 中不存在内建的名为 Emitter 的对象)</span><span class="w"></span>
<span class="kd">let</span><span class="w"> </span><span class="nx">emitter</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="ow">new</span><span class="w"> </span><span class="nx">Emitter</span><span class="p">();</span><span class="w"></span>
<span class="c1">// 假设 Emitter 对象具有信号量 notice,将一个回调函数连接到这个信号量上</span><span class="w"></span>
<span class="kd">const</span><span class="w"> </span><span class="nx">connection</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">emitter</span><span class="p">.</span><span class="nx">connect</span><span class="p">(</span><span class="s1">'notice'</span><span class="p">,</span><span class="w"> </span><span class="p">(</span><span class="nx">content</span><span class="p">)</span><span class="w"> </span><span class="p">=></span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="c1">// 信号量携带一些参数,它们会直接被传递给槽函数,例如这里的 content</span><span class="w"></span>
<span class="w"> </span><span class="nx">std</span><span class="p">.</span><span class="nx">print</span><span class="p">(</span><span class="sb">`Notice: </span><span class="si">${</span><span class="nx">content</span><span class="si">}</span><span class="sb">\n`</span><span class="p">);</span><span class="w"></span>
<span class="p">});</span><span class="w"></span>
<span class="c1">// 同一个对象的同一个信号量上可以连接多个槽</span><span class="w"></span>
<span class="nx">emitter</span><span class="p">.</span><span class="nx">connect</span><span class="p">(</span><span class="s1">'notice'</span><span class="p">,</span><span class="w"> </span><span class="p">(</span><span class="nx">content</span><span class="p">)</span><span class="w"> </span><span class="p">=></span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="c1">// 执行一些其它动作</span><span class="w"></span>
<span class="p">});</span><span class="w"></span>
<span class="c1">// 随时可以断开槽的连接</span><span class="w"></span>
<span class="nx">emitter</span><span class="p">.</span><span class="nx">disconnect</span><span class="p">(</span><span class="nx">connection</span><span class="p">);</span><span class="w"></span>
</pre></div>
</div>
<div class="admonition warning">
<p class="admonition-title">警告</p>
<p>尽管同一个对象上的同一个信号量上可以连接多个槽,但是这些槽函数的调用顺序是没有任何保证的,
用户不应当假设任何槽函数被调用的顺序。</p>
</div>
</section>
</section>
<section id="api">
<h2><span class="section-number">4.3. </span>渲染线程控制相关 API<a class="headerlink" href="#api" title="此标题的永久链接"></a></h2>
<div class="admonition note">
<p class="admonition-title">备注</p>
<p>关于 API 的详细信息,读者参见 API 文档。本文档不会详细介绍每个 API。
此处介绍一些 API 是为了说明它们应该如何、在何时被使用。</p>
</div>
<p>在 <code class="docutils literal notranslate"><span class="pre">RenderHost</span></code> 对象上有数个静态方法,用于控制渲染线程的行为和线程间同步。</p>
<p>使用 <code class="docutils literal notranslate"><span class="pre">WaitForSyncBarrier</span></code> 方法可以使当前线程阻塞,直到渲染线程将目前为止所有的消息处理完毕。
该函数引发的阻塞是真正意义上的阻塞,而不是在事件循环中等待,整个 JavaScript 线程会被暂停
(如果当前 JavaScript 运行时连接到了 Inspector,则 Inspector 也会暂停响应),
直到渲染线程清空消息队列或达到最大等待时间。</p>
<div class="admonition note">
<p class="admonition-title">备注</p>
<p>如果阻塞期间有来自渲染线程的信号量,它们会在解除阻塞后被处理,且严格按照信号被发送的顺序处理。</p>
</div>
<p>该方法用于显式地等待渲染线程完成先前提交的所有请求,若渲染线程被大量的请求占用,
调用该方法可以暂停主线程来缓解渲染线程的压力。在一些调试情景下,该函数也十分有用。</p>
<p>另一个需要介绍的方法是 <code class="docutils literal notranslate"><span class="pre">SleepRendererFor</span></code> ,该函数可以使渲染线程阻塞一定的时间,
用以模拟渲染线程暂时失去响应的状态。需要注意的是,该函数并不会立刻使渲染线程阻塞,
而是等待先前发送的请求都处理完毕后再阻塞(可以认为阻塞也是一种特殊的请求,
也需要在消息队列中等待处理)。<code class="docutils literal notranslate"><span class="pre">SleepRendererFor</span></code> 函数返回一个 <code class="docutils literal notranslate"><span class="pre">Promise</span></code>
对象,当渲染线程从阻塞状态退出并恢复正常工作时,该 promise 被 resolve.</p>
<div class="admonition note">
<p class="admonition-title">备注</p>
<p>如果阻塞期间主线程又对渲染线程发送了消息(异步函数调用),它们会在解除阻塞后被正确处理。</p>
</div>
<div class="admonition warning">
<p class="admonition-title">警告</p>
<p>此函数仅用于测试等特殊目的,长时间阻塞渲染线程可能导致意料之外的错误。</p>
</div>
</section>
<section id="id4">
<h2><span class="section-number">4.4. </span>追踪渲染线程上的消息传递<a class="headerlink" href="#id4" title="此标题的永久链接"></a></h2>
<p>在 Cocoa 启动时添加 <code class="docutils literal notranslate"><span class="pre">--gl-transfer-queue-profile</span></code>
命令行选项可以生成对渲染线程消息队列的 profiling 报告,当 Cocoa 正常退出后,
会在工作目录生成一个 <code class="docutils literal notranslate"><span class="pre">transfer-profiling-PID.json</span></code> 文件,以 JSON 格式保存了 profiling 结果。</p>
<p>该文件遵循如下格式:</p>
<div class="highlight-json notranslate"><div class="highlight"><pre><span></span><span class="linenos">1</span><span class="p">{</span><span class="w"></span>
<span class="linenos">2</span><span class="w"> </span><span class="nt">"samples"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w"> </span><span class="cm">/* Something */</span><span class="w"> </span><span class="p">],</span><span class="w"></span>
<span class="linenos">3</span><span class="w"> </span><span class="nt">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"GLAMOR Message Queue Profiling"</span><span class="p">,</span><span class="w"></span>
<span class="linenos">4</span><span class="w"> </span><span class="nt">"version"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">]</span><span class="w"></span>
<span class="linenos">5</span><span class="p">}</span><span class="w"></span>
</pre></div>
</div>
<p><code class="docutils literal notranslate"><span class="pre">samples</span></code> 字段部分是一个对象数组,其中的每一个对象称为一个 Sample(样本),
每个 Sample 就代表消息队列中的一次消息传递(一次异步函数调用或者一次信号量传递)。</p>
<p>因此自然地,Sample 有两种类型,分别代表异步函数调用和信号量传递,如果该 Sample
是一次异步函数调用,它具有如下格式:</p>
<div class="highlight-json notranslate"><div class="highlight"><pre><span></span><span class="linenos"> 1</span><span class="p">{</span><span class="w"></span>
<span class="linenos"> 2</span><span class="w"> </span><span class="nt">"milestones"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="linenos"> 3</span><span class="w"> </span><span class="nt">"ClientFeedback"</span><span class="p">:</span><span class="w"> </span><span class="mi">9735</span><span class="p">,</span><span class="w"></span>
<span class="linenos"> 4</span><span class="w"> </span><span class="nt">"ClientProcessed"</span><span class="p">:</span><span class="w"> </span><span class="mi">9732</span><span class="p">,</span><span class="w"></span>
<span class="linenos"> 5</span><span class="w"> </span><span class="nt">"ClientReceived"</span><span class="p">:</span><span class="w"> </span><span class="mi">123</span><span class="p">,</span><span class="w"></span>
<span class="linenos"> 6</span><span class="w"> </span><span class="nt">"HostConstruction"</span><span class="p">:</span><span class="w"> </span><span class="mi">107</span><span class="p">,</span><span class="w"></span>
<span class="linenos"> 7</span><span class="w"> </span><span class="nt">"HostEnqueued"</span><span class="p">:</span><span class="w"> </span><span class="mi">110</span><span class="p">,</span><span class="w"></span>
<span class="linenos"> 8</span><span class="w"> </span><span class="nt">"HostReceived"</span><span class="p">:</span><span class="w"> </span><span class="mi">9771</span><span class="w"></span>
<span class="linenos"> 9</span><span class="w"> </span><span class="p">},</span><span class="w"></span>
<span class="linenos">10</span><span class="w"> </span><span class="nt">"opcode"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"></span>
<span class="linenos">11</span><span class="w"> </span><span class="nt">"receiver"</span><span class="p">:</span><span class="w"> </span><span class="s2">"0x7fc3ef64ebd0"</span><span class="p">,</span><span class="w"></span>
<span class="linenos">12</span><span class="w"> </span><span class="nt">"receiverType"</span><span class="p">:</span><span class="w"> </span><span class="s2">"RenderHostCreator"</span><span class="p">,</span><span class="w"></span>
<span class="linenos">13</span><span class="w"> </span><span class="nt">"returnStatus"</span><span class="p">:</span><span class="w"> </span><span class="s2">"OpSuccess"</span><span class="p">,</span><span class="w"></span>
<span class="linenos">14</span><span class="w"> </span><span class="nt">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Invocation"</span><span class="w"></span>
<span class="linenos">15</span><span class="p">}</span><span class="w"></span>
</pre></div>
</div>
<dl>
<dt><em>milestones</em></dt><dd><p>包含一系列时间戳,其数值表示自渲染线程启动开始到记录该时间戳所经历的微秒数。
各个时间戳的意义如下(按其发生的时间先后顺序排列):</p>
<table class="docutils align-default">
<colgroup>
<col style="width: 35%" />
<col style="width: 65%" />
</colgroup>
<thead>
<tr class="row-odd"><th class="head"><p>STAMP</p></th>
<th class="head"><p>Meaning</p></th>
</tr>
</thead>
<tbody>
<tr class="row-even"><td><p>HostConstruction</p></td>
<td><p>RenderHost 构造消息对象</p></td>
</tr>
<tr class="row-odd"><td><p>HostEnqueued</p></td>
<td><p>RenderHost 将消息推入消息队列</p></td>
</tr>
<tr class="row-even"><td><p>ClientReceived</p></td>
<td><p>RenderClient 从消息队列中取出消息</p></td>
</tr>
<tr class="row-odd"><td><p>ClientProcessed</p></td>
<td><p>RenderClient 完成消息处理</p></td>
</tr>
<tr class="row-even"><td><p>ClientFeedback</p></td>
<td><p>RenderClient 将消息返回值推入消息队列</p></td>
</tr>
<tr class="row-odd"><td><p>HostReceived</p></td>
<td><p>RenderHost 从消息队列中取出返回值</p></td>
</tr>
</tbody>
</table>
</dd>
<dt><em>opcode</em></dt><dd><p>保存了该操作的操作码,可以理解为异步函数调用的函数名,
有关操作码的详细信息属于 Cocoa 原生层的内容,
读者可参见 Cocoa 源码中的 <code class="docutils literal notranslate"><span class="pre">GLOP_*</span></code> 系列宏定义。</p>
</dd>
<dt><em>receiver</em></dt><dd><p>消息接收者(C++ 对象)的内存地址,用于调试目的。</p>
</dd>
<dt><em>receiverType</em></dt><dd><p>消息接收者的类型(C++ 类型)。</p>
</dd>
<dt><em>returnStatus</em></dt><dd><p>返回状态,有多种:</p>
<table class="docutils align-default">
<colgroup>
<col style="width: 38%" />
<col style="width: 63%" />
</colgroup>
<thead>
<tr class="row-odd"><th class="head"><p>Return Status</p></th>
<th class="head"><p>Meaning</p></th>
</tr>
</thead>
<tbody>
<tr class="row-even"><td><p>Pending</p></td>
<td><p>悬而未决的,即消息还没有被处理</p></td>
</tr>
<tr class="row-odd"><td><p>OpCodeInvalid</p></td>
<td><p>无效的 opcode</p></td>
</tr>
<tr class="row-even"><td><p>ArgsInvalid</p></td>
<td><p>无效的参数</p></td>
</tr>
<tr class="row-odd"><td><p>Caught</p></td>
<td><p>调用过程中有异常被抛出</p></td>
</tr>
<tr class="row-even"><td><p>OpSuccess</p></td>
<td><p>操作成功</p></td>
</tr>
<tr class="row-odd"><td><p>OpFailed</p></td>
<td><p>操作失败</p></td>
</tr>
</tbody>
</table>
<p>不难理解,profiling 中不会出现 Pending 这种情况。</p>
</dd>
<dt><em>type</em></dt><dd><p>对于异步函数调用,恒为 <code class="docutils literal notranslate"><span class="pre">Invocation</span></code> 。</p>
</dd>
</dl>
<p>而对于信号量传递,Sample 具有如下格式:</p>
<div class="highlight-json notranslate"><div class="highlight"><pre><span></span><span class="linenos"> 1</span><span class="p">{</span><span class="w"></span>
<span class="linenos"> 2</span><span class="w"> </span><span class="nt">"emitter"</span><span class="p">:</span><span class="w"> </span><span class="s2">"0x7fc3d500a010"</span><span class="p">,</span><span class="w"></span>
<span class="linenos"> 3</span><span class="w"> </span><span class="nt">"emitterType"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Display"</span><span class="p">,</span><span class="w"></span>
<span class="linenos"> 4</span><span class="w"> </span><span class="nt">"milestones"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="linenos"> 5</span><span class="w"> </span><span class="nt">"ClientEmitted"</span><span class="p">:</span><span class="w"> </span><span class="mi">7625976</span><span class="p">,</span><span class="w"></span>
<span class="linenos"> 6</span><span class="w"> </span><span class="nt">"HostReceived"</span><span class="p">:</span><span class="w"> </span><span class="mi">7626054</span><span class="w"></span>
<span class="linenos"> 7</span><span class="w"> </span><span class="p">},</span><span class="w"></span>
<span class="linenos"> 8</span><span class="w"> </span><span class="nt">"signalCode"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"></span>
<span class="linenos"> 9</span><span class="w"> </span><span class="nt">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Signal"</span><span class="w"></span>
<span class="linenos">10</span><span class="p">}</span><span class="w"></span>
</pre></div>
</div>
<dl class="simple">
<dt><em>emitter</em></dt><dd><p>信号发出者(C++ 对象)的内存地址。</p>
</dd>
<dt><em>emitterType</em></dt><dd><p>信号发出者的类型(C++ 类型)。</p>
</dd>
<dt><em>milestones</em></dt><dd><p>含义与上文中描述的相同。</p>
</dd>
<dt><em>signalCode</em></dt><dd><p>信号编号,有关信号编号的内容属于 Cocoa 原生层的内容,
读者可以参见 Glamor 源码中的 <code class="docutils literal notranslate"><span class="pre">GLSI_*</span></code> 系列宏定义。
在 JavaScript 中,如果一个对象可以使用 <code class="docutils literal notranslate"><span class="pre">connect</span></code> 方法来连接信号量,
那么可以调用它的 <code class="docutils literal notranslate"><span class="pre">inspectObject</span></code> 方法来获得在该对象上的信号编号到信号名称的映射关系
(见下一小节)。</p>
</dd>
<dt><em>type</em></dt><dd><p>对于信号量传递,恒为 <code class="docutils literal notranslate"><span class="pre">Signal</span></code>。</p>
</dd>
</dl>
</section>
<section id="inspectobject">
<h2><span class="section-number">4.5. </span>可异步调用对象上的 <code class="docutils literal notranslate"><span class="pre">inspectObject</span></code> 方法<a class="headerlink" href="#inspectobject" title="此标题的永久链接"></a></h2>
<p>如果一个来自 Glamor 的 JavaScript 对象具有 <code class="docutils literal notranslate"><span class="pre">connect</span></code> 和 <code class="docutils literal notranslate"><span class="pre">disconnect</span></code>
方法(即它们继承于 <code class="docutils literal notranslate"><span class="pre">RenderClientObject</span></code> 对象),就可以调用这些对象上的
<code class="docutils literal notranslate"><span class="pre">inspectObject</span></code> 方法来获得有关此异步对象的信息。</p>
<p>该方法返回一个具有如下格式的 JavaScript 对象:</p>
<div class="highlight-javascript notranslate"><div class="highlight"><pre><span></span><span class="linenos"> 1</span><span class="p">{</span><span class="w"></span>
<span class="linenos"> 2</span><span class="w"> </span><span class="nx">objectType</span><span class="o">:</span><span class="w"> </span><span class="s2">"SomeType"</span><span class="p">,</span><span class="w"></span>
<span class="linenos"> 3</span><span class="w"> </span><span class="nx">signals</span><span class="o">:</span><span class="w"> </span><span class="p">[</span><span class="w"></span>
<span class="linenos"> 4</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="linenos"> 5</span><span class="w"> </span><span class="nx">name</span><span class="o">:</span><span class="w"> </span><span class="s2">"SignalName"</span><span class="p">,</span><span class="w"></span>
<span class="linenos"> 6</span><span class="w"> </span><span class="nx">code</span><span class="o">:</span><span class="w"> </span><span class="mf">1</span><span class="p">,</span><span class="w"></span>
<span class="linenos"> 7</span><span class="w"> </span><span class="nx">connectedCallbacks</span><span class="o">:</span><span class="w"> </span><span class="p">[</span><span class="w"> </span><span class="cm">/* Functions */</span><span class="w"> </span><span class="p">]</span><span class="w"></span>
<span class="linenos"> 8</span><span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="linenos"> 9</span><span class="w"> </span><span class="p">]</span><span class="w"></span>
<span class="linenos">10</span><span class="p">}</span><span class="w"></span>
</pre></div>
</div>
<p><code class="docutils literal notranslate"><span class="pre">objectType</span></code> 给出的类型名与 profiling 中 <code class="docutils literal notranslate"><span class="pre">emitterType</span></code> 和 <code class="docutils literal notranslate"><span class="pre">receiverType</span></code>
给出的类型名是一致的。同时也可以看到一个信号量的编号与名称之间的映射关系。</p>
<div class="admonition note">
<p class="admonition-title">备注</p>
<p>尽管 <code class="docutils literal notranslate"><span class="pre">objectType</span></code> 给出了一个类型名称,但该名称并不稳定,随着 Cocoa 版本的迭代更新,
这些类型名称在未来很可能会变化。</p>
</div>
</section>
<section id="id5">
<h2><span class="section-number">4.6. </span>线程池中的异步任务<a class="headerlink" href="#id5" title="此标题的永久链接"></a></h2>
<p>Glamor 中并非所有的异步任务都由渲染线程执行,一些涉及到文件读写的操作也是异步的,
但是它们可能交由线程池完成,例如 <code class="docutils literal notranslate"><span class="pre">CkImage.MakeFromEncodedFile</span></code> 方法,
这可能有助于用户利用线程池可并行的特性改善性能。例如,对于 <code class="docutils literal notranslate"><span class="pre">CkImage.MakeFromEncodedFile</span></code>
方法,用户如果需要在短时间内从文件系统中加载并解码大量图片,
就可以利用其在线程池中执行的特性来并行加载:</p>
<div class="highlight-javascript notranslate"><div class="highlight"><pre><span></span><span class="linenos">1</span><span class="kd">const</span><span class="w"> </span><span class="nx">imageFiles</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="w"> </span><span class="cm">/* many image files */</span><span class="w"> </span><span class="p">];</span><span class="w"></span>
<span class="linenos">2</span><span class="kd">const</span><span class="w"> </span><span class="nx">promises</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[];</span><span class="w"></span>
<span class="linenos">3</span><span class="k">for</span><span class="w"> </span><span class="p">(</span><span class="kd">const</span><span class="w"> </span><span class="nx">file</span><span class="w"> </span><span class="k">of</span><span class="w"> </span><span class="nx">imageFiles</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="linenos">4</span><span class="w"> </span><span class="nx">promises</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">GL</span><span class="p">.</span><span class="nx">CkImage</span><span class="p">.</span><span class="nx">MakeFromEncodedFile</span><span class="p">(</span><span class="nx">file</span><span class="p">));</span><span class="w"></span>
<span class="linenos">5</span><span class="p">}</span><span class="w"></span>
<span class="linenos">6</span><span class="nb">Promise</span><span class="p">.</span><span class="nx">all</span><span class="p">(</span><span class="nx">promises</span><span class="p">).</span><span class="nx">then</span><span class="p">((</span><span class="nx">images</span><span class="p">)</span><span class="w"> </span><span class="p">=></span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="linenos">7</span><span class="w"> </span><span class="nx">std</span><span class="p">.</span><span class="nx">print</span><span class="p">(</span><span class="s2">"Images has been loaded successfully\n"</span><span class="p">);</span><span class="w"></span>
<span class="linenos">8</span><span class="p">});</span><span class="w"></span>
</pre></div>
</div>
<p>经过测试,从文件系统中加载 300 张大小不一的图片,使用逐个串行加载的方式,平均耗时 2100ms;
而采用上述并行加载的方式(8 核 CPU,4 个工作线程),可以将耗时显著减小到 560ms.</p>
<p>无论如何,异步操作由哪一个线程完成对于用户来说是透明的,
Cocoa 总是保证这些异步操作会按照约定返回正确的结果,且不会出现冲突。</p>
</section>
</section>
</div>
</div>
<footer><div class="rst-footer-buttons" role="navigation" aria-label="Footer">
<a href="rdrfw_glamor_intro.html" class="btn btn-neutral float-left" title="3. Glamor 渲染器简介" accesskey="p" rel="prev"><span class="fa fa-arrow-circle-left" aria-hidden="true"></span> 上一页</a>
<a href="rdrfw_platform_abstraction.html" class="btn btn-neutral float-right" title="5. 窗口系统与平台抽象" accesskey="n" rel="next">下一页 <span class="fa fa-arrow-circle-right" aria-hidden="true"></span></a>
</div>
<hr/>
<div role="contentinfo">
<p>© 版权所有 2022, OpenACG Group.</p>
</div>
利用 <a href="https://www.sphinx-doc.org/">Sphinx</a> 构建,使用了
<a href="https://github.com/readthedocs/sphinx_rtd_theme">主题</a>
由 <a href="https://readthedocs.org">Read the Docs</a>开发.
</footer>
</div>
</div>
</section>
</div>
<script>
jQuery(function () {
SphinxRtdTheme.Navigation.enable(true);
});
</script>
</body>
</html>