UNPKG

@effect-ts/system

Version:

Effect-TS is a zero dependency set of libraries to write highly productive, purely functional TypeScript at scale.

331 lines (285 loc) 8.47 kB
// ets_tracing: off import * as ChunkFilter from "../Collections/Immutable/Chunk/api/filter.js" import * as Chunk from "../Collections/Immutable/Chunk/core.js" import type { AtomicBoolean } from "../Support/AtomicBoolean/index.js" import type { MutableQueue } from "../Support/MutableQueue/index.js" import { EmptyQueue } from "../Support/MutableQueue/index.js" import * as T from "./effect.js" import * as P from "./promise.js" import type { XQueue } from "./xqueue.js" import { concreteQueue } from "./xqueue.js" export { Dequeue, Queue, XQueue } from "./xqueue.js" export interface Strategy<A> { readonly handleSurplus: ( as: Chunk.Chunk<A>, queue: MutableQueue<A>, takers: MutableQueue<P.Promise<never, A>>, isShutdown: AtomicBoolean ) => T.UIO<boolean> readonly unsafeOnQueueEmptySpace: ( queue: MutableQueue<A>, takers: MutableQueue<P.Promise<never, A>> ) => void readonly surplusSize: number readonly shutdown: T.UIO<void> } export class DroppingStrategy<A> implements Strategy<A> { handleSurplus( _as: Chunk.Chunk<A>, _queue: MutableQueue<A>, _takers: MutableQueue<P.Promise<never, A>>, _isShutdown: AtomicBoolean ): T.UIO<boolean> { return T.succeed(false) } unsafeOnQueueEmptySpace(_queue: MutableQueue<A>) { // } get shutdown(): T.UIO<void> { return T.unit } get surplusSize(): number { return 0 } } export class SlidingStrategy<A> implements Strategy<A> { handleSurplus( as: Chunk.Chunk<A>, queue: MutableQueue<A>, takers: MutableQueue<P.Promise<never, A>>, _isShutdown: AtomicBoolean ): T.UIO<boolean> { return T.succeedWith(() => { this.unsafeSlidingOffer(queue, as) unsafeCompleteTakers(this, queue, takers) return true }) } unsafeOnQueueEmptySpace(_queue: MutableQueue<A>) { // } get shutdown(): T.UIO<void> { return T.unit } get surplusSize(): number { return 0 } private unsafeSlidingOffer(queue: MutableQueue<A>, as: Chunk.Chunk<A>) { let bs = as while (Chunk.size(bs) > 0) { if (queue.capacity === 0) { return } // poll 1 and retry queue.poll(EmptyQueue) if (queue.offer(Chunk.unsafeGet_(bs, 0))) { bs = Chunk.drop_(bs, 1) } } } } export function unsafeCompletePromise<A>(p: P.Promise<never, A>, a: A) { return P.unsafeDone(T.succeed(a))(p) } export function unsafeCompleteTakers<A>( strategy: Strategy<A>, queue: MutableQueue<A>, takers: MutableQueue<P.Promise<never, A>> ) { let keepPolling = true while (keepPolling && !queue.isEmpty) { const taker = takers.poll(EmptyQueue) if (taker !== EmptyQueue) { const element = queue.poll(EmptyQueue) if (element !== EmptyQueue) { unsafeCompletePromise(taker, element) strategy.unsafeOnQueueEmptySpace(queue, takers) } else { unsafeOfferAll(takers, Chunk.prepend_(unsafePollAll(takers), taker)) } keepPolling = true } else { keepPolling = false } } } export function unsafeRemove<A>(q: MutableQueue<A>, a: A) { unsafeOfferAll( q, ChunkFilter.filter_(unsafePollAll(q), (b) => a !== b) ) } export function unsafePollN<A>(q: MutableQueue<A>, max: number): Chunk.Chunk<A> { return q.pollUpTo(max) } export function unsafeOfferAll<A>( q: MutableQueue<A>, as: Chunk.Chunk<A> ): Chunk.Chunk<A> { return q.offerAll(as) } export function unsafePollAll<A>(q: MutableQueue<A>): Chunk.Chunk<A> { let as = Chunk.empty<A>() while (!q.isEmpty) { const elem = q.poll(EmptyQueue)! if (elem !== EmptyQueue) { as = Chunk.append_(as, elem) } } return as } /** * Waits until the queue is shutdown. * The `IO` returned by this method will not resume until the queue has been shutdown. * If the queue is already shutdown, the `IO` will resume right away. */ export function awaitShutdown<RA, RB, EA, EB, A, B>( self: XQueue<RA, RB, EA, EB, A, B> ) { concreteQueue(self) return self.awaitShutdown } /** * How many elements can hold in the queue */ export function capacity<RA, RB, EA, EB, A, B>(self: XQueue<RA, RB, EA, EB, A, B>) { concreteQueue(self) return self.capacity } /** * `true` if `shutdown` has been called. */ export function isShutdown<RA, RB, EA, EB, A, B>(self: XQueue<RA, RB, EA, EB, A, B>) { concreteQueue(self) return self.isShutdown } /** * Places one value in the queue. * * @ets_data_first offer_ */ export function offer<A>(a: A) { return <RA, RB, EA, EB, B>(self: XQueue<RA, RB, EA, EB, A, B>) => offer_(self, a) } /** * Places one value in the queue. */ export function offer_<RA, RB, EA, EB, A, B>(self: XQueue<RA, RB, EA, EB, A, B>, a: A) { concreteQueue(self) return self.offer(a) } /** * Places one value in the queue. * * @ets_data_first offerTo_ */ export function offerTo<RA, RB, EA, EB, A, B>(self: XQueue<RA, RB, EA, EB, A, B>) { return (a: A) => offer_(self, a) } /** * Places one value in the queue. * * @ets_data_first offerTo_ */ export function offerTo_<RA, RB, EA, EB, A, B>( a: A, self: XQueue<RA, RB, EA, EB, A, B> ) { return offer_(self, a) } /** * For Bounded Queue: uses the `BackPressure` Strategy, places the values in the queue and always returns true. * If the queue has reached capacity, then * the fiber performing the `offerAll` will be suspended until there is room in * the queue. * * For Unbounded Queue: * Places all values in the queue and returns true. * * For Sliding Queue: uses `Sliding` Strategy * If there is room in the queue, it places the values otherwise it removes the old elements and * enqueues the new ones. Always returns true. * * For Dropping Queue: uses `Dropping` Strategy, * It places the values in the queue but if there is no room it will not enqueue them and return false. * * @ets_data_first offerAll_ */ export function offerAll<A>(as: Iterable<A>) { return <RA, RB, EA, EB, B>(self: XQueue<RA, RB, EA, EB, A, B>) => offerAll_(self, as) } /** * For Bounded Queue: uses the `BackPressure` Strategy, places the values in the queue and always returns true. * If the queue has reached capacity, then * the fiber performing the `offerAll` will be suspended until there is room in * the queue. * * For Unbounded Queue: * Places all values in the queue and returns true. * * For Sliding Queue: uses `Sliding` Strategy * If there is room in the queue, it places the values otherwise it removes the old elements and * enqueues the new ones. Always returns true. * * For Dropping Queue: uses `Dropping` Strategy, * It places the values in the queue but if there is no room it will not enqueue them and return false. */ export function offerAll_<RA, RB, EA, EB, A, B>( self: XQueue<RA, RB, EA, EB, A, B>, as: Iterable<A> ) { concreteQueue(self) return self.offerAll(as) } /** * Interrupts any fibers that are suspended on `offer` or `take`. * Future calls to `offer*` and `take*` will be interrupted immediately. */ export function shutdown<RA, RB, EA, EB, A, B>(self: XQueue<RA, RB, EA, EB, A, B>) { concreteQueue(self) return self.shutdown } /** * Retrieves the size of the queue, which is equal to the number of elements * in the queue. This may be negative if fibers are suspended waiting for * elements to be added to the queue. */ export function size<RA, RB, EA, EB, A, B>(self: XQueue<RA, RB, EA, EB, A, B>) { concreteQueue(self) return self.size } /** * Removes the oldest value in the queue. If the queue is empty, this will * return a computation that resumes when an item has been added to the queue. */ export function take<RA, RB, EA, EB, A, B>(self: XQueue<RA, RB, EA, EB, A, B>) { concreteQueue(self) return self.take } /** * Removes all the values in the queue and returns the list of the values. If the queue * is empty returns empty list. */ export function takeAll<RA, RB, EA, EB, A, B>(self: XQueue<RA, RB, EA, EB, A, B>) { concreteQueue(self) return self.takeAll } /** * Takes up to max number of values in the queue. * * @ets_data_first takeAllUpTo_ */ export function takeAllUpTo(n: number) { return <RA, RB, EA, EB, A, B>(self: XQueue<RA, RB, EA, EB, A, B>) => takeAllUpTo_(self, n) } /** * Takes up to max number of values in the queue. */ export function takeAllUpTo_<RA, RB, EA, EB, A, B>( self: XQueue<RA, RB, EA, EB, A, B>, n: number ) { concreteQueue(self) return self.takeUpTo(n) }