import { Map, Collection, OrderedMap } from "immutable"; import { Sum } from "../collections/domains/sum/state"; import { id } from "../fun/domains/id/state"; import { Unit } from "../fun/domains/unit/state"; import { BasicUpdater } from "../fun/domains/updater/state"; import { BasicFun, Fun } from "../fun/state"; import { Guid, SimpleCallback } from "../../main"; export type DeltaT = number; export type Coroutine = { ([context, deltaT]: [context, DeltaT]): CoroutineStep; // map: ( // f: Fun // ) => Coroutine; // mapState: ( // f: Fun | undefined, Updater | undefined> // ) => Coroutine; embed: ( narrow: BasicFun, widen: BasicFun, BasicUpdater>, ) => Coroutine; then: ( f: BasicFun>, ) => Coroutine; seq: ( k: Coroutine, ) => Coroutine; }; const callMaybe = ( f: BasicFun, BasicUpdater>, ): BasicFun | undefined, BasicUpdater | undefined> => (x) => x == undefined ? undefined : f(x); const thenMaybe = ( f: BasicUpdater | undefined, g: BasicUpdater | undefined, ): BasicUpdater | undefined => f == undefined ? g : g == undefined ? f : (x) => g(f(x)); export const Coroutine = { Create: ( p: BasicFun<[context, DeltaT], CoroutineStep>, ): Coroutine => { const co = p as unknown as Coroutine; // co.map = function ( // f: Fun // ): Coroutine { // return Coroutine.Map(this, f); // }; // co.mapState = function ( // f: Fun | undefined, Updater | undefined> // ): Coroutine { // return Coroutine.MapState(this, f); // }; co.embed = function ( this: Coroutine, narrow: BasicFun, widen: BasicFun, BasicUpdater>, ): Coroutine { return Coroutine.Embed< context, state, result, parentContext, parentState >(this, narrow, widen); }; co.then = function ( f: BasicFun>, ): Coroutine { // return Coroutine.Join(Coroutine.Map(this, f)); return Coroutine.Create((_) => CoroutineStep.Then(undefined, this, f)); }; co.seq = function ( f: Coroutine, ): Coroutine { return this.then(() => f); }; return co; }, UpdateState: ( stateUpdater: BasicFun>, ): Coroutine => Coroutine.Create(([__, _]) => CoroutineStep.Result((state) => stateUpdater(state)(state), {}), ), SetState: ( stateUpdater: BasicUpdater, ): Coroutine => Coroutine.Yield( Coroutine.Create(([__, _]) => CoroutineStep.Result(stateUpdater, {})), ), GetState: (): Coroutine => Coroutine.Create(([context, __]) => CoroutineStep.Result(undefined, context), ), Start: (): Coroutine => Coroutine.Create(([__, _]) => CoroutineStep.Result(undefined, {})), Return: ( result: result, ): Coroutine => Coroutine.Create(([__, _]) => CoroutineStep.Result(undefined, result)), Yield: ( next: Coroutine, ): Coroutine => Coroutine.Create(([__, _]) => CoroutineStep.Yield(undefined, next)), Waiting: ( msLeft: number, next: Coroutine, ): Coroutine => Coroutine.Create(([__, _]) => CoroutineStep.Waiting(undefined, msLeft, next), ), // Map: ( // p: Coroutine, // f: Fun // ): Coroutine => // Coroutine.Create((state) => CoroutineStep.Map(p(state), f)), MapContext: ( p: Coroutine, narrow: BasicFun, ): Coroutine => Coroutine.Create(([parentContext, deltaT]) => { const childContext = narrow(parentContext); if (childContext != undefined) return CoroutineStep.MapContext(p([childContext, deltaT]), narrow); return CoroutineStep.Yield(undefined, Coroutine.MapContext(p, narrow)); }), Embed: ( p: Coroutine, narrow: BasicFun, widen: BasicFun, BasicUpdater>, ): Coroutine => Coroutine.Create(([parentContext, deltaT]) => { const childContext = narrow(parentContext); if (childContext != undefined) return CoroutineStep.Embed(p([childContext, deltaT]), narrow, widen); return CoroutineStep.Yield(undefined, Coroutine.Embed(p, narrow, widen)); }), MapState: ( p: Coroutine, f: BasicFun, BasicUpdater>, ): Coroutine => Coroutine.Create((state) => CoroutineStep.MapState(p(state), f)), // Join: ( // p: Coroutine> // ): Coroutine => // Coroutine.Create(([state, deltaT]) => { // const step = p([state, deltaT]); // if (step.kind == "result") // return CoroutineStep.MapState(step // .result([(step.newState || id)(state), deltaT]), // (_) => // _ == undefined // ? step.newState // : step.newState == undefined // ? _ // : then(step.newState, _) // ); // if (step.kind == "yield") // return CoroutineStep.Yield(step.newState, Coroutine.Join(step.next)); // if (step.kind == "waiting") { // return CoroutineStep.Waiting( // step.newState, // step.msLeft, // Coroutine.Join(step.next) // ); // } // return CoroutineStep.WaitingForEvent(step.newState, (e) => { // const next = step.next(e); // return next == "no match" ? next : Coroutine.Join(next); // }); // }), Tick: ( context: context, p: Coroutine, deltaT: number, ): | { kind: "continuing"; excludeOthers?: true; state: BasicUpdater | undefined; next: Coroutine; } | { kind: "done"; state: BasicUpdater | undefined; result: result; } => { // debugger const step = p([context, deltaT]); if (step.kind == "result") return { kind: "done", state: step.newState, result: step.result }; if (step.kind == "yield") return { kind: "continuing", state: step.newState, next: step.next }; if (step.kind == "waiting" && step.msLeft <= deltaT) return { kind: "continuing", state: step.newState, next: step.next }; if (step.kind == "waiting") return { kind: "continuing", state: step.newState, next: Coroutine.Waiting(step.msLeft - deltaT, step.next), }; const next = Coroutine.Tick(context, step.p, deltaT); if (next.kind == "done") return { kind: "continuing", state: thenMaybe(step.newState, next.state), next: step.k(next.result), }; else return { kind: "continuing", state: next.state, next: next.next.then(step.k), }; }, Nothing: (): Coroutine => Coroutine.Create(([__, _]) => CoroutineStep.Yield(undefined, Coroutine.Nothing()), ), Any: ( ps: Array>, ): Coroutine => Coroutine.Create(([context, deltaT]) => { const ps1: Array> = []; let nextState: BasicUpdater | undefined = undefined; for (const p of ps) { const step = Coroutine.Tick(context, p, deltaT); nextState = thenMaybe(nextState, step.state); if (step.kind == "done") return CoroutineStep.Result(nextState, step.result); else { if (step.excludeOthers) { // console.log( // `exclude others detected, killing ${ps.length - 1 // } other coroutines` // ); return CoroutineStep.MapState( step.next([context, deltaT]), (_) => thenMaybe(nextState, _) || id, ); } else ps1.push(step.next); } } return CoroutineStep.Yield(nextState, Coroutine.Any(ps1)); }), All: ( ps: Array>, ): Coroutine> => Coroutine.Create(([context, deltaT]) => { const ps1: Array> = []; let nextState: BasicUpdater | undefined = undefined; let stillRunning = false; const results: Array = []; for (const p of ps) { const step = Coroutine.Tick(context, p, deltaT); nextState = thenMaybe(nextState, step.state); if (step.kind == "done") { results.push(step.result); ps1.push(Coroutine.Return(step.result)); } else { stillRunning = true; ps1.push(step.next); } } return stillRunning ? CoroutineStep.Yield(nextState, Coroutine.All(ps1)) : CoroutineStep.Result(nextState, results); }), Repeat: ( p: Coroutine, ): Coroutine => p.then((_) => Coroutine.Yield(Coroutine.Repeat(p))), Seq: ( ps: Array>, ): Coroutine => ps.length <= 0 ? Coroutine.Return({}) : ps[0].then(() => Coroutine.Seq(ps.slice(1))), For: ( collection: Collection, p: BasicFun>, ): Coroutine => collection.isEmpty() ? Coroutine.Return({}) : Coroutine.Seq([ p(collection.first()!), Coroutine.For(collection.skip(1), p), ]), While: ( predicate: BasicFun<[context], boolean>, p: Coroutine, ): Coroutine => Coroutine.Create(([context, deltaT]) => { if (predicate([context])) { return p.then((_) => Coroutine.While(predicate, p))([ context, deltaT, ]); } else return CoroutineStep.Result(undefined, {}); }), Wait: (ms: number): Coroutine => Coroutine.Waiting(ms, Coroutine.Return({})), Await: ( promise: BasicFun>, onCatch: BasicFun, debugName?: string, ): Coroutine> => Coroutine.Create(([_, __]) => { let promiseResult: | { kind: "resolve"; result: result } | { kind: "reject"; error: error } | undefined = undefined; // const started = Date.now(); // if (SharedLayoutConstants.LogCoroutineTicks) // console.log(`co::await::launched ${debugName}`); setTimeout(() => promise({}) .then((result) => { promiseResult = { kind: "resolve", result: result }; }) .catch((_) => { promiseResult = { kind: "reject", error: onCatch(_) }; }), ); // if (SharedLayoutConstants.LogCoroutineTicks) // console.log( // `co::await::creating awaiter ${debugName} (deltaT = ${ // Date.now() - started // })` // ); const awaiter = (): CoroutineStep> => { // if (SharedLayoutConstants.LogCoroutineTicks) // console.log( // `co::await::checking awaiter ${debugName}`, // promiseResult // ); return promiseResult == undefined ? CoroutineStep.Yield( undefined, Coroutine.Create(([_, __]) => awaiter()), ) : promiseResult.kind == "resolve" ? CoroutineStep.Result( undefined, Sum().Default.left(promiseResult.result), ) : CoroutineStep.Result( undefined, Sum().Default.right(promiseResult.error), ); }; return CoroutineStep.Yield( undefined, Coroutine.Create(([_, __]) => awaiter()), ); }), On: < eventKind, event, context extends { inboundEvents: Map> }, state extends { inboundEvents: Map> }, matchedEvent extends InboundKindFromContext, >( kind: matchedEvent, filter?: BasicFun< [ InboundEventFromContext & { kind: matchedEvent }, context & state, ], boolean >, ): Coroutine< context & state, state, InboundEventFromContext & { kind: matchedEvent } > => Coroutine.GetState().then((context) => { // { inboundEvents:Map, OrderedMap>> } let inboundEvents = (context as any).inboundEvents ?? Map(); let eventsOfKind = ((context as any).inboundEvents ?? Map()).get( kind as any, ); if (eventsOfKind == undefined || eventsOfKind.isEmpty()) { return Coroutine.Yield< context & state, state, InboundEventFromContext & { kind: matchedEvent } >( Coroutine.On( kind, filter, ), ); } else { const firstEventOfKind = filter == undefined ? eventsOfKind?.first() : eventsOfKind .filter((e: any) => { return filter([e, context]); }) .first(); if (firstEventOfKind == undefined) { return Coroutine.Yield< context & state, state, InboundEventFromContext & { kind: matchedEvent } >( Coroutine.On( kind, filter, ), ); } eventsOfKind = eventsOfKind.remove(firstEventOfKind.id); if (eventsOfKind.isEmpty()) inboundEvents = inboundEvents.remove(kind); else inboundEvents = inboundEvents.set(kind, eventsOfKind); return Coroutine.SetState((_) => ({ ..._, inboundEvents: inboundEvents, })).then(() => Coroutine.Return< context & state, state, InboundEventFromContext & { kind: matchedEvent } >(firstEventOfKind), ); } }), Trigger: < context, state extends { outboundEvents: Map> }, event extends { id: Guid; kind: kind }, kind extends string, >( event: event, ): Coroutine => Coroutine.SetState((_) => ({ ..._, outboundEvents: _.outboundEvents.set( event.kind, (_.outboundEvents.get(event.kind) ?? OrderedMap()).set(event.id, event), ), })), Do: ( action: SimpleCallback, ): Coroutine => Coroutine.Create(([_, __]) => { action(); return CoroutineStep.Yield(undefined, Coroutine.Return({})); }), }; export type CoroutineStep = { newState: BasicUpdater | undefined; } & ( | { kind: "result"; result: result } | { kind: "then"; p: Coroutine; k: BasicFun>; } | { kind: "yield"; next: Coroutine } | { kind: "waiting"; msLeft: number; next: Coroutine; } ) & { // map: ( // f: Fun // ) => CoroutineStep; // mapState: ( // f: Fun | undefined, Updater | undefined> // ) => CoroutineStep; }; export const CoroutineStep = { Result: ( newState: BasicUpdater | undefined, result: result, ): CoroutineStep => ({ newState, kind: "result", result: result, // map: function (f: Fun) { // return CoroutineStep.Map(this, f); // }, // mapState: function ( // f: Fun | undefined, Updater | undefined> // ) { // return CoroutineStep.MapState(this, f); // }, }), Then: ( newState: BasicUpdater | undefined, p: Coroutine, k: BasicFun>, ): CoroutineStep => ({ kind: "then", newState: newState, p: p, k: k, // map: function (f: Fun) { // return CoroutineStep.Map(this, f); // }, // mapState: function ( // f: Fun | undefined, Updater | undefined> // ) { // return CoroutineStep.MapState(this, f); // }, }), Yield: ( newState: BasicUpdater | undefined, next: Coroutine, ): CoroutineStep => ({ newState, kind: "yield", next: next, // map: function (f: Fun) { // return CoroutineStep.Map(this, f); // }, // mapState: function ( // f: Fun | undefined, Updater | undefined> // ) { // return CoroutineStep.MapState(this, f); // }, }), Waiting: ( newState: BasicUpdater | undefined, msLeft: number, next: Coroutine, ): CoroutineStep => ({ newState, kind: "waiting", msLeft: msLeft, next: next, // map: function (f: Fun) { // return CoroutineStep.Map(this, f); // }, // mapState: function ( // f: Fun | undefined, Updater | undefined> // ) { // return CoroutineStep.MapState(this, f); // }, }), // Map: ( // p: CoroutineStep, // f: Fun // ): CoroutineStep => // p.kind == "result" // ? CoroutineStep.Result(p.newState, f(p.result)) // : p.kind == "yield" // ? CoroutineStep.Yield(p.newState, Coroutine.Map(p.next, f)) // : p.kind == "waiting" // ? CoroutineStep.Waiting(p.newState, p.msLeft, Coroutine.Map(p.next, f)) // : CoroutineStep.WaitingForEvent(p.newState, (e) => { // const next = p.next(e); // return next == "no match" ? next : Coroutine.Map(next, f) // }), MapContext: ( p: CoroutineStep, narrow: BasicFun, ): CoroutineStep => p.kind == "result" ? p : p.kind == "yield" ? CoroutineStep.Yield(p.newState, Coroutine.MapContext(p.next, narrow)) : p.kind == "waiting" ? CoroutineStep.Waiting( p.newState, p.msLeft, Coroutine.MapContext(p.next, narrow), ) : CoroutineStep.Then( p.newState, Coroutine.MapContext(p.p, narrow), Fun(p.k).then((_) => Coroutine.MapContext(_, narrow)), ), MapState: ( p: CoroutineStep, f: BasicFun, BasicUpdater>, ): CoroutineStep => p.kind == "result" ? CoroutineStep.Result(callMaybe(f)(p.newState), p.result) : p.kind == "yield" ? CoroutineStep.Yield( callMaybe(f)(p.newState), Coroutine.MapState(p.next, f), ) : p.kind == "waiting" ? CoroutineStep.Waiting( callMaybe(f)(p.newState), p.msLeft, Coroutine.MapState(p.next, f), ) : CoroutineStep.Then( callMaybe(f)(p.newState), Coroutine.MapState(p.p, f), Fun(p.k).then((_) => Coroutine.MapState(_, f)), ), Embed: ( p: CoroutineStep, narrow: BasicFun, widen: BasicFun, BasicUpdater>, ): CoroutineStep => CoroutineStep.MapState(CoroutineStep.MapContext(p, narrow), widen), }; export type InboundEventFromContext = s extends { inboundEvents: Map>; } ? event : never; export type InboundKindFromContext = s extends { inboundEvents: Map>; } ? kind : never;