import { EventConsumer } from './events.js'; import { Endo } from './functions.js'; /** * A computationally expensive operation. This interface provides a generic way * to run either aggressively (fully synchronously, with minimal overhead) or * politely (releasing the event loop regularly). * * Instances are constructed from a generator function via `intensive`. Each * `yield` statement in the generator is an opportunity for the run to * "breathe", i.e. yield to the event loop. Note that not all yields will * necessarily trigger a breath so the overhead is lower than a naive * implementation. * * Operations may only be run once, whether directly (via `run` and `runSync`) * or indirectly (by embedding it). */ export interface Intensive extends EventConsumer> { readonly [isIntensiveMarker]: true; /** * Runs the operation, attempting to "breathe" every `ms` milliseconds * (defaulting to 200ms). Each "breath" yields back to the event loop. */ run(ms?: number): Promise; /** * Runs an intensive operation synchronously. Be aware that this will hold the * event loop for the entire duration of the operation. */ runSync(): V; } /** * Transforms an `Intensive` operation into an iterable which yields suitably. * This allows combining operations via `yield*`. For example: * * const mapped = intensive(function* (embed) { * const val = yield* embed(intensiveDependency); * return fn(val); // Transform the value. * }); * * `breath` events will be emitted on both the outer and inner operations. The * outer operation will always yield just before and just after the embedded * (inner) operation runs to allow clearing any delays. */ export type EmbedIntensive = (op: Intensive) => IntensiveIterable; export interface IntensiveIterable { [Symbol.iterator](): Iterator; } export interface IntensiveListeners { /** Emitted when the operation starts running. */ start(): void; /** * Emitted each time the operation yields to the event loop (just before). * `interval` is the time since the previous breath or start in milliseconds. * `lateness` is defined as `interval / breathInterval - 1` and can be used to * detect whether an operation doesn't yield often enough. */ breath(lateness: number, interval: number): void; /** * Emitted when the operation ends, successfully or not. `yieldCount` */ end(stats: IntensiveStats, err?: unknown): void; /** * Emitted when the outermost operation starts running. This event is not * emitted on embedded operations. The function argument can be used to wrap * the operation's execution. */ run(wrap: (endo: Endo>) => void): void; } export interface IntensiveStats { /** Total time taken to run in milliseconds. */ readonly runtime: number; /** Number of times the operation provided opportunities for breathing. */ readonly yieldCount: number; } /** * Constructs a new intensive operation. The operation is only started once its * `run`, `runAsync` method is called directly or via being embedded within * another operation. * * For this to be most effective, the input generator should yield often enough * for breaths to be timed appropriately. ~10ms between each yield is a good * target, anything above 50ms will likely lead to delays. */ export declare function intensive(gen: (embed: EmbedIntensive) => Iterator): Intensive; export declare function intensive(self: S, gen: (this: S, embed: EmbedIntensive) => Iterator): Intensive; declare const isIntensiveMarker: "@opvious/stl-utils:isIntensive+v1"; /** Checks whether the input is an intensive instance. */ export declare function isIntensive(arg: unknown): arg is Intensive; export {};