import { Source } from "../_core" import { Set } from "../_polyfill" import { priorityOf } from "../_priority" import { Operation, Transaction } from "../_tx" import { EventType, Operator } from "./_base" export abstract class JoinOperator extends Operator { private forked: boolean = false private errs: Set = new Set() private head: ForkedEvent | null = null private tail: ForkedEvent | null = null private n: number = 0 private hasErr: boolean = false constructor(origin: Source) { super(origin) } public dispose(): void { this.abortJoin() super.dispose() } public startJoin(tx: Transaction): void { this.forked = false this.join(tx) } public abortJoin(): void { this.forked = false this.hasErr && ((this.hasErr = false) || this.errs.clear()) this.head = this.tail = null this.n = 0 } public join(tx: Transaction): void { const n = this.n let head = this.head this.hasErr && ((this.hasErr = false) || this.errs.clear()) this.head = this.tail = null this.n = 0 // perf optimization: usual case is that we have only one, so let's write it explictly if (n === 1) { this.handleFE(tx, head as ForkedEvent) } else { while (head !== null && this.active) { this.handleFE(tx, head) head = head.n } } } protected fork(tx: Transaction): void { if (this.forked === false) { this.forked = true tx.queue(new Join(priorityOf(this.ord, this.weight), this as any)) } } protected isForked(): boolean { return this.forked } protected forkNext(tx: Transaction, val: B): void { this.fe(tx, { t: EventType.NEXT, v: val, n: null, e: null as any }) } protected forkError(tx: Transaction, err: Error): void { if (!this.errs.has(err)) { this.hasErr = true this.errs.add(err) this.fe(tx, { t: EventType.ERROR, e: err, n: null, v: null as any }) } } protected forkEnd(tx: Transaction): void { this.fe(tx, { t: EventType.END, n: null, e: null as any, v: null as any }) } protected forkCustom(tx: Transaction, val: any): void { this.fe(tx, { t: -1 as any, v: val, n: null, e: null as any }) } protected joinNext(tx: Transaction, val: B): void { // TODO: send in try-catch block (sendNext)?? this.sink.next(tx, val) } protected joinError(tx: Transaction, err: Error): void { this.sink.error(tx, err) } protected joinEnd(tx: Transaction): void { this.sink.end(tx) } protected joinCustom(tx: Transaction, val: any): void {} private handleFE(tx: Transaction, fe: ForkedEvent): void { switch (fe.t) { case EventType.NEXT: this.joinNext(tx, (fe.v as any) as B) break case EventType.ERROR: this.joinError(tx, (fe.e as any) as Error) break case EventType.END: this.joinEnd(tx) break default: this.joinCustom(tx, fe.v) break } } private fe(tx: Transaction, fe: ForkedEvent) { ++this.n this.tail === null ? (this.head = this.tail = fe) : (this.tail = this.tail.n = fe) this.fork(tx) } } class Join implements Operation { constructor(public priority: number, private target: JoinOperator) {} public exec(tx: Transaction): void { this.target.startJoin(tx) } public abort(): void { this.target.abortJoin() } } interface ForkedEvent { t: EventType v?: T e?: Error n: ForkedEvent | null }