UNPKG

threaded.std.js

Version:
1,124 lines (983 loc) 124 kB
/* * MIT License * * Copyright (c) 2025 Flame * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * 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. */ /* Version 1.2.1 */ class ThreadedTools { static workerSourceCode(generatorFunc) { return (ThreadedTools.isnodejs ? "import { parentPort } from 'node:worker_threads';\n" : "") + 'class ThreadExecutor {' + '\n' + ' static __threadExceptionOccurred__(ex) {' + '\n' + ' ' + (ThreadedTools.isnodejs ? 'parentPort' : 'self') + '.postMessage({ error: ex });' + '\n' + ' throw ex;' + '\n' + ' }' + '\n' + '}' + '\n' + 'ThreadExecutor.currentThread = {};' + '\n' + ThreadedTools.toString() + '\n' + 'ThreadedTools.postMessageToWorker = (worker, data) => ' + (ThreadedTools.isnodejs ? 'global' : 'worker') + '.onmessage(data);' + '\n' + IsolatedThread.toString() + '\n' + ThreadError.toString() + '\n' + 'IsolatedThread.this = new IsolatedThread();' + '\n' + 'IsolatedThread.this.__createInternalWebWorker__ = () => {}' + '\n' + 'IsolatedThread.this.terminate = () => {' + '\n' + ' ' + (ThreadedTools.isnodejs ? 'parentPort' : 'self') + '.postMessage({ terminate: true });' + '\n' + '}' + '\n' + 'IsolatedThread.this.__worker__ = ' + (ThreadedTools.isnodejs ? 'parentPort' : 'self') + ';' + '\n' + 'let generatorFunc = ' + generatorFunc.toString() + '\n' + 'let generator = null;' + '\n' + 'let args = [];' + '\n' + (ThreadedTools.isnodejs ? 'global.' : 'let ') + 'onmessage = function(e) {' + '\n' + ' try {' + '\n' + ' let data = e.data === undefined ? e : e.data;' + '\n' + ' ' + (ThreadedTools.isnodejs ? 'global' : 'self') + '.id = data.id;' + '\n' + ' let started = data.started;' + '\n' + ' let running = data.running;' + '\n' + ' let stopped = data.stopped;' + '\n' + ' let sleeping = data.sleeping;' + '\n' + ' let paused = data.paused;' + '\n' + ' let restarted = data.restarted;' + '\n' + ' let argschanged = data.argschanged;' + '\n' + ' IsolatedThread.this.id = data.id;' + '\n' + ' IsolatedThread.this.__started__ = started;' + '\n' + ' IsolatedThread.this.__running__ = running;' + '\n' + ' IsolatedThread.this.__stopped__ = stopped;' + '\n' + ' IsolatedThread.this.__sleeping__ = sleeping;' + '\n' + ' IsolatedThread.this.__paused__ = paused;' + '\n' + ' IsolatedThread.this.__argschanged__ = argschanged;' + '\n' + ' if (argschanged || restarted) {' + '\n' + ' args = data.args !== null ? data.args : args;' + '\n' + ' generator = generatorFunc(...args);' + '\n' + ' }' + '\n' + ' let stepscount = 1;' + '\n' + ' function loop() {' + '\n' + ' if (' + (ThreadedTools.isnodejs ? 'global' : 'self') + '.__looptimer__ !== undefined) try { clearTimeout(' + (ThreadedTools.isnodejs ? 'global' : 'self') + '.__looptimer__); } catch (ex) {}' + '\n' + ' if (!started || !running || stopped) return;' + '\n' + ' let done = false;' + '\n' + ' if (!sleeping && !paused) {' + '\n' + ' let result = generator.next();' + '\n' + ' done = result.done === true;' + '\n' + ' if (result instanceof Error) return result;' + '\n' + ' stepscount = !done ? result : stepscount;' + '\n' + ' ' + (ThreadedTools.isnodejs ? 'parentPort' : 'self') + '.postMessage({' + '\n' + ' id: ' + (ThreadedTools.isnodejs ? 'global' : 'self') + '.id,' + '\n' + ' result: result.value,' + '\n' + ' done: done,' + '\n' + ' });' + '\n' + ' }' + '\n' + ' if (!done) ' + (ThreadedTools.isnodejs ? 'global' : 'self') + '.__looptimer__ = setTimeout(loop, 10);' + '\n' + ' }' + '\n' + ' loop();' + '\n' + ' } catch (ex) {' + '\n' + ' ThreadExecutor.__threadExceptionOccurred__(ex);' + '\n' + ' return ex;' + '\n' + ' }' + '\n' + '}' + '\n' + (ThreadedTools.isnodejs ? "parentPort.on('message', global.onmessage);" : "self.onmessage = onmessage;"); } } ThreadedTools.createWorker = (isolatedThread, generatorFunc, onmessage) => { let workerCode = ThreadedTools.workerSourceCode(generatorFunc); if (ThreadedTools.isnodejs === true) { isolatedThread.__worker__ = ThreadedTools.createNodeWorker(workerCode); isolatedThread.__worker__.on('message', onmessage); } else { isolatedThread.__blob__ = new Blob([workerCode], { type: "application/javascript" }); isolatedThread.__workerURL__ = URL.createObjectURL(isolatedThread.__blob__); isolatedThread.__worker__ = new Worker(isolatedThread.__workerURL__, ThreadedTools.isesm === true ? { type: 'module' } : undefined); isolatedThread.__worker__.onmessage = onmessage; } } ThreadedTools.postMessageToWorker = (worker, data) => { worker.postMessage(data); } ThreadedTools.ast_generator = {parse: undefined}; ThreadedTools.ast_walker = {fullAncestor: undefined, simple: undefined, base: undefined}; ThreadedTools.escodegenerator = {generate: undefined}; class Thread { constructor(givenfunc, prioritylevel, id, innerfunctionsisolation) { if (Thread.this === undefined) Thread.this = this; if (Thread.__stopcontrols__ === true) return this; // Used in AST processing to prevent premature execution if (Thread.__count__ === undefined || Thread.__count__ === null || Number.isNaN(Thread.__count__)) { Thread.__count__ = 0; } Thread.__count__++; this.__func__ = givenfunc; this.innerfunctionsisolation = innerfunctionsisolation == undefined || innerfunctionsisolation === null ? false : innerfunctionsisolation; this.__generatorFunc__ = ThreadExecutor.__generatorFunc__(givenfunc, this.innerfunctionsisolation); this.id = id === undefined ? ('anonymous thread n-' + Thread.__count__) : (id + ' (thread n-' + Thread.__count__ + ')'); if (ThreadExecutor.queue === undefined) { ThreadExecutor.queue = []; } this.__sleepingpriority__ = false; this.__resumingpriority__ = false; this.__stepscount__ = 0; if (prioritylevel !== undefined) { if (typeof prioritylevel !== 'number') { throw new ThreadError('thread priority level must be a valid number', this); } if (! Number.isInteger(prioritylevel)) { throw new ThreadError('thread priority level must be an integer number', this); } if (prioritylevel < 0) { throw new ThreadError('thread priority level must be a positive number', this); } this.prioritylevel = prioritylevel; } else { this.prioritylevel = (prioritylevel ?? 1) | 1; } this.__args__ = []; this.__result__ = null; this.__sleepingtimeout__ = null; this.__delaytimeout__ = null; this.__isolateErrors__ = false; this.__done__ = false; this.__sleeping__ = false; this.__started__ = false; this.__running__ = false; this.__stopped__ = false; this.__paused__ = false; } static count() { return Thread.__count__; } static innerThreadFor(outerThread, func, prioritylevel, id, innerfunctionsisolation) { if (Thread.__sharedinnerthreads__ === undefined) { Thread.__sharedinnerthreads__ = new Map(); } if (! Thread.__sharedinnerthreads__.has(outerThread)) { Thread.__sharedinnerthreads__.set(outerThread, new Map()); } if (! Thread.__sharedinnerthreads__.get(outerThread).has(func)) { Thread.__sharedinnerthreads__.get(outerThread).set(func, [new Thread(func, prioritylevel, id, innerfunctionsisolation)]); } let thread = null; for (const i in Thread.__sharedinnerthreads__.get(outerThread).get(func)) { const thr = Thread.__sharedinnerthreads__.get(outerThread).get(func)[i]; if (! thr.__running__) { thread = thr; break; } } if (thread === null) { thread = new Thread(func, prioritylevel, id, innerfunctionsisolation); Thread.__sharedinnerthreads__.get(outerThread).get(func).push(thread); } if (prioritylevel !== undefined) { thread.setPriorityLevel(prioritylevel !== undefined ? prioritylevel : outerThread.prioritylevel); } if (id !== undefined) { thread.setId(id); } if (innerfunctionsisolation !== undefined) { thread.isolateInnerFunctions(innerfunctionsisolation); } return thread; } setId(id) { if (Thread.__stopcontrols__ === true) return this; // Used in AST processing to prevent premature execution this.id = (id === undefined || id === null) ? ('anonymous thread n-' + Thread.__count__) : (id + ' (thread n-' + Thread.__count__ + ')'); return this; } setArgs(...args) { if (Thread.__stopcontrols__ === true) return this; // Used in AST processing to prevent premature execution this.__args__ = args; return this; } isolateErrors(flag) { if (Thread.__stopcontrols__ === true) return this; // Used in AST processing to prevent premature execution if (flag === undefined || flag === null) { this.__isolateErrors__ = false; } this.__isolateErrors__ = flag; return this; } setFunction(fn) { if (Thread.__stopcontrols__ === true) return this; // Used in AST processing to prevent premature execution if (! this.__paused__) this.pause(); this.__func__ = fn; this.__generatorFunc__ = ThreadExecutor.__generatorFunc__(this.__func__, this.innerfunctionsisolation); if (this.__started__) this.start(); return this; } isolateInnerFunctions(flag) { if (Thread.__stopcontrols__ === true) return this; // Used in AST processing to prevent premature execution let innerfunctionsisolation = flag; if (innerfunctionsisolation === undefined || innerfunctionsisolation === null) { innerfunctionsisolation = true; // if user just types isolateInnerFunctions()... } this.innerfunctionsisolation = innerfunctionsisolation; if (! this.__paused__) this.pause(); this.__generatorFunc__ = ThreadExecutor.__generatorFunc__(this.__func__, innerfunctionsisolation); if (this.__started__) this.start(); return this; } toString() { return this.id; } stepsCount() { return this.__stepscount__; } setPriorityLevel(prioritylevel) { if (Thread.__stopcontrols__ === true) return this; // Used in AST processing to prevent premature execution if (typeof prioritylevel !== 'number') { throw new ThreadError('thread priority level must be a valid number', this); } if (! Number.isInteger(prioritylevel)) { throw new ThreadError('thread priority level must be an integer number', this); } if (prioritylevel < 0) { throw new ThreadError('thread priority level must be a positive number', this); } this.prioritylevel = prioritylevel; ThreadExecutor.notifyForPriority(); return this; } start() { if (Thread.__stopcontrols__ === true) return this; // Used in AST processing to prevent premature execution if (this.__sleepingtimeout__ !== null) try { clearTimeout(this.__sleepingtimeout__); } catch(ex) { // Ignored... } this.__generator__ = undefined; this.__stepscount__ = 0; this.__sleepingpriority__ = false; this.__resumingpriority__ = false; this.__result__ = null; this.__done__ = false; this.__sleeping__ = false; this.__started__ = true; this.__paused__ = false; this.__stopped__ = false; this.__running__ = true; ThreadExecutor.__timeSpentOutsideThreads__ = undefined; ThreadExecutor.queue.push(this); if (this.prioritylevel > 1) { ThreadExecutor.notifyForPriority(false); } if (! ThreadExecutor.isHandling()) { ThreadExecutor.handleThreadQueue(); } return this; } startAfter(ms) { if (Thread.__stopcontrols__ === true) return this; // Used in AST processing to prevent premature execution if (this.__delaytimeout__ !== null) try { clearTimeout(this.__delaytimeout__); } catch(ex) { // Ignored... } if (! this.__paused__) this.pause(); this.__delaytimeout__ = setTimeout(() => this.start(), ms); return this; } interrupt() { if (Thread.__stopcontrols__ === true) return this; // Used in AST processing to prevent premature execution return this.stop(); } static interrupt() { if (Thread.__stopcontrols__ === true) return this; // Used in AST processing to prevent premature execution const currentThread = ThreadExecutor.currentThread; if (currentThread === undefined || currentThread === null) { throw new ThreadError("Thread.interrupt called outside thread environment"); } currentThread.interrupt(); } interruptAfter(ms) { if (Thread.__stopcontrols__ === true) return this; // Used in AST processing to prevent premature execution if (this.__delaytimeout__ !== null) try { clearTimeout(this.__delaytimeout__); } catch(ex) { // Ignored... } if (! this.__paused__) this.pause(); this.__delaytimeout__ = setTimeout(() => this.interrupt(), ms); return this; } static interruptAfter(ms) { if (Thread.__stopcontrols__ === true) return this; // Used in AST processing to prevent premature execution const currentThread = ThreadExecutor.currentThread; if (currentThread === undefined || currentThread === null) { throw new ThreadError("Thread.interruptAfter called outside thread environment"); } currentThread.interruptAfter(ms); } stop() { if (Thread.__stopcontrols__ === true) return this; // Used in AST processing to prevent premature execution if (! this.__started__) { console.error("Thread not started yet"); return this; } if (this.__stopped__) { console.error("Thread already stopped"); return this; } this.__stopped__ = true; this.__running__ = false; const previousIndex = ThreadExecutor.queue.indexOf(this); if (previousIndex === -1) { throw new ThreadError("Cannot stop thread \"" + this.id + ", the thread is not in the execution queue"); } ThreadExecutor.queue.splice(previousIndex, 1); return this; } static stop() { if (Thread.__stopcontrols__ === true) return this; // Used in AST processing to prevent premature execution const currentThread = ThreadExecutor.currentThread; if (currentThread === undefined || currentThread === null) { throw new ThreadError("Thread.stop called outside thread environment"); } currentThread.stop(); } stopAfter(ms) { if (Thread.__stopcontrols__ === true) return this; // Used in AST processing to prevent premature execution if (this.__delaytimeout__ !== null) try { clearTimeout(this.__delaytimeout__); } catch(ex) { // Ignored... } this.__delaytimeout__ = setTimeout(() => this.stop(), ms); return this; } static stopAfter(ms) { if (Thread.__stopcontrols__ === true) return this; // Used in AST processing to prevent premature execution const currentThread = ThreadExecutor.currentThread; if (currentThread === undefined || currentThread === null) { throw new ThreadError("Thread.stopAfter called outside thread environment"); } currentThread.stopAfter(ms); } pause() { if (Thread.__stopcontrols__ === true) return this; // Used in AST processing to prevent premature execution if (this.__paused__) { console.error("Thread already paused"); return this; } this.__paused__ = true; return this; } static pause() { if (Thread.__stopcontrols__ === true) return this; // Used in AST processing to prevent premature execution const currentThread = ThreadExecutor.currentThread; if (currentThread === undefined || currentThread === null) { throw new ThreadError("Thread.pause called outside thread environment"); } currentThread.pause(); } pauseAfter(ms) { if (Thread.__stopcontrols__ === true) return this; // Used in AST processing to prevent premature execution if (this.__delaytimeout__ !== null) try { clearTimeout(this.__delaytimeout__); } catch(ex) { // Ignored... } this.__delaytimeout__ = setTimeout(() => this.pause(), ms); return this; } static pauseAfter(ms) { if (Thread.__stopcontrols__ === true) return this; // Used in AST processing to prevent premature execution const currentThread = ThreadExecutor.currentThread; if (currentThread === undefined || currentThread === null) { throw new ThreadError("Thread.pauseAfter called outside thread environment"); } currentThread.pauseAfter(ms); } resume() { if (Thread.__stopcontrols__ === true) return this; // Used in AST processing to prevent premature execution if (!this.__paused__ && !this.__sleeping__) { console.error("Thread is neither paused nor sleeping"); return this; } const previousIndex = ThreadExecutor.queue.indexOf(this); if (previousIndex === -1) { throw new ThreadError("Cannot resume thread \"" + this.id + ", the thread is not in the execution queue"); } if (this.__sleepingtimeout__ !== null) try { clearTimeout(this.__sleepingtimeout__); } catch(ex) { // Ignored... } if (this.__paused__) { this.__sleeping__ = false; this.__sleepingpriority__ = false; this.__paused__ = false; this.__resumingpriority__ = true; ThreadExecutor.notifyForResuming(this, previousIndex); } else if (this.__sleeping__) { this.__paused__ = false; this.__resumingpriority__ = false; this.__sleeping__ = false; this.__sleepingpriority__ = true; ThreadExecutor.notifyForSleeping(this, previousIndex); } return this; } static resume() { if (Thread.__stopcontrols__ === true) return this; // Used in AST processing to prevent premature execution const currentThread = ThreadExecutor.currentThread; if (currentThread === undefined || currentThread === null) { throw new ThreadError("Thread.resume called outside thread environment"); } currentThread.resume(); } resumeAfter(ms) { if (Thread.__stopcontrols__ === true) return this; // Used in AST processing to prevent premature execution if (this.__delaytimeout__ !== null) try { clearTimeout(this.__delaytimeout__); } catch(ex) { // Ignored... } this.__delaytimeout__ = setTimeout(() => this.resume(), ms); return this; } static resumeAfter(ms) { if (Thread.__stopcontrols__ === true) return this; // Used in AST processing to prevent premature execution const currentThread = ThreadExecutor.currentThread; if (currentThread === undefined || currentThread === null) { throw new ThreadError("Thread.resumeAfter called outside thread environment"); } currentThread.resumeAfter(ms); } sleep(ms) { if (Thread.__stopcontrols__ === true) return this; // Used in AST processing to prevent premature execution if (this.__sleepingtimeout__ !== null) try { clearTimeout(this.__sleepingtimeout__); } catch(ex) { // Ignored... } if (! this.__started__) { throw new ThreadError("Cannot sleep thread \"" + this.id + ", the thread is not started yet"); } if (this.__stopped__) { throw new ThreadError("Cannot sleep thread \"" + this.id + ", the thread is stopped"); } if (! this.__running__) { throw new ThreadError("Cannot sleep thread \"" + this.id + ", the thread is not running"); } if (this.__paused__) { throw new ThreadError("Cannot sleep thread \"" + this.id + ", the thread is paused"); } const previousIndex = ThreadExecutor.queue.indexOf(this); if (previousIndex === -1) { throw new ThreadError("Cannot sleep thread \"" + this.id + ", the thread is not in the execution queue"); } this.__sleeping__ = true; this.__sleepingpriority__ = true; this.__sleepingtimeout__ = setTimeout(() => ThreadExecutor.notifyForSleeping(this, previousIndex), ms); return this; } static sleep(ms) { if (Thread.__stopcontrols__ === true) return this; // Used in AST processing to prevent premature execution const currentThread = ThreadExecutor.currentThread; if (currentThread === undefined || currentThread === null) { throw new ThreadError("Thread.sleep called outside thread environment"); } currentThread.sleep(ms); } sleepAfter(delay, ms) { if (Thread.__stopcontrols__ === true) return this; // Used in AST processing to prevent premature execution if (this.__delaytimeout__ !== null) try { clearTimeout(this.__delaytimeout__); } catch(ex) { // Ignored... } this.__delaytimeout__ = setTimeout(() => this.sleep(ms), delay); return this; } static sleepAfter(delay, ms) { if (Thread.__stopcontrols__ === true) return this; // Used in AST processing to prevent premature execution const currentThread = ThreadExecutor.currentThread; if (currentThread === undefined || currentThread === null) { throw new ThreadError("Thread.sleepAfter called outside thread environment"); } currentThread.sleepAfter(delay, ms); } join(timeoutms) { if (Thread.__stopcontrols__ === true) return; // Used in AST processing to prevent premature execution // The design of joining can't be achieved just by looping until the thread finishes executing // That will block the entire event loop // Instead... // For efficiency... // thread.join() // Will be replaced with : // while (thread.isrunning()) { // yield __thefunctionstepscount__; // or just yield; for custom generator functions // } // And // thread.join(timeoutms) // Will be replaced with : // let __isjointimeoutN__ = false; // let __jointimeoutN__ = setTimeout(function () { __isjointimeoutN__ = true; }, timeoutms) // while (thread.isrunning() && !__isjointimeoutN__) { // yield __thefunctionstepscount__; // or just yield; for custom generator functions // } // clearTimeout(__jointimeoutN__); // Using the AST processor... // Only inside a thread environment (inside a thread) // Using it somewhere else will throw the error below... if (true) throw new ThreadError("Thread.join method can't be called outside thread environment"); } catch(exceptionFunc) { if (Thread.__stopcontrols__ === true) return this; // Used in AST processing to prevent premature execution this.__exceptionFunc__ = exceptionFunc; return this; } onfinish(finishFunc) { if (Thread.__stopcontrols__ === true) return this; // Used in AST processing to prevent premature execution this.__finishFunc__ = finishFunc; return this; } onyield(yieldFunc) { if (Thread.__stopcontrols__ === true) return this; // Used in AST processing to prevent premature execution this.__yieldFunc__ = yieldFunc; return this; } isdone() { return this.__done__; } issleeping() { return this.__sleeping__; } isstarted() { return this.__started__; } isrunning() { return this.__running__; } isstopped() { return this.__stopped__; } ispaused() { return this.__paused__; } result() { return this.__result__; } } Thread.LOW_PRIORITY_LEVEL = 1; Thread.MID_PRIORITY_LEVEL = 2; Thread.HIGH_PRIORITY_LEVEL = 3; Thread.innerfunctionsisolation = false; Thread.__stopcontrols__ = false; class ThreadExecutor { static handleThreadQueue() { if (ThreadExecutor.queue === undefined) { return; } if (ThreadExecutor.queue.length === 0) { return; } ThreadExecutor.__isLooping__ = true; ThreadExecutor.__isHandling__ = false; ThreadExecutor.__timeSpentOutsideThreads__ = undefined; ThreadExecutor.queueIndex = 0; ThreadExecutor.currentThread = null; ThreadExecutor.handlerLoop(false); } static handlerLoop(calledFromNotification) { if (ThreadExecutor.__timeSpentOutsideThreads__ !== undefined) { ThreadExecutor.__timeSpentOutsideThreads__ = Date.now() - ThreadExecutor.__timeSpentOutsideThreads__; } else { ThreadExecutor.__timeSpentOutsideThreads__ = 10; } ThreadExecutor.__timeSpentOutsideThreads__ = Math.min(ThreadExecutor.__timeSpentOutsideThreads__, 20); if (! calledFromNotification) if (ThreadExecutor.__isHandling__) { return; } if (ThreadExecutor.handlerlooptimeout !== undefined && ThreadExecutor.handlerlooptimeout !== null) { try { clearTimeout(ThreadExecutor.handlerlooptimeout); } catch(ex) { // Ignored... } } const thread = ThreadExecutor.queue[ThreadExecutor.queueIndex]; if (thread !== undefined && thread !== null) if ((! thread.__sleeping__) && (thread.__started__ && !thread.__stopped__ && thread.__running__ && !thread.__paused__)) { ThreadExecutor.__isHandling__ = true; ThreadExecutor.currentThread = thread; Thread.this = thread; thread.__sleepingpriority__ = false; thread.__resumingpriority__ = false; if (thread.__generator__ === undefined) { thread.__generator__ = thread.__generatorFunc__(...thread.__args__); } const result = thread.__generator__.next(); thread.__stepscount__ = result.done !== true ? result.value : thread.__stepscount__; if (!(result instanceof Error) && result.done === true) { ThreadExecutor.queue.splice(ThreadExecutor.queueIndex, 1); thread.__result__ = result.value; thread.__done__ = true; //thread.__stopped__ = true; thread.__running__ = false; thread.__sleepingpriority__ = false; thread.__resumingpriority__ = false; if (thread.__finishFunc__ !== undefined) { thread.__finishFunc__(result.value); } if (ThreadExecutor.__globalFinishFunc__ !== undefined && ThreadExecutor.__globalFinishFunc__ !== null) { ThreadExecutor.__globalFinishFunc__(result.value, thread); } if (thread.__threadgroup__ !== undefined && thread.__threadgroup__ !== null) { if (thread.__threadgroup__.__finishFunc__ !== undefined && thread.__threadgroup__.__finishFunc__ !== null) { thread.__threadgroup__.__finishFunc__(result.value, thread); } } if (ThreadExecutor.queueIndex >= ThreadExecutor.queue.length) { ThreadExecutor.queueIndex = 0; } } else { if (thread.__yieldFunc__ !== undefined) { thread.__yieldFunc__(result.value); } if (ThreadExecutor.__globalYieldFunc__ !== undefined && ThreadExecutor.__globalYieldFunc__ !== null) { ThreadExecutor.__globalYieldFunc__(result.value, thread); } if (thread.__threadgroup__ !== undefined && thread.__threadgroup__ !== null) { if (thread.__threadgroup__.__yieldFunc__ !== undefined && thread.__threadgroup__.__yieldFunc__ !== null) { thread.__threadgroup__.__yieldFunc__(result.value, thread); } } ThreadExecutor.queueIndex++; ThreadExecutor.queueIndex = ThreadExecutor.queueIndex % ThreadExecutor.queue.length; } ThreadExecutor.__isHandling__ = false; } else { ThreadExecutor.queueIndex++; ThreadExecutor.queueIndex = ThreadExecutor.queueIndex % ThreadExecutor.queue.length; } if (ThreadExecutor.queue.length !== 0) { ThreadExecutor.handlerlooptimeout = setTimeout(() => ThreadExecutor.handlerLoop(false), (ThreadExecutor.__loopingbeattime__ === undefined || ThreadExecutor.__loopingbeattime__ === ThreadExecutor.ADAPTIVE) ? ThreadExecutor.__timeSpentOutsideThreads__ : ThreadExecutor.__loopingbeattime__); ThreadExecutor.__timeSpentOutsideThreads__ = Date.now(); } else { ThreadExecutor.__isLooping__ = false; } } static isHandling() { if (ThreadExecutor.__isHandling__ === undefined) { return false; } return ThreadExecutor.__isHandling__; } static isLooping() { if (ThreadExecutor.__isLooping__ === undefined) { return false; } return ThreadExecutor.__isLooping__; } static setBeatTime(beatTime) { ThreadExecutor.__loopingbeattime__ = beatTime; } static notifyForSleeping(sleptThread, previousIndex) { for (let i = 0; i < ThreadExecutor.queue.length; i++) { const thread = ThreadExecutor.queue[i]; if (! thread.__sleepingpriority__) { ThreadExecutor.queue.splice(previousIndex, 1); ThreadExecutor.queue.splice(i, 0, sleptThread); break; } } sleptThread.__sleeping__ = false; ThreadExecutor.__timeSpentOutsideThreads__ = undefined; if (! ThreadExecutor.isLooping()) { ThreadExecutor.handleThreadQueue(); } else { ThreadExecutor.handlerLoop(true); } } static notifyForResuming(resumedThread, previousIndex) { for (let i = 0; i < ThreadExecutor.queue.length; i++) { const thread = ThreadExecutor.queue[i]; if (! thread.__sleepingpriority__ && ! thread.__resumingpriority__) { ThreadExecutor.queue.splice(previousIndex, 1); ThreadExecutor.queue.splice(i, 0, resumedThread); break; } } ThreadExecutor.__timeSpentOutsideThreads__ = undefined; if (! ThreadExecutor.isLooping()) { ThreadExecutor.handleThreadQueue(); } else { ThreadExecutor.handlerLoop(true); } } static notifyForPriority(notifyhandler) { let start = ThreadExecutor.queue.length; for (let i = 0; i < ThreadExecutor.queue.length; i++) { const thread = ThreadExecutor.queue[i]; if (! thread.__sleepingpriority__ && ! thread.__resumingpriority__) { start = i; break; } } if (ThreadExecutor.queue.length - start === 0) return; ThreadExecutor.queue = ThreadExecutor.queue.slice(0, start).concat(ThreadExecutor.queue.slice(start, ThreadExecutor.queue.length).sort((a, b) => ((a.prioritylevel ?? 1) | 1) - ((b.prioritylevel ?? 1) | 1))); ThreadExecutor.__timeSpentOutsideThreads__ = undefined; if (notifyhandler === undefined || notifyhandler == true) if (! ThreadExecutor.isLooping()) { ThreadExecutor.handleThreadQueue(); } else { ThreadExecutor.handlerLoop(true); } } static catch(globalExceptionFunc) { ThreadExecutor.__globalExceptionFunc__ = globalExceptionFunc; } static __threadExceptionOccurred__(ex) { if (ex instanceof TypeError && ex.message === "Generator is already running") { // Ignored... return; } ex = new ThreadError(ex, ThreadExecutor.currentThread); let noExceptionFuncIsSet = true; if (ThreadExecutor.currentThread !== undefined && ThreadExecutor.currentThread !== null) { if (ThreadExecutor.currentThread.__exceptionFunc__ !== undefined && ThreadExecutor.currentThread.__exceptionFunc__ !== null) { noExceptionFuncIsSet = false; ThreadExecutor.currentThread.__exceptionFunc__(ex); } if (ThreadExecutor.currentThread.__threadgroup__ !== undefined && ThreadExecutor.currentThread.__threadgroup__ !== null) { if (ThreadExecutor.currentThread.__threadgroup__.__exceptionFunc__ !== undefined && ThreadExecutor.currentThread.__threadgroup__.__exceptionFunc__ !== null) { noExceptionFuncIsSet = false; if (! ThreadExecutor.currentThread.__isolateErrors__) { ThreadExecutor.currentThread.__threadgroup__.__exceptionFunc__(ex, ThreadExecutor.currentThread); } } } if (ThreadExecutor.__globalExceptionFunc__ !== undefined && ThreadExecutor.__globalExceptionFunc__ !== null) { noExceptionFuncIsSet = false; if (! ThreadExecutor.currentThread.__isolateErrors__) { ThreadExecutor.__globalExceptionFunc__(ex, ThreadExecutor.currentThread); } } if (noExceptionFuncIsSet && !ThreadExecutor.currentThread.__isolateErrors__) { console.error(ex); } } else { console.error("Error thrown outside thread environment : ", ex); } } static __generatorFunc__(func, innerfunctionsisolation) { let functionSource = `(${func.toString()})`; if (functionSource === null) throw new ThreadError("Failed to retrieve the function source code"); if (ThreadExecutor.__isNativeFunction__(func)) throw new ThreadError("Can't execute native function \"" + functionSource + "\", the thread function has to be a normal function, try to wrap the function into a normal function instead..."); // Parse the source into an AST const ast = ThreadedTools.ast_generator.parse(functionSource, { ecmaVersion: 'latest' }); let alreadyAGeneratorFunction = typeof func === 'function' && func.constructor && func.constructor.name === 'GeneratorFunction'; ThreadedTools.ast_walker.simple(ast, { FunctionDeclaration(node) { node.body.istheouterfunction = true; }, FunctionExpression(node) { node.body.istheouterfunction = true; }, ArrowFunctionExpression(node) { node.body.istheouterfunction = true; } }, { // override default behavior: prevent descending into function bodies ...ThreadedTools.ast_walker.base, FunctionDeclaration() {}, // override walk behavior: skip body FunctionExpression() {}, ArrowFunctionExpression() {} }); ThreadedTools.ast_walker.fullAncestor(ast, (node, ancestors) => { // Inject `yield` after each statement in function body or block // Only if its not already a generator function Thread.__stopcontrols__ = true; IsolatedThread.__stopcontrols__ = true; ThreadGroup.__stopcontrols__ = true; let functionId = 0; let jointThreadId = 0; if ( node.type === 'BlockStatement' ) { for (let i = ancestors.length - 1; i >= 0; i--) { const ancestor = ancestors[i]; if (ancestor.type === 'FunctionDeclaration' || ancestor.type === 'FunctionExpression' || ancestor.type === 'ArrowFunctionExpression' ) { let functioncode = ThreadedTools.escodegenerator.generate(ancestor); let innerfunc = null; try { innerfunc = eval(`(${functioncode})`); ancestor.body.isFunctionGenerator = typeof innerfunc === 'function' && innerfunc.constructor && innerfunc.constructor.name === 'GeneratorFunction'; } catch (ex) { // Then its not a generator function and its being processed... } if (ancestor.body.istheouterfunction !== true) ancestor.body.isinnerfunction = true; if (ancestor.body.isFunctionGenerator) { //return node; } else if (ancestor.type === 'ArrowFunctionExpression') { ancestor.type = 'FunctionExpression'; ancestor.id = null; ancestor.expression = undefined; if (node.body.type !== 'BlockStatement') { ThreadExecutor.__wrapArrowFunctionInBlockStatement__(ancestor); } } } } let isFunctionGenerator = node.isFunctionGenerator !== undefined ? node.isFunctionGenerator : alreadyAGeneratorFunction; function handleStatement(stmt, newBody) { // Handling inline if else statements... if (stmt.type === 'IfStatement') { let current = stmt; while (current) { // Wrap 'then' body if (current.consequent && current.consequent.type !== 'BlockStatement') { let newBody = []; handleStatement(current.consequent, newBody); current.consequent = { type: 'BlockStatement', body: newBody, }; } // Handle 'else' const alt = current.alternate; if (!alt) break; if (alt.type === 'IfStatement') { // Continue walking the chain current = alt; } else { // Else-final: wrap if needed if (alt.type !== 'BlockStatement') { let newBody = []; handleStatement(alt, newBody); current.alternate = { type: 'BlockStatement', body: newBody, }; } break; // End of chain } } } else if ( stmt.type === 'WhileStatement' || stmt.type === 'DoWhileStatement' || stmt.type === 'ForStatement' || stmt.type === 'ForInStatement' || stmt.type === 'ForOfStatement' ) { // All loop types share a `body` property if (stmt.body && stmt.body.type !== 'BlockStatement') { const newBody = []; handleStatement(stmt.body, newBody); stmt.body = { type: 'BlockStatement', body: newBody, }; } } let callee = null; let calleeisthread = false; let calleeisthreadgroup = false; let calleeisisolatedthread = false; try { callee = eval(ThreadedTools.escodegenerator.generate(stmt.expression.callee.object)); calleeisthread = callee instanceof Thread; calleeisthreadgroup = callee instanceof ThreadGroup; calleeisisolatedthread = callee instanceof IsolatedThread; } catch(ex) { // Ignored... } let stmtisjoin = stmt.type === 'ExpressionStatement' && stmt.expression.type === 'CallExpression' && stmt.expression.callee.type === 'MemberExpression' && stmt.expression.callee.property.type === 'Identifier' && stmt.expression.callee.property.name === 'join' && (calleeisthread || calleeisthreadgroup || calleeisisolatedthread); if (stmtisjoin) { // thread.join case... const threadVar = stmt.expression.callee.object; const callArgs = stmt.expression.arguments; jointThreadId++; if (callArgs.length >= 1) { // thread.join(timeoutms) case... newBody.push({ type: 'VariableDeclaration', declarations: [{ type: 'VariableDeclarator', id: { type: 'Identifier', name: `__isjointimeout${jointThreadId}__` }, init: { type: 'Literal', value: false } }], kind: 'let', leadingComments: [ { type: 'Line', value: ` AUTO GENERATED : ${ThreadedTools.escodegenerator.generate(stmt).replaceAll(';', '')} call implementation...` } ] }); newBody.push({ type: 'VariableDeclaration', declarations: [{ type: 'VariableDeclarator', id: { type: 'Identifier', name: `__jointimeout${jointThreadId}__` }, init: { type: 'ExpressionStatement', expression: { type: 'CallExpression', callee: { type: 'Identifier', name: 'setTimeout' }, arguments: [ { type: 'FunctionExpression', id: null, params: [], body: { type: 'BlockStatement', body: [ { type: 'ExpressionStatement', expression: { type: 'AssignmentExpression', operator: '=', left: { type: 'Identifier', name: `__isjointimeout${jointThreadId}__` }, right: { type: 'Literal', value: true } } } ], processforbidden: true } }, callArgs[0] // the timeoutms argument ] } } }], kind: 'let' }); } newBody.push({ type: 'VariableDeclaration',