type Runner = ((taskUnit: T) => T | undefined) | undefined; type ThreadOptions = { priority: number }; let isLooping = false; function getMapKeys(map: Map) { return Array.from(map).map((it) => it[0]); } function workLoop() { isLooping = true; const prioritys = getMapKeys(Thread.threadMap).sort((a, b) => b - a); const curPriority = prioritys[0]; const curPriorityArray = Thread.threadMap.get(curPriority); curPriorityArray.forEach((thread) => { thread.samePriorityLength = curPriorityArray.length; let taskUnit = thread.taskUnit; thread.start(); while (taskUnit && !thread.shouldYield && !thread.isEmpty) { taskUnit = thread.taskUnit = thread.exec(); } if (taskUnit === undefined) { thread.destroy(); } else { thread.pause(); } }); if (Thread.isAllEmpty()) { isLooping = false; } else { nextTick(workLoop); } } let channel: MessageChannel; function nextTick(callback) { if (window.MessageChannel) { if (!channel) { channel = new MessageChannel(); channel.port1.onmessage = callback; } channel.port2.postMessage("notify"); } else { setTimeout(callback, 0); } } class Thread { static create( runner: Runner, taskUnit: unknown = undefined, options: ThreadOptions ) { const thread = new Thread(runner, taskUnit, options); Thread.threads.push(thread); const newThreadPriority = thread.options.priority; const curPriorityArray: Thread[] | undefined = Thread.threadMap.get(newThreadPriority); if (curPriorityArray) { curPriorityArray.push(thread); } else { Thread.threadMap.set(newThreadPriority, [thread]); } return thread; } static threads: Thread[] = []; static threadMap: Map = new Map(); static isAllEmpty() { return !Thread.threads.find((thread) => !thread.isEmpty); } static yieldInterval = 5; runner: Runner = undefined; taskUnit: unknown = undefined; options: ThreadOptions = { priority: 0, }; status: "running" | "destroyed" | "paused" = "paused"; samePriorityLength = 0; private startTime = 0; constructor(runner: Runner, taskUnit: unknown, options: ThreadOptions) { this.runner = runner; this.taskUnit = taskUnit; this.options = Object.assign(this.options, options); } start() { this.status = "running"; this.startTime = Date.now(); if (!isLooping) { workLoop(); } } pause() { this.status = "paused"; this.startTime = 0; } exec() { return this.runner(this.taskUnit); } destroy() { Thread.threads.splice( Thread.threads.findIndex((it) => it === this), 1 ); const curPriorityArray = Thread.threadMap.get(this.options.priority); curPriorityArray.splice( curPriorityArray.findIndex((it) => it === this), 1 ); if (curPriorityArray.length === 0) { Thread.threadMap.delete(this.options.priority); } this.runner = undefined; this.options = undefined; this.taskUnit = undefined; this.status = "destroyed"; } get shouldYield() { return Date.now() > this.startTime + this.yieldInterval; } get isEmpty() { return this.taskUnit === undefined; } get yieldInterval() { return Thread.yieldInterval / this.samePriorityLength; } } export { Thread };