/** * @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)。
* 本函數可代替迴圈操作 loop, for, setTimeout / setInterval,亦可避免長時間之迴圈操作被 browser * 判別為耗時 loop 而 hang 住。
* 可視作一種 iterator / forEach()。
* This module use asynchronous method (e.g., setTimeout) to avoid the * "Script Taking Too Long" message. * * TODO:
* .result[], .next() = .step(), input array as option * * // 單 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);}}); * * * @param {Function}loop_thread * loop_thread({Integer}process_to_index) {
* return
* 'SIGABRT': terminated (accident occurred), won't run * options.final();
* others(!0): all done
} * * @param {Object}[options] * 設定選項。
{
* {String}id : process id (有設定 id 則可以從 * Serial_execute.process(id) 控制。),
*
* {Integer}start : start from 哪一序號(index),
* {Integer}index : process to 哪一序號,
// length, last 二選一。
* {Integer}length : 執行 length 次,
* {Integer}last : 執行至哪一序號(end index),
*
* argument : 傳給 handler 之 arguments,
* {Integer}interval : 週期間隔(ms),
* {Function}first : run first,
* {Function}final : run after all, 結尾, epilogue.
* {Boolean}skip_throw : skip throw of thread(),
} * * @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 初成。
* 2012/2/4 12:31:53 包裝成物件。
* 2012/11/16 19:30:53 re-write start。
* 2012/11/23 23:51:44 re-write finished。
* 2013/3/3 19:20:52 重新定義 options.length。
*/ 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 Unix signal */ 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; /** * // 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]); */ // ----------------------------------------------------------------------------------------------------------------------------------------------------------// return (_// JSDT:_module_ ); }