Skip to content

Commit

Permalink
feat(fibers): add/update operators, add Fiber.isActive()
Browse files Browse the repository at this point in the history
  • Loading branch information
postspectacular committed Aug 3, 2023
1 parent 23fcf87 commit a1099c5
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 14 deletions.
11 changes: 9 additions & 2 deletions packages/fibers/src/fiber.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,14 @@ export class Fiber<T = any>
return this.value;
}

/**
* Returns true if this fiber is still in new or active state (i.e. still
* can be processed).
*/
isActive() {
return this.state <= STATE_ACTIVE;
}

/**
* Returns child fiber for given `id`.
*
Expand Down Expand Up @@ -135,8 +143,7 @@ export class Fiber<T = any>
body?: Nullable<Fiber<F> | FiberFactory<F> | Generator<unknown, F>>,
opts?: Partial<FiberOpts>
) {
if (this.state > STATE_ACTIVE)
illegalState(`fiber (id: ${this.id}) not active`);
if (!this.isActive()) illegalState(`fiber (id: ${this.id}) not active`);
const $fiber = fiber(body, {
parent: this,
logger: this.logger,
Expand Down
64 changes: 52 additions & 12 deletions packages/fibers/src/ops.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
STATE_ERROR,
type FiberFactory,
type FiberOpts,
type MaybeFiber,
} from "./api.js";
import { Fiber, fiber } from "./fiber.js";

Expand All @@ -17,10 +18,12 @@ import { Fiber, fiber } from "./fiber.js";
*/
export const wait = (delay?: number) =>
delay !== undefined
? untilState(now(), (t0) => timeDiff(t0, now()) >= delay)
: (function* () {
? untilPromise(
new Promise<void>((resolve) => setTimeout(resolve, delay))
)
: fiber(function* () {
while (true) yield;
})();
});

/**
* Returns co-routine which "blocks" for given number of frames.
Expand All @@ -45,7 +48,7 @@ export const sequence = (
fiber(function* (ctx) {
for (let fiber of fibers) {
const $fiber = ctx.fork(fiber);
while ($fiber.state <= STATE_ACTIVE) yield;
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;
Expand All @@ -55,7 +58,22 @@ export const sequence = (

/**
* Returns a fiber which executes given fibers as child processes until **one**
* of them is finished/terminated.
* of them is finished/terminated. That child fiber itself will be the result.
*
* @remarks
* Also see {@link withTimeout}, {@link all}.
*
* @example
* ```ta
* // wait until mouse click for max 5 seconds
* const res = yield* first([
* untilEvent(window, "click", { id: "click" }),
* wait(5000)
* ]);
*
* // one way to check result
* if (res.id === "click") { ... }
* ```
*
* @param fibers
* @param opts
Expand All @@ -68,7 +86,7 @@ export const first = (
const $fibers = fibers.map((f) => ctx.fork(f));
while (true) {
for (let f of $fibers) {
if (f.state > STATE_ACTIVE) return f;
if (!f.isActive()) return f;
}
yield;
}
Expand All @@ -78,30 +96,52 @@ export const first = (
* Returns a fiber which executes given fibers as child processes until **all**
* of them are finished/terminated.
*
* @remarks
* Also see {@link first}.
*
* @param fibers
* @param opts
*/
export const all = (
fibers: (Fiber | FiberFactory | Generator)[],
opts?: Partial<FiberOpts>
) =>
export const all = (fibers: MaybeFiber[], opts?: Partial<FiberOpts>) =>
fiber<void>((ctx) => {
for (let f of fibers) ctx.fork(f);
return ctx.join();
}, opts);

/**
* Syntax sugar common use cases of {@link first} where a child fiber should be
* limited to a max. time period before giving up.
*
* @example
* ```ts
* // wait for fetch response max. 5 seconds
* const res = yield* withTimeout(untilPromise(fetch("example.json")), 5000);
*
* if (res.deref() != null) { ... }
* ```
*
* @param fiber
* @param timeout
* @param opts
*/
export const withTimeout = (
fiber: MaybeFiber,
timeout: number,
opts?: Partial<FiberOpts>
) => first([fiber, wait(timeout)], opts);

/**
* Higher-order fiber which repeatedly executes given `fiber` until its
* completion, but does so in a time-sliced manner, such that the fiber never
* consumes more than `maxTime` milliseconds per update cycle.
*
* @param maxTime
* @param fiber
* @param maxTime
* @param opts
*/
export const timeSlice = (
maxTime: number,
body: Fiber | FiberFactory | Generator,
maxTime: number,
opts?: Partial<FiberOpts>
) =>
fiber(function* () {
Expand Down

0 comments on commit a1099c5

Please sign in to comment.