import React from "react"; import { unit, Unit } from "../fun/domains/unit/state"; import { BasicUpdater, Updater } from "../fun/domains/updater/state"; import { BasicFun } from "../fun/state"; import { Coroutine } from "./state"; import { Template, TemplateProps } from "../template/state"; export type CoroutineComponentOptions = { interval?: number; key?: BasicFun, string>; restartWhenFinished?: boolean; runFilter?: BasicFun, boolean>; }; const Co = Coroutine; type CoroutineReadonlyContext = { initialCoroutine: Coroutine; options?: CoroutineComponentOptions; }; type CoroutineComponentProps = { context: context; setState: BasicFun, void>; } & CoroutineReadonlyContext; type CoroutineComponentState = { currentCoroutine: Coroutine; }; class CoroutineComponent extends React.Component< CoroutineComponentProps, CoroutineComponentState > { constructor(props: CoroutineComponentProps) { super(props); this.state = { currentCoroutine: props.initialCoroutine }; } running = false; animationFrameId?: NodeJS.Timeout = undefined; componentDidMount(): void { let lastTimestamp = Date.now(); this.running = true; const tick = () => { if (!this.running) { clearInterval(this.animationFrameId); return; } const currTimestamp = Date.now(); const step = Co.Tick( this.props.context, this.state.currentCoroutine, currTimestamp - lastTimestamp, ); lastTimestamp = currTimestamp; if (!this.running) return; if (step.kind == "done") { this.setState( (s) => ({ ...s, currentCoroutine: !!this.props.options?.restartWhenFinished ? this.props.initialCoroutine : Co.Nothing(), }), () => step.state && this.running ? this.props.setState(step.state) : undefined, ); } else { this.setState( (s) => ({ ...s, currentCoroutine: step.next }), () => step.state && this.running ? this.props.setState(Updater(step.state)) : undefined, ); } // if (this.running) // this.animationFrameId = setTimeout( // tick, // this.props.options?.interval || 250 // ); }; this.animationFrameId = setInterval( tick, this.props.options?.interval || 250, ); } componentWillUnmount(): void { this.running = false; clearInterval(this.animationFrameId); } shouldComponentUpdate(): boolean { return false; } render() { return <>; } } export const CoroutineTemplate = () => Template.Default< context & CoroutineReadonlyContext, state, foreignMutations >((props) => ( ));