@thi.ng/fibers
Version:
Process hierarchies & operators for cooperative multitasking
135 lines (134 loc) • 4.07 kB
JavaScript
import { DroppingBuffer } from "@thi.ng/buffers/dropping";
import { FIFOBuffer } from "@thi.ng/buffers/fifo";
import { LIFOBuffer } from "@thi.ng/buffers/lifo";
import { SlidingBuffer } from "@thi.ng/buffers/sliding";
import { isNumber } from "@thi.ng/checks/is-number";
import { Fiber, fiber } from "./fiber.js";
const STATE_OPEN = 0;
const STATE_CLOSING = 1;
const STATE_CLOSED = 2;
class Channel {
constructor(buffer = 1, opts) {
this.opts = opts;
this.buffer = isNumber(buffer) ? new FIFOBuffer(buffer) : buffer;
}
buffer;
state = STATE_OPEN;
/**
* Returns a new fiber which attempts to read a value from the channel and
* "blocks" until that value is available. Unless the channel has meanwhile
* been closed, the fiber returns the read value (otherwise: `undefined`).
*
* @remarks
* Depending on chosen back buffer behavior/implementation and
* {@link Channel.close}, read requests might still be successful whilst the
* channel is closing and there're still buffered values.
*
* @example
* ```ts
* const val = yield* chan.read();
* ```
*/
read() {
const chan = this;
return fiber(function* (ctx) {
while (chan.readable()) {
if (chan.buffer.readable()) {
const val = chan.buffer.read();
ctx.logger?.debug("read", val);
return val;
} else if (chan.state === STATE_CLOSING) {
return;
}
yield;
}
}, this.opts);
}
/**
* Returns a new fiber which attempts to write the given `value` to the
* channel and "blocks" until channel is writable (which depends on the
* channel's buffer implementation).
*
* @remarks
* Once the channel has been closed (or still is closing, see
* {@link Channel.close}), all write requests will be silently ignored.
*
* @example
* ```ts
* yield* chan.write(23);
* ```
*/
write(val) {
const chan = this;
return fiber(function* (ctx) {
while (chan.writable()) {
if (chan.buffer.writable()) {
ctx.logger?.debug("write", val);
chan.buffer.write(val);
return;
}
yield;
}
}, this.opts);
}
/**
* Returns new fiber which closes the channel. By default this op will defer
* the full closing until all buffered values have been read (by another
* fiber), however any writes will already become unavailable/ignored even
* at this stage (also see {@link Channel.write}). If `wait=false`, the
* channel will be closed immediately, the backing buffered cleared and any
* in-flight reads or writes will be canceled.
*
* @param wait
*/
close(wait = true) {
const chan = this;
return fiber(function* (ctx) {
if (chan.state >= STATE_CLOSING) return;
if (wait) {
ctx.logger?.debug("waiting to close...");
chan.state = STATE_CLOSING;
while (chan.buffer.readable()) yield;
}
chan.state = STATE_CLOSED;
chan.buffer.clear();
ctx.logger?.debug("channel closed");
}, this.opts);
}
/**
* Returns true if the channel is principally readable (i.e. not yet
* closed), however there might not be any values available yet and reads
* might block.
*/
readable() {
return this.state <= STATE_CLOSING;
}
/**
* Returns true if the channel is principally writable (i.e. not closing or
* closed), however depending on buffer behavior the channel might not yet
* accept new values and writes might block.
*/
writable() {
return this.state === STATE_OPEN;
}
/**
* Returns true if the channel is fully closed and no further reads or
* writes are possible.
*/
closed() {
return this.state === STATE_CLOSED;
}
}
const channel = (buffer, opts) => new Channel(buffer, opts);
const fifo = (cap) => new FIFOBuffer(cap);
const lifo = (cap) => new LIFOBuffer(cap);
const sliding = (cap) => new SlidingBuffer(cap);
const dropping = (cap) => new DroppingBuffer(cap);
export {
Channel,
channel,
dropping,
fifo,
lifo,
sliding
};