@thi.ng/fibers
Version:
Process hierarchies & operators for cooperative multitasking
145 lines (144 loc) • 3.49 kB
JavaScript
import { shuffle as $shuffle } from "@thi.ng/arrays/shuffle";
import { now, timeDiff } from "@thi.ng/timestamp";
import { SYSTEM } from "@thi.ng/random/system";
import {
STATE_ACTIVE,
STATE_DONE,
STATE_ERROR
} from "./api.js";
import { Fiber, fiber } from "./fiber.js";
const wait = (delay) => delay !== void 0 ? untilPromise(
new Promise((resolve) => setTimeout(resolve, delay))
) : fiber(function* () {
while (true) yield;
});
function* waitFrames(delay) {
while (delay-- > 0) yield;
}
const sequence = (fibers, opts) => fiber(function* (ctx) {
for (let fiber2 of fibers) {
const $fiber = ctx.fork(fiber2);
while ($fiber.isActive()) yield;
if ($fiber.state === STATE_ERROR) throw $fiber.error;
ctx.value = $fiber.value;
if ($fiber.state > STATE_DONE || ctx.state > STATE_ACTIVE) break;
}
return ctx.value;
}, opts);
const first = (fibers, opts) => fiber(function* (ctx) {
const $fibers = [...fibers].map((f) => ctx.fork(f));
while (true) {
for (let f of $fibers) {
if (!f.isActive()) return f;
}
yield;
}
}, opts);
const all = (fibers, opts) => fiber((ctx) => {
ctx.forkAll(...fibers);
return ctx.join();
}, opts);
const withTimeout = (body, timeout, opts) => first([body, wait(timeout)], opts);
const timeSlice = (body, maxTime, opts) => fiber(function* () {
const $fiber = fiber(body);
while (true) {
let t0 = now();
do {
if ($fiber.state > STATE_ACTIVE || $fiber.next() > STATE_ACTIVE)
return;
} while (timeDiff(t0) < maxTime);
yield;
}
}, opts);
const timeSliceIterable = (src, consume, maxTime, opts) => fiber(function* () {
const iter = src[Symbol.iterator]();
while (true) {
let t0 = now();
const buf = [];
do {
const { value, done } = iter.next();
if (done) {
consume(buf);
return;
}
buf.push(value);
} while (timeDiff(t0) < maxTime);
consume(buf);
yield;
}
}, opts);
function* until(pred) {
while (!pred()) yield;
}
function* untilState(state, pred) {
while (!pred(state)) yield;
}
const untilPromise = (promise, opts) => fiber(function* (ctx) {
let error;
promise.then(
(x) => ctx.done(x),
(e) => error = e
);
while (true) {
if (error) throw error;
yield;
}
}, opts);
const untilEvent = (target, type, opts) => {
let listener;
return fiber(null, {
...opts,
init(ctx) {
listener = (e) => ctx.done(e);
target.addEventListener(type, listener);
},
deinit() {
target.removeEventListener(type, listener);
}
});
};
class Shuffle extends Fiber {
rnd;
constructor(fibers, opts) {
super((ctx) => ctx.join(), opts);
this.rnd = opts?.rnd || SYSTEM;
this.forkAll(...fibers);
}
next() {
if (!this.isActive()) return this.state;
$shuffle(this.children, this.children.length, this.rnd);
return super.next();
}
}
const shuffle = (fibers, opts) => new Shuffle(fibers, opts);
const asPromise = (task, opts) => new Promise((resolve, reject) => {
fiber(task, {
...opts,
deinit: (ctx) => {
opts?.deinit?.(ctx);
ctx.state < STATE_ERROR && resolve(ctx.deref());
},
catch: (ctx, e) => {
if (opts?.catch?.(ctx, e)) return true;
reject(e);
return false;
}
}).run();
});
export {
Shuffle,
all,
asPromise,
first,
sequence,
shuffle,
timeSlice,
timeSliceIterable,
until,
untilEvent,
untilPromise,
untilState,
wait,
waitFrames,
withTimeout
};