mirror of
https://scm.univ-tours.fr/22107988t/rappaurio-sae501_502.git
synced 2025-08-29 08:45:58 +02:00
611 lines
17 KiB
JavaScript
611 lines
17 KiB
JavaScript
/**
|
||
* @name CeL function for thread
|
||
* @fileoverview 本檔案包含了 thread / process 流程控制的 functions。
|
||
* @since 2012/2/3 19:13:49
|
||
*/
|
||
|
||
'use strict';
|
||
// 'use asm';
|
||
|
||
// --------------------------------------------------------------------------------------------
|
||
|
||
// 不採用 if 陳述式,可以避免 Eclipse JSDoc 與 format 多縮排一層。
|
||
typeof CeL === 'function' && CeL.run({
|
||
name : 'data.code.thread',
|
||
|
||
require : 'data.code.compatibility.',
|
||
|
||
// 設定不匯出的子函式。
|
||
// 完全不 export 至 library_namespace.
|
||
no_extend : '*',
|
||
|
||
// 為了方便格式化程式碼,因此將 module 函式主體另外抽出。
|
||
code : module_code
|
||
});
|
||
|
||
function module_code(library_namespace) {
|
||
|
||
/**
|
||
* null module constructor
|
||
*
|
||
* @class thread 的 functions
|
||
*/
|
||
var _// JSDT:_module_
|
||
= function() {
|
||
// null module constructor
|
||
};
|
||
|
||
/**
|
||
* for JSDT: 有 prototype 才會將之當作 Class
|
||
*/
|
||
_// JSDT:_module_
|
||
.prototype = {
|
||
// constructor : _
|
||
};
|
||
|
||
// ----------------------------------------------------------------------------------------------------------------------------------------------------------//
|
||
|
||
var
|
||
/**
|
||
* const, 不可能為 setTimeout() id.
|
||
*/
|
||
is_running = [ 'is running' ],
|
||
/**
|
||
* const, 紀錄 process hook。
|
||
*/
|
||
Serial_execute_process = {},
|
||
/**
|
||
* const, Serial_execute core data 中,可被變更的值。
|
||
*/
|
||
Serial_execute_allow_to_set = {
|
||
interval : '{Integer}設定執行之週期間隔(ms)',
|
||
|
||
thread : '{Function}設定每次 loop 所欲執行之執行緒 (handler thread)。',
|
||
|
||
// 傳給 handler thread 之 this 與 arguments。
|
||
'this' : '在各 thread 間當作 this 傳遞的 data. "this" argument send to thread.',
|
||
argument : 'argument : 傳給 handler 之 arguments',
|
||
|
||
// loop 序號控制。單純輸入數字相當於 {start : 1, length : 自然數序號}
|
||
index : '{Integer}index : process to 哪一序號',
|
||
start : '{Integer}start from 哪一序號(index)',
|
||
// length, last 二選一。
|
||
length : '{Integer}執行 length 次',
|
||
last : '{Integer}執行至哪一序號(end index)',
|
||
|
||
// 設定流程控制用 signals。
|
||
stopped : '{Boolean}process stopped',
|
||
finished : '{Boolean}process finished',
|
||
terminated : '{Boolean}process terminated',
|
||
|
||
// 設定當 handler 產生錯誤時,是否繼續執行下去。Or stop on throw error of thread.
|
||
skip_throw : '{Boolean}skip throw of thread'
|
||
};
|
||
|
||
/**
|
||
* 設定循序執行(serial execution) 程序,並可以作 stop, resume 等流程控制 (inter-process
|
||
* communication)。<br />
|
||
* 本函數可代替迴圈操作 loop, for, setTimeout / setInterval,亦可避免長時間之迴圈操作被 browser
|
||
* 判別為耗時 loop 而 hang 住。<br />
|
||
* 可視作一種 iterator / forEach()。<br />
|
||
* This module use asynchronous method (e.g., setTimeout) to avoid the
|
||
* "Script Taking Too Long" message.
|
||
*
|
||
* TODO:<br />
|
||
* .result[], .next() = .step(), input array as option
|
||
*
|
||
* <code>
|
||
// 單 thread
|
||
var i=0,s=0;for(;i<100;i++)s+=Math.random();alert(s);
|
||
|
||
CeL.run('data.code.thread');
|
||
// 方法1
|
||
new CeL.Serial_execute(function(i, d) {d.s+=Math.random();}, {length: 100, first: function(d) {d.s=0;}, final: function(i, d) {alert(d.s);}});
|
||
// 方法2
|
||
new CeL.Serial_execute(function() {this.s+=Math.random();}, {length: 100, first: function() {this.s=0;}, final: function() {alert(this.s);}});
|
||
* </code>
|
||
*
|
||
* @param {Function}loop_thread
|
||
* loop_thread({Integer}process_to_index) {<br />
|
||
* return<br />
|
||
* 'SIGABRT': terminated (accident occurred), won't run
|
||
* options.final();<br />
|
||
* others(!0): all done<br /> }
|
||
*
|
||
* @param {Object}[options]
|
||
* 設定選項。<br /> {<br />
|
||
* {String}id : process id (有設定 id 則可以從
|
||
* Serial_execute.process(id) 控制。),<br />
|
||
* <br />
|
||
* {Integer}start : start from 哪一序號(index),<br />
|
||
* {Integer}index : process to 哪一序號,<br /> // length, last 二選一。<br />
|
||
* {Integer}length : 執行 length 次,<br />
|
||
* {Integer}last : 執行至哪一序號(end index),<br />
|
||
* <br />
|
||
* argument : 傳給 handler 之 arguments,<br />
|
||
* {Integer}interval : 週期間隔(ms),<br />
|
||
* {Function}first : run first,<br />
|
||
* {Function}final : run after all, 結尾, epilogue.<br />
|
||
* {Boolean}skip_throw : skip throw of thread(),<br /> }
|
||
*
|
||
* @returns {Serial_execute}process handler
|
||
*
|
||
* @constructor
|
||
*
|
||
* @see http://wiki.ecmascript.org/doku.php?id=strawman:async_functions
|
||
* http://support.mozilla.org/en-US/kb/warning-unresponsive-script
|
||
* http://support.microsoft.com/kb/175500
|
||
*
|
||
* @since 2012/2/3 18:38:02 初成。<br />
|
||
* 2012/2/4 12:31:53 包裝成物件。<br />
|
||
* 2012/11/16 19:30:53 re-write start。<br />
|
||
* 2012/11/23 23:51:44 re-write finished。<br />
|
||
* 2013/3/3 19:20:52 重新定義 options.length。<br />
|
||
*/
|
||
function Serial_execute(loop_thread, options) {
|
||
if (typeof loop_thread !== 'function')
|
||
return;
|
||
|
||
var tmp,
|
||
// (private) 行程間核心 data.
|
||
core_data = {
|
||
start_time : new Date,
|
||
// interval : 0,
|
||
thread : loop_thread,
|
||
skip_throw : false,
|
||
count : 0
|
||
};
|
||
|
||
// public interface.
|
||
// 處理初始化必要,且不允許被 loop_thread 改變的 methods/設定/狀態值.
|
||
this.get = function(name) {
|
||
if (name in core_data)
|
||
return core_data[name];
|
||
};
|
||
this.set = function(name, value) {
|
||
if (name in Serial_execute_allow_to_set) {
|
||
if (arguments.length > 1)
|
||
return core_data[name] = value;
|
||
else
|
||
delete core_data[name];
|
||
}
|
||
};
|
||
|
||
if (library_namespace.is_digits(options))
|
||
// 當作執行次數。
|
||
options = {
|
||
length : options
|
||
};
|
||
if (Array.isArray(options))
|
||
// 當作 Array.prototype.forEach()
|
||
options = {
|
||
list : options
|
||
};
|
||
|
||
// 處理 options 中與執行相關,且不允許被 loop_thread 改變的設定。
|
||
if (library_namespace.is_Object(options)) {
|
||
// 將 options 之 digits 設定 copy 到 core_data。
|
||
library_namespace.set_method(core_data, options, [ function(key) {
|
||
return !library_namespace.is_digits(options[key]);
|
||
}, 'start', 'last', 'index', 'interval' ]);
|
||
if (false)
|
||
library_namespace.extend(
|
||
[ 'start', 'last', 'index', 'interval' ], core_data,
|
||
options, function(i, v) {
|
||
return !library_namespace.is_digits(v);
|
||
});
|
||
|
||
if (!('last' in core_data) && options.length > 0) {
|
||
if (!library_namespace.is_digits(core_data.start))
|
||
core_data.start = 1;
|
||
core_data.last = core_data.start + options.length - 1;
|
||
}
|
||
// 從這邊起,.last 表示結束之序號。
|
||
|
||
// 將 options 之 digits 設定 copy 到 core_data。
|
||
library_namespace.set_method(core_data, options, [ 'skip_throw',
|
||
'final', 'this' ]);
|
||
if (false)
|
||
library_namespace.extend([ 'skip_throw', 'final', 'this' ],
|
||
core_data, options);
|
||
|
||
if ('argument' in options)
|
||
core_data.argument = Array.isArray(tmp = options.argument) ? tmp
|
||
: [ tmp ];
|
||
|
||
// data list.
|
||
if ('list' in options)
|
||
try {
|
||
// check if list is an array-like object, we can use []
|
||
// operator and .length.
|
||
var last = options.list.length - 1, test = options.list[last];
|
||
if (!library_namespace.is_digits(last))
|
||
// 確認 test 會被演算。
|
||
throw '' + test;
|
||
|
||
// 這邊不作 Array.prototype.slice.call(),讓 caller 可再作更動。
|
||
// 若希望保留 const,caller 需要自己作 Array.prototype.slice.call()。
|
||
core_data.list = options.list;
|
||
|
||
if (!library_namespace.is_digits(options.start))
|
||
// 若尚未設定 .start,則定為 0。
|
||
core_data.start = 0;
|
||
|
||
if (!library_namespace.is_digits(core_data.last))
|
||
core_data.last = last;
|
||
|
||
// add an item to list.
|
||
this.add = function(item) {
|
||
// 為確保為 generic method,不用 .push()!
|
||
core_data.list[++core_data.last] = item;
|
||
};
|
||
// 取得/設定當前 index。
|
||
this.index = function(index) {
|
||
if (library_namespace.is_digits(index))
|
||
core_data.index = index;
|
||
else
|
||
return core_data.index;
|
||
};
|
||
|
||
} catch (e) {
|
||
if (library_namespace.is_Object(options.list)) {
|
||
// TODO
|
||
}
|
||
}
|
||
|
||
if (options.record) {
|
||
// 執行結果將會依序置於此 Array。
|
||
core_data.result = [];
|
||
}
|
||
|
||
// 登記 process id.
|
||
if (tmp = options.id) {
|
||
if (tmp in Serial_execute_process)
|
||
library_namespace.debug('已有相同 id (' + tmp
|
||
+ ') 之 process 執行中!');
|
||
else
|
||
// 作個登記。
|
||
Serial_execute_process[core_data.id = tmp] = this;
|
||
}
|
||
|
||
} else
|
||
// 還是給予個預設值,省略判斷,簡化流程。
|
||
options = {};
|
||
|
||
// 外包裹執行緒: 可寫在 prototype 中。
|
||
this.package_thread = Serial_execute_package_thread.bind(this,
|
||
core_data);
|
||
|
||
// 既然首尾都設定了,自動設定 index。
|
||
if (!library_namespace.is_digits(core_data.index)
|
||
&& library_namespace.is_digits(core_data.start)
|
||
&& library_namespace.is_digits(core_data.last))
|
||
// start from 哪一序號。
|
||
core_data.index = core_data.start;
|
||
|
||
// 必須先執行之程序。
|
||
tmp = options.first;
|
||
if (typeof tmp === 'function')
|
||
if (core_data.argument)
|
||
tmp.apply(core_data['this'] || this, core_data.argument);
|
||
else
|
||
tmp.call(core_data['this'] || this, core_data);
|
||
|
||
// 預設自動開始執行。
|
||
if (!('autostart' in options) || options.autostart)
|
||
setTimeout(this.package_thread, 0);
|
||
}
|
||
|
||
/**
|
||
* 取得指定 id 之控制程序。
|
||
*/
|
||
Serial_execute.process = function(id) {
|
||
if (id in Serial_execute_process)
|
||
return Serial_execute_process[id];
|
||
};
|
||
|
||
/**
|
||
* signal 定義。
|
||
*
|
||
* @see <a href="http://en.wikipedia.org/wiki/Unix_signal"
|
||
* accessdate="2012/2/4 15:35">Unix signal</a>
|
||
*/
|
||
Serial_execute.signal = {
|
||
// running : 0,
|
||
STOP : 1,
|
||
// 結束程序。
|
||
FINISH : 2,
|
||
// abort
|
||
TERMINATE : 3
|
||
};
|
||
|
||
/**
|
||
* Serial_execute controller.
|
||
*
|
||
* @param signal
|
||
* @param result
|
||
* @returns {Serial_execute_controller}
|
||
*/
|
||
function Serial_execute_controller(signal, result) {
|
||
this.signal = signal;
|
||
if (arguments.length > 1)
|
||
this.result = result;
|
||
}
|
||
|
||
Serial_execute.controller = Serial_execute_controller;
|
||
|
||
// private: 預設外包裹執行緒。iterator.
|
||
function Serial_execute_package_thread(data, force) {
|
||
|
||
if (data.stopped) {
|
||
library_namespace.debug('執行緒已被中止。欲執行請先解除暫停設置。', 1,
|
||
'Serial_execute_package_thread');
|
||
return;
|
||
}
|
||
|
||
if (data.timer_id !== undefined) {
|
||
if (data.timer_id === is_running) {
|
||
library_namespace.debug('執行緒正執行中,忽略本次執行要求。', 1,
|
||
'Serial_execute_package_thread');
|
||
return;
|
||
}
|
||
clearTimeout(data.timer_id);
|
||
}
|
||
// lock
|
||
data.timer_id = is_running;
|
||
|
||
var result, list = data.list, to_terminate = data.terminated, argument_array,
|
||
// 設定是否已結束。
|
||
to_finish = to_terminate || data.finished || data.index > data.last,
|
||
// debug 用
|
||
id_tag = 'process [' + data.id + '] @ ' + data.index + ' / '
|
||
+ data.start + '-' + data.last;
|
||
|
||
if (!to_finish) {
|
||
// 已執行幾次。在 thread 中為從 1,而非從 0 開始!
|
||
data.count++;
|
||
library_namespace.debug('實際執行 loop thread()。', 2,
|
||
'Serial_execute_package_thread');
|
||
|
||
if (list)
|
||
list = [ list[data.index], data.index, list ];
|
||
|
||
if (data.argument) {
|
||
// data.argument 應為 Array。
|
||
argument_array = data.argument;
|
||
if (list)
|
||
// 不動到原先的 data.argument。
|
||
// 將 data.argument 當作最前面的 arguments,之後才填入 list 的部分。
|
||
argument_array = argument_array.concat(list);
|
||
} else if (list)
|
||
// 當作 Array.prototype.forEach()
|
||
argument_array = list;
|
||
|
||
try {
|
||
result = argument_array ?
|
||
//
|
||
data.thread.apply(data['this'] || this, argument_array) :
|
||
//
|
||
data.thread.call(data['this'] || this, data.index, data.count);
|
||
library_namespace.debug('loop thread() 程序執行完畢。', 2,
|
||
'Serial_execute_package_thread');
|
||
if (data.result)
|
||
data.result.push(result);
|
||
|
||
} catch (e) {
|
||
if (e.constructor === Serial_execute_controller) {
|
||
// signal cache
|
||
var signal = Serial_execute.signal;
|
||
switch (e.signal) {
|
||
case signal.STOP:
|
||
library_namespace.debug('Stop ' + id_tag, 1,
|
||
'Serial_execute_package_thread');
|
||
data.stopped = true;
|
||
break;
|
||
case signal.TERMINATE:
|
||
library_namespace.debug('Terminate ' + id_tag, 1,
|
||
'Serial_execute_package_thread');
|
||
to_terminate = true;
|
||
// terminate 的同時,也設定 to_finish。
|
||
case signal.FINISH:
|
||
to_finish = true;
|
||
break;
|
||
default:
|
||
// ignore others.
|
||
break;
|
||
}
|
||
|
||
result = e.result;
|
||
if (data.result)
|
||
data.result.push(result);
|
||
} else {
|
||
library_namespace.warn(id_tag + ' failed.');
|
||
library_namespace.error(e);
|
||
if (!data.skip_throw)
|
||
data.stopped = true;
|
||
}
|
||
}
|
||
}
|
||
|
||
data.index++;
|
||
|
||
if (to_finish) {
|
||
library_namespace.debug('執行收尾/收拾工作。', 1,
|
||
'Serial_execute_package_thread');
|
||
if (!to_terminate && typeof data['final'] === 'function')
|
||
try {
|
||
argument_array = [ data.result || data.count ];
|
||
data['final'].apply(data['this'] || this,
|
||
data.argument ? data.argument
|
||
.concat(argument_array) : argument_array);
|
||
} catch (e) {
|
||
library_namespace.error(e);
|
||
}
|
||
|
||
if (data.id in Serial_execute_process)
|
||
delete Serial_execute_process[data.id];
|
||
|
||
data.stopped = data.finished = true;
|
||
// TODO: delete all elements in this.
|
||
} else if (data.stopped)
|
||
delete data.timer_id;
|
||
else {
|
||
data.timer_id = setTimeout(this.package_thread, data.interval | 0);
|
||
}
|
||
|
||
return result;
|
||
}
|
||
;
|
||
|
||
library_namespace.set_method(Serial_execute.prototype, {
|
||
|
||
// 行程控制。
|
||
// run, continue, resume
|
||
start : function() {
|
||
this.set('stopped', false);
|
||
library_namespace.debug('Resume [' + this.get('id') + ']');
|
||
return this.package_thread();
|
||
},
|
||
|
||
// pause. 中止/停止執行緒。
|
||
stop : function() {
|
||
this.set('stopped', true);
|
||
},
|
||
|
||
// next one, step, moveNext.
|
||
next : function() {
|
||
var result = this.start();
|
||
this.stop();
|
||
return result;
|
||
},
|
||
|
||
// set position = start.
|
||
rewind : function() {
|
||
this.set('index', this.get('start') || 0);
|
||
},
|
||
|
||
// 結束程序。
|
||
finish : function() {
|
||
// gettext_config:{"id":"finished"}
|
||
this.set('finished', true);
|
||
this.set('stopped', false);
|
||
// return this.package_thread();
|
||
},
|
||
|
||
// abort (abnormal termination), remove.
|
||
terminate : function() {
|
||
this.set('terminated', true);
|
||
this.set('stopped', false);
|
||
// return this.package_thread();
|
||
},
|
||
|
||
// -----------------------------------------------------------------------------------------------------
|
||
// status / property
|
||
|
||
// 每隔多少 ms 執行一次。
|
||
interval : function(interval_ms) {
|
||
if (library_namespace.is_digits(interval_ms))
|
||
this.set('interval', interval_ms);
|
||
},
|
||
|
||
finished : function() {
|
||
// gettext_config:{"id":"finished"}
|
||
return this.get('finished');
|
||
},
|
||
|
||
stopped : function() {
|
||
return this.get('stopped');
|
||
},
|
||
|
||
argument : function() {
|
||
if (arguments.length)
|
||
this.set('argument', arguments.length > 1
|
||
//
|
||
? Array.prototype.slice.call(arguments)
|
||
//
|
||
: Array.isArray(argument) ? argument : [ argument ]);
|
||
|
||
return this.get('argument');
|
||
},
|
||
|
||
length : function() {
|
||
return this.get('length');
|
||
}
|
||
|
||
});
|
||
|
||
_.Serial_execute = Serial_execute;
|
||
|
||
/**
|
||
* <code>
|
||
|
||
// testing for data.code.thread
|
||
|
||
// adding 0 to 100.
|
||
CeL.run('data.code.thread', function() {
|
||
if (typeof runCode === 'object')
|
||
runCode.setR = 0;
|
||
p = new CeL.Serial_execute(function(i) {
|
||
CeL.debug(this.sum += i);
|
||
}, {
|
||
// id : 't',
|
||
interval : 800,
|
||
length : 100,
|
||
first : function() {
|
||
this.sum = 0;
|
||
CeL.log('Setuped.');
|
||
},
|
||
final : function(i) {
|
||
CeL.log('Done @ ' + i);
|
||
}
|
||
});
|
||
// p.stop();
|
||
});
|
||
// p.next();
|
||
// p.terminate();
|
||
// CeL.log(p);
|
||
|
||
// adding 0 to 100.
|
||
CeL.Serial_execute(function(data) {
|
||
data[1] += ++data[0];
|
||
CeL.debug(data[0]);
|
||
}, {
|
||
argument : [[ 0, 0 ]],
|
||
length : 100,
|
||
final : function(data) {
|
||
CeL.log('done @ ' + data[0] + ' : ' + data[1]);
|
||
}
|
||
});
|
||
|
||
// run 100 times: 0~99.
|
||
new CeL.Serial_execute(function(i) {
|
||
CeL.log(i + ': ' + (this.s = (this.s || 0) + i));
|
||
}, 100);
|
||
|
||
new CeL.Serial_execute(function(i) {
|
||
this.s = (this.s || 0) + i;
|
||
if (i === 100) {
|
||
CeL.log(i + ':' + this.s);
|
||
this.finish();
|
||
}
|
||
});
|
||
|
||
|
||
|
||
|
||
// 當作 Array.prototype.forEach()
|
||
|
||
CeL.run('data.code.thread');
|
||
new CeL.Serial_execute(function(item, index) {
|
||
CeL.log(item);
|
||
}, [2, 1, 3, 6]);
|
||
|
||
|
||
</code>
|
||
*/
|
||
|
||
// ----------------------------------------------------------------------------------------------------------------------------------------------------------//
|
||
return (_// JSDT:_module_
|
||
);
|
||
}
|