-
Notifications
You must be signed in to change notification settings - Fork 1.8k
/
pipeline.js
407 lines (374 loc) · 14.6 KB
/
pipeline.js
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
/****************************************************************************
Copyright (c) 2013-2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
https://www.cocos.com/
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated engine source code (the "Software"), a limited,
worldwide, royalty-free, non-assignable, revocable and non-exclusive license
to use Cocos Creator solely to develop games on your target platforms. You shall
not use Cocos Creator software for developing other software or tools that's
used for developing games. You are not granted to publish, distribute,
sublicense, and/or sell copies of Cocos Creator.
The software or tools in this License Agreement are licensed, not sold.
Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
var js = require('../platform/js');
var LoadingItems = require('./loading-items');
var ItemState = LoadingItems.ItemState;
function flow (pipe, item) {
var pipeId = pipe.id;
var itemState = item.states[pipeId];
var next = pipe.next;
var pipeline = pipe.pipeline;
if (item.error || itemState === ItemState.WORKING || itemState === ItemState.ERROR) {
return;
}
else if (itemState === ItemState.COMPLETE) {
if (next) {
flow(next, item);
}
else {
pipeline.flowOut(item);
}
}
else {
item.states[pipeId] = ItemState.WORKING;
// Pass async callback in case it's a async call
var result = pipe.handle(item, function (err, result) {
if (err) {
item.error = err;
item.states[pipeId] = ItemState.ERROR;
pipeline.flowOut(item);
}
else {
// Result can be null, then it means no result for this pipe
if (result) {
item.content = result;
}
item.states[pipeId] = ItemState.COMPLETE;
if (next) {
flow(next, item);
}
else {
pipeline.flowOut(item);
}
}
});
// If result exists (not undefined, null is ok), then we go with sync call flow
if (result instanceof Error) {
item.error = result;
item.states[pipeId] = ItemState.ERROR;
pipeline.flowOut(item);
}
else if (result !== undefined) {
// Result can be null, then it means no result for this pipe
if (result !== null) {
item.content = result;
}
item.states[pipeId] = ItemState.COMPLETE;
if (next) {
flow(next, item);
}
else {
pipeline.flowOut(item);
}
}
}
}
/**
* !#en
* A pipeline describes a sequence of manipulations, each manipulation is called a pipe.<br/>
* It's designed for loading process. so items should be urls, and the url will be the identity of each item during the process.<br/>
* A list of items can flow in the pipeline and it will output the results of all pipes.<br/>
* They flow in the pipeline like water in tubes, they go through pipe by pipe separately.<br/>
* Finally all items will flow out the pipeline and the process is finished.
*
* !#zh
* pipeline 描述了一系列的操作,每个操作都被称为 pipe。<br/>
* 它被设计来做加载过程的流程管理。所以 item 应该是 url,并且该 url 将是在处理中的每个 item 的身份标识。<br/>
* 一个 item 列表可以在 pipeline 中流动,它将输出加载项经过所有 pipe 之后的结果。<br/>
* 它们穿过 pipeline 就像水在管子里流动,将会按顺序流过每个 pipe。<br/>
* 最后当所有加载项都流出 pipeline 时,整个加载流程就结束了。
* @class Pipeline
*/
/**
* !#en
* Constructor, pass an array of pipes to construct a new Pipeline,
* the pipes will be chained in the given order.<br/>
* A pipe is an object which must contain an `id` in string and a `handle` function,
* the id must be unique in the pipeline.<br/>
* It can also include `async` property to identify whether it's an asynchronous process.
* !#zh
* 构造函数,通过一系列的 pipe 来构造一个新的 pipeline,pipes 将会在给定的顺序中被锁定。<br/>
* 一个 pipe 就是一个对象,它包含了字符串类型的 ‘id’ 和 ‘handle’ 函数,在 pipeline 中 id 必须是唯一的。<br/>
* 它还可以包括 ‘async’ 属性以确定它是否是一个异步过程。
*
* @method constructor
* @param {Array} pipes
* @example
* var pipeline = new Pipeline([
* {
* id: 'Downloader',
* handle: function (item, callback) {},
* async: true
* },
* {id: 'Parser', handle: function (item) {}, async: false}
* ]);
*/
var Pipeline = function (pipes) {
this._pipes = pipes;
this._cache = js.createMap(true);
for (var i = 0; i < pipes.length; ++i) {
var pipe = pipes[i];
// Must have handle and id, handle for flow, id for state flag
if (!pipe.handle || !pipe.id) {
continue;
}
pipe.pipeline = this;
pipe.next = i < pipes.length - 1 ? pipes[i+1] : null;
}
};
Pipeline.ItemState = ItemState;
var proto = Pipeline.prototype;
/**
* !#en
* Insert a new pipe at the given index of the pipeline. <br/>
* A pipe must contain an `id` in string and a `handle` function, the id must be unique in the pipeline.
* !#zh
* 在给定的索引位置插入一个新的 pipe。<br/>
* 一个 pipe 必须包含一个字符串类型的 ‘id’ 和 ‘handle’ 函数,该 id 在 pipeline 必须是唯一标识。
* @method insertPipe
* @param {Object} pipe The pipe to be inserted
* @param {Number} index The index to insert
*/
proto.insertPipe = function (pipe, index) {
// Must have handle and id, handle for flow, id for state flag
if (!pipe.handle || !pipe.id || index > this._pipes.length) {
cc.warnID(4921);
return;
}
if (this._pipes.indexOf(pipe) > 0) {
cc.warnID(4922);
return;
}
pipe.pipeline = this;
var nextPipe = null;
if (index < this._pipes.length) {
nextPipe = this._pipes[index];
}
var previousPipe = null;
if (index > 0) {
previousPipe = this._pipes[index-1];
}
if (previousPipe) {
previousPipe.next = pipe;
}
pipe.next = nextPipe;
this._pipes.splice(index, 0, pipe);
};
/**
* !en
* Insert a pipe to the end of an existing pipe. The existing pipe must be a valid pipe in the pipeline.
* !zh
* 在当前 pipeline 的一个已知 pipe 后面插入一个新的 pipe。
* @method insertPipeAfter
* @param {Object} refPipe An existing pipe in the pipeline.
* @param {Object} newPipe The pipe to be inserted.
*/
proto.insertPipeAfter = function (refPipe, newPipe) {
var index = this._pipes.indexOf(refPipe);
if (index < 0) {
return;
}
this.insertPipe(newPipe, index+1);
};
/**
* !#en
* Add a new pipe at the end of the pipeline. <br/>
* A pipe must contain an `id` in string and a `handle` function, the id must be unique in the pipeline.
* !#zh
* 添加一个新的 pipe 到 pipeline 尾部。 <br/>
* 该 pipe 必须包含一个字符串类型 ‘id’ 和 ‘handle’ 函数,该 id 在 pipeline 必须是唯一标识。
* @method appendPipe
* @param {Object} pipe The pipe to be appended
*/
proto.appendPipe = function (pipe) {
// Must have handle and id, handle for flow, id for state flag
if (!pipe.handle || !pipe.id) {
return;
}
pipe.pipeline = this;
pipe.next = null;
if (this._pipes.length > 0) {
this._pipes[this._pipes.length - 1].next = pipe;
}
this._pipes.push(pipe);
};
/**
* !#en
* Let new items flow into the pipeline. <br/>
* Each item can be a simple url string or an object,
* if it's an object, it must contain `id` property. <br/>
* You can specify its type by `type` property, by default, the type is the extension name in url. <br/>
* By adding a `skips` property including pipe ids, you can skip these pipe. <br/>
* The object can contain any supplementary property as you want. <br/>
* !#zh
* 让新的 item 流入 pipeline 中。<br/>
* 这里的每个 item 可以是一个简单字符串类型的 url 或者是一个对象,
* 如果它是一个对象的话,他必须要包含 ‘id’ 属性。<br/>
* 你也可以指定它的 ‘type’ 属性类型,默认情况下,该类型是 ‘url’ 的后缀名。<br/>
* 也通过添加一个 包含 ‘skips’ 属性的 item 对象,你就可以跳过 skips 中包含的 pipe。<br/>
* 该对象可以包含任何附加属性。
* @method flowIn
* @param {Array} items
* @example
* pipeline.flowIn([
* 'res/Background.png',
* {
* id: 'res/scene.json',
* type: 'scene',
* name: 'scene',
* skips: ['Downloader']
* }
* ]);
*/
proto.flowIn = function (items) {
var i, pipe = this._pipes[0], item;
if (pipe) {
// Cache all items first, in case synchronous loading flow same item repeatly
for (i = 0; i < items.length; i++) {
item = items[i];
this._cache[item.id] = item;
}
for (i = 0; i < items.length; i++) {
item = items[i];
flow(pipe, item);
}
}
else {
for (i = 0; i < items.length; i++) {
this.flowOut(items[i]);
}
}
};
/*
* !#en
* Let new items flow into the pipeline and give a callback when the list of items are all completed. <br/>
* This is for loading dependencies for an existing item in flow, usually used in a pipe logic. <br/>
* For example, we have a loader for scene configuration file in JSON, the scene will only be fully loaded <br/>
* after all its dependencies are loaded, then you will need to use function to flow in all dependencies <br/>
* found in the configuration file, and finish the loader pipe only after all dependencies are loaded (in the callback).
* !#zh
* 让新 items 流入 pipeline 并且当 item 列表完成时进行回调函数。<br/>
* 这个 API 的使用通常是为了加载依赖项。<br/>
* 例如:<br/>
* 我们需要加载一个场景配置的 JSON 文件,该场景会将所有的依赖项全部都加载完毕以后,进行回调表示加载完毕。
* @method flowInDeps
* @deprecated since v1.3
* @param {Array} urlList
* @param {Function} callback
* @return {Array} Items accepted by the pipeline
*/
proto.flowInDeps = function (owner, urlList, callback) {
var deps = LoadingItems.create(this, function (errors, items) {
callback(errors, items);
items.destroy();
});
return deps.append(urlList, owner);
};
proto.flowOut = function (item) {
if (item.error) {
delete this._cache[item.id];
}
else if (!this._cache[item.id]) {
this._cache[item.id] = item;
}
item.complete = true;
LoadingItems.itemComplete(item);
};
/**
* !#en
* Copy the item states from one source item to all destination items. <br/>
* It's quite useful when a pipe generate new items from one source item,<br/>
* then you should flowIn these generated items into pipeline, <br/>
* but you probably want them to skip all pipes the source item already go through,<br/>
* you can achieve it with this API. <br/>
* <br/>
* For example, an unzip pipe will generate more items, but you won't want them to pass unzip or download pipe again.
* !#zh
* 从一个源 item 向所有目标 item 复制它的 pipe 状态,用于避免重复通过部分 pipe。<br/>
* 当一个源 item 生成了一系列新的 items 时很有用,<br/>
* 你希望让这些新的依赖项进入 pipeline,但是又不希望它们通过源 item 已经经过的 pipe,<br/>
* 但是你可能希望他们源 item 已经通过并跳过所有 pipes,<br/>
* 这个时候就可以使用这个 API。
* @method copyItemStates
* @param {Object} srcItem The source item
* @param {Array|Object} dstItems A single destination item or an array of destination items
*/
proto.copyItemStates = function (srcItem, dstItems) {
if (!(dstItems instanceof Array)) {
dstItems.states = srcItem.states;
return;
}
for (var i = 0; i < dstItems.length; ++i) {
dstItems[i].states = srcItem.states;
}
};
/**
* !#en Returns an item in pipeline.
* !#zh 根据 id 获取一个 item
* @method getItem
* @param {Object} id The id of the item
* @return {Object}
*/
proto.getItem = function (id) {
var item = this._cache[id];
if (!item)
return item;
// downloader.js downloadUuid
if (item.alias)
item = item.alias;
return item;
};
/**
* !#en Removes an completed item in pipeline.
* It will only remove the cache in the pipeline or loader, its dependencies won't be released.
* cc.loader provided another method to completely cleanup the resource and its dependencies,
* please refer to {{#crossLink "loader/release:method"}}cc.loader.release{{/crossLink}}
* !#zh 移除指定的已完成 item。
* 这将仅仅从 pipeline 或者 loader 中删除其缓存,并不会释放它所依赖的资源。
* cc.loader 中提供了另一种删除资源及其依赖的清理方法,请参考 {{#crossLink "loader/release:method"}}cc.loader.release{{/crossLink}}
* @method removeItem
* @param {Object} id The id of the item
* @return {Boolean} succeed or not
*/
proto.removeItem = function (id) {
var removed = this._cache[id];
if (removed && removed.complete) {
delete this._cache[id];
}
return removed;
};
/**
* !#en Clear the current pipeline, this function will clean up the items.
* !#zh 清空当前 pipeline,该函数将清理 items。
* @method clear
*/
proto.clear = function () {
for (var id in this._cache) {
var item = this._cache[id];
delete this._cache[id];
if (!item.complete) {
item.error = new Error('Canceled manually');
this.flowOut(item);
}
}
};
cc.Pipeline = module.exports = Pipeline;