import { Evt, VoidEvt } from "../index"; import { assert, typeGuard } from "../../tools/typeSafety"; import { EvtError } from "../types/EvtError"; import { UnpackEvt, NonPostable, OneShot } from "../types/helper"; import { id } from "../../tools/typeSafety"; import { Deferred } from "../../tools/Deferred"; import { invokeOperator } from "./invokeOperator"; import { Operator } from "../types/Operator"; import { parseOverloadParamsFactory } from "./parseOverloadParams"; import { Ctx } from "../Ctx"; export type OneShotEvt = OneShot>; export type Racer = OneShotEvt | PromiseLike | T; export type UnpackRacer> = T extends OneShotEvt ? U : T extends PromiseLike ? V : T; export type RaceResultGeneric[]> = Readonly<{ data: Data; i: Index; racer: Racers[Index]; }>; export type RaceResult = RaceResultGeneric< UnpackRacer, number, readonly RacerUnion[] >; const prNever = new Promise(() => { }); type RaceRecResult = { data: UnpackRacer | T; i: null | number; }; const matchOnceEvt = (o: any): o is OneShotEvt => { assert(typeGuard.dry>(o)); return ( o instanceof Object && o.attachOnce instanceof Function && o.detach instanceof Function && o.waitFor instanceof Function ); }; const matchPromiseLike = (o: any): o is PromiseLike => { assert(typeGuard.dry>(o)); return ( o instanceof Object && o.then instanceof Function ); } /** If promise racer rejection unhandled */ const raceUnsafe = (() => { type RaceContext = Ctx & { evtRaceFinished: NonPostable; }; const raceUnsafeRec = (() => { type RaceCoupleResult = { data: UnionOfTAndU; i: 0 | 1; }; const raceCouple = (() => { const matchDirectValue = (o: OneShotEvt | PromiseLike | T): o is T => { return !matchOnceEvt(o) && !matchPromiseLike(o); }; const raceCoupleSym = ( evt: Evt>, raceContext: RaceContext, racer: Racer, i: 0 | 1 ): void => { let evtWeak: (typeof evt) | undefined = evt; raceContext.evtRaceFinished.attachOnce(() => evtWeak = undefined); const post = (raceCoupleResult: UnpackEvt) => { evt.getEvtAttach().detach(raceContext); evt.post(raceCoupleResult); }; if (matchDirectValue(racer)) { const raceCoupleResult: UnpackEvt = { "data": racer, i }; evt.getEvtAttach().attach( raceContext, ({ op }) => { assert(!Operator.fλ.Stateful.match(op)); if (!op(raceCoupleResult)) { return; } post(raceCoupleResult); } ); } if (matchPromiseLike(racer)) { racer.then(data => { if (evtWeak === undefined) { return; } const raceCoupleResult: UnpackEvt = { data, i }; if (evtWeak.isHandled(raceCoupleResult)) { post(raceCoupleResult); return; } evtWeak.getEvtAttach().attach( raceContext, ({ op }) => { assert(!Operator.fλ.Stateful.match(op)); if (!op(raceCoupleResult)) { return; } post(raceCoupleResult); } ); }); } if (matchOnceEvt(racer)) { const toRaceCoupleResult = (data: T): UnpackEvt => ({ data, i }); evt.getEvtAttach().attach( raceContext, ({ op }) => racer.attachOnce( data => { assert(!Operator.fλ.Stateful.match(op)); return !!op(toRaceCoupleResult(data)); }, raceContext, data => post(toRaceCoupleResult(data)) ) ); } }; return function raceCouple( raceContext: RaceContext, p1: Racer, p2: Racer ): OneShotEvt> { const evt = new Evt>(); raceCoupleSym(evt, raceContext, p1, 0); raceCoupleSym(evt, raceContext, p2, 1); return evt; } })(); return function raceUnsafeRec( raceContext: RaceContext, racersRest: readonly RacerUnion[], racerLast: Racer, ): OneShotEvt> { const evt = new Evt>(); const post = (raceRecResult: UnpackEvt) => { evt.getEvtAttach().detach(raceContext); evt.post(raceRecResult); }; if (racersRest.length === 0) { const toRaceRecResult = ( raceCoupleResult: RaceCoupleResult ): RaceRecResult => ({ ...raceCoupleResult, "i": null }); const evtRaceCoupleResult = raceCouple(raceContext, racerLast, prNever); evt.getEvtAttach().attach( raceContext, ({ op }) => evtRaceCoupleResult.attachOnce( raceCoupleResult => { assert(!Operator.fλ.Stateful.match(op)); return !!op(toRaceRecResult(raceCoupleResult)); }, raceCoupleResult => post(toRaceRecResult(raceCoupleResult)) ) ); return evt; } const newRest = racersRest.slice(0, racersRest.length - 1); const newRacerLast = racersRest[racersRest.length - 1]; const evtData = new Evt>(); let raceCoupleResult_i: 0 | 1; { const evtRaceCoupleResult = raceCouple, T>( raceContext, newRacerLast as Racer>, racerLast, ); const toData = ( raceCoupleResult: RaceCoupleResult> ): T | UnpackRacer => { raceCoupleResult_i = raceCoupleResult.i; return raceCoupleResult.data; } evtData.getEvtAttach().attach( raceContext, ({ op }) => evtRaceCoupleResult.attachOnce( raceCoupleResult => { assert(!Operator.fλ.Stateful.match(op)); return !!op(toData(raceCoupleResult)); }, raceCoupleResult => { evtData.getEvtAttach().detach(raceContext); evtData.post(toData(raceCoupleResult)); } ) ); } { const evtRaceRecResult = raceUnsafeRec>( raceContext, newRest, evtData, ); const transformRaceRecResult = ( raceRecResult: RaceRecResult> ): RaceRecResult => ({ "data": raceRecResult.data, "i": raceRecResult.i === null ? raceCoupleResult_i === 0 ? racersRest.length - 1 : null : raceRecResult.i }); evt.getEvtAttach().attach( raceContext, ({ op }) => evtRaceRecResult.attachOnce( raceRecResult => { assert(!Operator.fλ.Stateful.match(op)); return !!op(transformRaceRecResult(raceRecResult)); }, raceRecResult => post(transformRaceRecResult(raceRecResult)) ) ); } return evt; }; })(); return function raceUnsafe( racers: readonly RacerUnion[] ): OneShotEvt> { const evt = new Evt>(); const evtRaceFinished = new VoidEvt(); const raceContext: RaceContext = Object.assign(Evt.newCtx(), { evtRaceFinished }); const evtRaceRecResult = raceUnsafeRec>( raceContext, racers, prNever ); const toRaceResult = ( raceRecResult: RaceRecResult> ): RaceResult => { assert(!typeGuard.dry(raceRecResult.i, false)); return { ...raceRecResult, "i": raceRecResult.i, "racer": racers[raceRecResult.i] }; }; const detachAllEvtRacers = () => { evtRaceFinished.post(); racers .forEach(racer => { if (!matchOnceEvt>(racer)) { return; } racer.detach(raceContext); }) }; evt.getEvtAttach().attach( raceContext, ({ op, promise }) => { promise.catch(() => { if (evt.getHandlers().length !== 0) { return; } detachAllEvtRacers(); }); evtRaceRecResult.attachOnce( raceRecResult => { assert(!Operator.fλ.Stateful.match(op)); return !!op(toRaceResult(raceRecResult)); }, raceContext, raceRecResult => { evt.getEvtAttach().detach(raceContext); detachAllEvtRacers(); evt.post(toRaceResult(raceRecResult)); } ); } ); return evt; }; })(); export type PrResultWrap = PrResultWrap.Fulfilled | PrResultWrap.Rejected ; export namespace PrResultWrap { type Common = { promise: PromiseLike; }; export type Fulfilled = Common & { isFulfilled: true; data: T; }; export type Rejected = Common & { isFulfilled: false; data: never; error: any; }; } function wrapRejection(promise: PromiseLike): PromiseLike> { return promise.then( data => id>({ promise, "isFulfilled": true, data }), error => id>({ promise, "isFulfilled": false, "data": null as unknown as never, error }) ); } function generateProxyFunctionFactory(oneShotEvt: OneShotEvt>>) { const parseOverloadParams = parseOverloadParamsFactory(); return function generateProxyFunction(methodName: "waitFor" | "attachOnce") { const methodBackup: (...inputs: any[]) => Promise = id<(...inputs: any[]) => Promise>(oneShotEvt[methodName]).bind(oneShotEvt) ; Object.defineProperty( oneShotEvt, methodName, { "value": (...inputs: any[]) => { const { op } = parseOverloadParams( inputs, methodName === "waitFor" ? "waitFor" : "attach*" ); let i = inputs.indexOf(op); if (i < 0) { inputs = [undefined, ...inputs]; i = 0; } const dOut = new Deferred(); inputs[i] = function matcherOverride(raceResult: RaceResult>) { if (!matchPromiseLike(raceResult.racer)) { assert(!Operator.fλ.Stateful.match(op)); return op(raceResult); } const prResultWrap: PrResultWrap = raceResult.data; if (!prResultWrap.isFulfilled) { dOut.reject( new EvtError.RacePromiseRejection( prResultWrap.error, raceResult.i, raceResult.racer ) ); return "DETACH"; } assert(!Operator.fλ.Stateful.match(op)); return invokeOperator( op, { "i": raceResult.i, "data": prResultWrap.data, "racer": prResultWrap.promise, } ); }; methodBackup(...inputs) .then( data => dOut.resolve(data), error => dOut.reject(error) ) ; return dOut.pr; } } ); } }; export function race( racers: readonly [ Racer, Racer ] ): OneShotEvt< RaceResultGeneric | RaceResultGeneric >; export function race( racers: readonly [ Racer, Racer, Racer ] ): OneShotEvt< RaceResultGeneric | RaceResultGeneric | RaceResultGeneric >; export function race( racers: readonly [ Racer, Racer, Racer, Racer ] ): OneShotEvt< RaceResultGeneric | RaceResultGeneric | RaceResultGeneric | RaceResultGeneric >; export function race( racers: readonly [ Racer, Racer, Racer, Racer, Racer ] ): OneShotEvt< RaceResultGeneric | RaceResultGeneric | RaceResultGeneric | RaceResultGeneric | RaceResultGeneric >; export function race( racers: readonly [ Racer, Racer, Racer, Racer, Racer, Racer ] ): OneShotEvt< RaceResultGeneric | RaceResultGeneric | RaceResultGeneric | RaceResultGeneric | RaceResultGeneric | RaceResultGeneric >; export function race( racers: readonly [ Racer, Racer, Racer, Racer, Racer, Racer, Racer ] ): OneShotEvt< RaceResultGeneric | RaceResultGeneric | RaceResultGeneric | RaceResultGeneric | RaceResultGeneric | RaceResultGeneric | RaceResultGeneric >; export function race( racers: readonly [ Racer, Racer, Racer, Racer, Racer, Racer, Racer, Racer ] ): OneShotEvt< RaceResultGeneric | RaceResultGeneric | RaceResultGeneric | RaceResultGeneric | RaceResultGeneric | RaceResultGeneric | RaceResultGeneric | RaceResultGeneric >; export function race( racers: readonly [ Racer, Racer, Racer, Racer, Racer, Racer, Racer, Racer, Racer ] ): OneShotEvt< RaceResultGeneric | RaceResultGeneric | RaceResultGeneric | RaceResultGeneric | RaceResultGeneric | RaceResultGeneric | RaceResultGeneric | RaceResultGeneric | RaceResultGeneric >; export function race( racers: readonly [ Racer, Racer, Racer, Racer, Racer, Racer, Racer, Racer, Racer, Racer ] ): OneShotEvt< RaceResultGeneric | RaceResultGeneric | RaceResultGeneric | RaceResultGeneric | RaceResultGeneric | RaceResultGeneric | RaceResultGeneric | RaceResultGeneric | RaceResultGeneric | RaceResultGeneric >; export function race( racers: readonly [ Racer, Racer, Racer, Racer, Racer, Racer, Racer, Racer, Racer, Racer, Racer ] ): OneShotEvt< RaceResultGeneric | RaceResultGeneric | RaceResultGeneric | RaceResultGeneric | RaceResultGeneric | RaceResultGeneric | RaceResultGeneric | RaceResultGeneric | RaceResultGeneric | RaceResultGeneric | RaceResultGeneric >; export function race( racers: readonly [ Racer, Racer, Racer, Racer, Racer, Racer, Racer, Racer, Racer, Racer, Racer, Racer ] ): OneShotEvt< RaceResultGeneric | RaceResultGeneric | RaceResultGeneric | RaceResultGeneric | RaceResultGeneric | RaceResultGeneric | RaceResultGeneric | RaceResultGeneric | RaceResultGeneric | RaceResultGeneric | RaceResultGeneric | RaceResultGeneric >; export function race( racers: readonly RacerUnion[] ): OneShotEvt>; export function race( racers: readonly RacerUnion[] ): OneShotEvt { const oneShotEvt = raceUnsafe( racers.map( racer => matchPromiseLike>(racer) ? wrapRejection>(racer) : racer ) ); { const generateProxyFunction = generateProxyFunctionFactory(oneShotEvt); (["waitFor", "attachOnce"] as const) .forEach(generateProxyFunction) ; } return id>>>(oneShotEvt); };