Path: blob/master/src/library/utils/multithreading.ts
1784 views
import { configuration } from "../configurations.js";1import { system } from "@minecraft/server";2import { contentLog } from "./contentlog.js";3import { setTickTimeout, sleep } from "./scheduling.js";45let threadId = 1;67const threads: Thread[] = [];8const tasks = new WeakMap<Thread, Generator>();9const promises = new WeakMap<Thread, unknown>();10const waitForPromise = { waitingForPromise: true } as const;1112class ErrorPackage {13constructor(public err: unknown) {}14}1516// eslint-disable-next-line @typescript-eslint/no-explicit-any17class Thread {18public readonly id: number;1920private active = false;21private valid = true;2223constructor() {24this.id = ++threadId;25}2627get isActive() {28return this.active;29}3031get isValid() {32return this.active;33}3435start<T extends any[]>(task: (...args: T) => Generator<unknown>, ...args: T) {36if (!this.valid) return;3738tasks.set(this, task(...args));39threads.unshift(this);40this.active = true;41this.valid = false;4243if (!configuration.multiThreadingEnabled) this.join();44}4546async join() {47if (this.valid || !this.active) return;4849if (threads.includes(this)) threads.splice(threads.indexOf(this), 1);50this.active = false;5152while (Object.is(promises.get(this), waitForPromise)) await sleep(1);5354let promiseRes = promises.get(this);55const task = tasks.get(this);56promises.delete(this);57let next = promiseRes instanceof ErrorPackage ? task.throw(promiseRes.err) : task.next(promiseRes);58while (!next.done) {59if (next.value instanceof Promise) {60try {61promiseRes = await next.value;62} catch (err) {63promiseRes = new ErrorPackage(err);64}65}66next = promiseRes instanceof ErrorPackage ? task.throw(promiseRes.err) : task.next(promiseRes);67promiseRes = undefined;68}69}7071abort() {72if (this.valid || !this.active) return;73if (threads.includes(this)) threads.splice(threads.indexOf(this), 1);74promises.delete(this);75this.active = false;76}7778toString() {79return `[thread #${this.id}]`;80}81}8283let currentThread: Thread = undefined;84system.runInterval(() => {85const ms = Date.now();86while (Date.now() - ms < configuration.multiThreadingTimeBudget && threads.length) {87const thread = threads.pop();88const task = tasks.get(thread);89currentThread = thread;90try {91const promiseRes = promises.get(thread);92if (Object.is(promiseRes, waitForPromise)) {93setTickTimeout(() => threads.unshift(thread));94continue;95}96promises.delete(thread);97const next = promiseRes instanceof ErrorPackage ? task.throw(promiseRes.err) : task.next(promiseRes);98if (next.done) {99thread.join();100continue;101} else if (next.value instanceof Promise) {102promises.set(thread, waitForPromise);103next.value.then((result) => promises.set(thread, result)).catch((err) => promises.set(thread, new ErrorPackage(err)));104}105threads.unshift(thread);106} catch (e) {107contentLog.error(e);108task.throw(e);109thread.join();110}111currentThread = undefined;112}113});114115let iterCount = 0;116function* iterateChunk<T>(val: T) {117if (iterCount++ > 16) {118iterCount = 0;119yield val;120}121}122123function getCurrentThread() {124return currentThread;125}126127function shutdownThreads() {128threads.length = 0;129}130131export { Thread, iterateChunk, getCurrentThread, shutdownThreads };132133134