import { checkFunction, checkPositiveInt } from "../_check" import { NOOP_SUBSCRIPTION, Source, Subscription } from "../_core" import { In, Out, Projection } from "../_interfaces" import { toObs } from "../_interrop" import { makeObservable } from "../_obs" import { Transaction } from "../_tx" import { curry2, curry3 } from "../_util" import { dispatcherOf, Observable } from "../Observable" import { stepIn, stepOut } from "../scheduler/index" import { Pipe, PipeSubscriber } from "./_base" import { JoinOperator } from "./_join" export type InnerResult = T | Observable export const flatMapLatest: CurriedFlatMapLatest = curry2(_flatMapLatest) export interface CurriedFlatMapLatest { ( project: Projection>, observable: In, ): Out (project: Projection>): ( observable: In, ) => Out } export const flatMapFirst: CurriedFlatMapFirst = curry2(_flatMapFirst) export interface CurriedFlatMapFirst { ( project: Projection>, observable: In, ): Out (project: Projection>): ( observable: In, ) => Out } export const flatMap: CurriedFlatMap = curry2(_flatMap) export interface CurriedFlatMap { ( project: Projection>, observable: In, ): Out (project: Projection>): ( observable: In, ) => Out } export const flatMapConcat: CurriedFlatMapConcat = curry2(_flatMapConcat) export interface CurriedFlatMapConcat { ( project: Projection>, observable: In, ): Out (project: Projection>): ( observable: In, ) => Out } export const flatMapWithConcurrencyLimit: CurriedFlatMapWithConcurrencyLimit = curry3( _flatMapWithConcurrencyLimit, ) export interface CurriedFlatMapWithConcurrencyLimit { ( limit: number, project: Projection>, observable: In, ): Out (limit: number, project: Projection>): ( observable: In, ) => Out (limit: number): ( project: Projection>, observable: In, ) => Out (limit: number): ( project: Projection>, ) => (observable: In) => Out } function _flatMapLatest( project: Projection>, observable: Observable, ): Observable { checkFunction(project) return makeObservable(observable, new FlatMapLatest(dispatcherOf(observable), project)) } function _flatMapFirst( project: Projection>, observable: Observable, ): Observable { checkFunction(project) return makeObservable(observable, new FlatMapFirst(dispatcherOf(observable), project)) } function _flatMapConcat( project: Projection>, observable: Observable, ): Observable { return _flatMapWithConcurrencyLimitNoCheck(1, project, observable as any) } function _flatMap( project: Projection>, observable: Observable, ): Observable { return _flatMapWithConcurrencyLimitNoCheck(Infinity, project, observable as any) } function _flatMapWithConcurrencyLimit( limit: number, project: Projection>, observable: Observable, ): Observable { checkPositiveInt(limit) checkFunction(project) return _flatMapWithConcurrencyLimitNoCheck(limit, project, observable) } function _flatMapWithConcurrencyLimitNoCheck( limit: number, project: Projection>, observable: Observable, ): Observable { return makeObservable(observable, new FlatMapConcurrent(dispatcherOf(observable), project, limit)) } abstract class FlatMapBase extends JoinOperator implements PipeSubscriber { protected outerEnded: boolean = false protected otx: Transaction | null = null constructor(source: Source, protected readonly proj: Projection>) { super(source) } public abstract next(tx: Transaction, val: A): void public abstract innerEnd(sender: Pipe): boolean public abstract isInnerEnded(): boolean public abstract disposeInner(): void public abstract reorderInner(order: number): void public reorder(order: number): void { this.reorderInner(order) super.reorder(order) } public dispose(): void { this.outerEnded = false this.disposeInner() this.disposeOuter() super.dispose() } public end(tx: Transaction): void { this.outerEnded = true if (this.isInnerEnded()) { this.sink.end(tx) } else { this.disposeOuter() } } public pipedNext(sender: Pipe, tx: Transaction, val: B): void { this.forkNext(this.otx || tx, val) } public pipedError(sender: Pipe, tx: Transaction, err: Error): void { this.forkError(this.otx || tx, err) } public pipedEnd(sender: Pipe, tx: Transaction): void { // we need sender later so queue end as custom event this.forkCustom(this.otx || tx, sender) } protected inInnerCtx(f: any, tx: Transaction, args: any[]): void { const otx = this.otx this.otx = tx try { stepIn() f.apply(this, args) } finally { try { stepOut() } finally { this.otx = otx } } } protected joinCustom(tx: Transaction, sender: any): void { this.joinInnerEnd(tx, sender) } private joinInnerEnd(tx: Transaction, sender: Pipe): void { const innerEnded = this.innerEnd(sender) if (innerEnded && this.outerEnded) { this.sink.end(tx) } } private disposeOuter(): void { const { subs } = this this.subs = NOOP_SUBSCRIPTION subs.dispose() } } // TODO: flatten to constants enum IStatus { IDLE = 0, RUNNING = 1, ENDED = 2, } const { IDLE, RUNNING, ENDED } = IStatus abstract class FlatMapSwitchable extends FlatMapBase { protected isubs: Subscription = NOOP_SUBSCRIPTION protected istat: IStatus = IDLE public abstract next(tx: Transaction, val: A): void public innerEnd(sender: Pipe): boolean { this.disposeInner() this.istat = ENDED return true } public isInnerEnded(): boolean { return this.istat !== RUNNING } public reorderInner(order: number): void { this.isubs.reorder(order + 1) } public disposeInner(): void { const isRunning = this.istat === RUNNING this.istat = IDLE if (isRunning) { const { isubs } = this this.isubs = NOOP_SUBSCRIPTION isubs.dispose() } } protected switch(tx: Transaction, val: A): void { this.inInnerCtx(this.doSwitch, tx, [val]) } private doSwitch(val: A): void { const { isubs, proj } = this const inner = toObs>(proj(val)) const innerSource = dispatcherOf(inner) const subscription = (this.isubs = innerSource.subscribe(new Pipe(this), this.ord + 1)) this.istat = RUNNING isubs.dispose() this.abortJoin() subscription.activate(true) } } class FlatMapLatest extends FlatMapSwitchable { public next(tx: Transaction, val: A): void { this.switch(tx, val) } } class FlatMapFirst extends FlatMapSwitchable { public next(tx: Transaction, val: A): void { if (this.isInnerEnded()) { this.switch(tx, val) } } } class FlatMapConcurrent extends FlatMapBase { private itail: InnerPipe | null = null private ihead: InnerPipe | null = null private ni: number = 0 constructor(source: Source, proj: Projection>, private limit: number) { super(source, proj) } public next(tx: Transaction, val: A): void { if (this.append(new InnerPipe(this, val)) <= this.limit) { this.inInnerCtx(this.subscribeHead, tx, []) } } public innerEnd(pipe: Pipe): boolean { const inner = pipe as InnerPipe const n = --this.ni const subs = inner.subs inner.subs = NOOP_SUBSCRIPTION inner.t = null subs.dispose() if (this.ihead !== null) { this.inInnerCtx(this.subscribeHead, this.otx as Transaction, []) return false } else { return n === 0 } } public isInnerEnded(): boolean { return this.ni === 0 } public reorderInner(order: number): void { let inner = this.ihead while (inner !== null) { inner.subs.reorder(order) inner = inner.t } } public disposeInner(): void { let inner = this.itail this.itail = this.ihead = null this.ni = 0 while (inner !== null) { const subs = inner.subs inner.subs = NOOP_SUBSCRIPTION subs.dispose() inner = inner.t } } private append(inner: InnerPipe): number { if (this.itail === null) { this.itail = inner } else { this.itail = this.itail.t = inner } this.ihead === null && (this.ihead = this.itail) return ++this.ni } private subscribeHead(): void { const { proj } = this const inner = this.ihead as InnerPipe this.ihead = inner.t const iobs = toObs>(proj(inner.v)) inner.subs = dispatcherOf(iobs).subscribe(inner, this.ord + 1) inner.subs.activate(true) } } class InnerPipe extends Pipe { public t: InnerPipe | null = null public subs: Subscription = NOOP_SUBSCRIPTION constructor(dest: PipeSubscriber, public v: A) { super(dest) } }