UNPKG

@thi.ng/fibers

Version:

Process hierarchies & operators for cooperative multitasking

135 lines (134 loc) 4.07 kB
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 };