(** The Flan program's own thread, as the compiler reaches it when both are in one process. [flan dev] builds one binary that is the compiled program and holds this compiler. The program owns the main thread; this compiler is a thread beside it. What changed to make this module necessary is that a program which finishes no longer ends the process — the main thread parks, keeping every global the run left behind, and can be sent round [main] again. That is the Common Lisp and Clojure arrangement and it is wanted for their reason: closing a window should not cost the session, and getting another one should not cost a rebuild. Two facts cross from there to here, and both are a call into [lib/dynload_stubs.c] rather than anything the daemon can infer. Liveness used to be inferred — the program closed its stdout and the compiler read EOF off the pipe — and that could not survive a program that runs again: a pipe delivers EOF once, so the signal and the program's output were the same resource. Asking is what replaces it. [Absent] means there is no program thread in this process: the [flan] binary that builds a merged program, [flan reload], the two-process daemon, every test. As with [Agent.request] the answer comes from the linker — the symbols are weak and null in a binary that links no merged entry point — so it cannot disagree with reality the way a flag could. *) type state = | Running (* inside [main] *) | Parked (* finished, waiting to be sent round again *) | Absent (* this process has no program of its own *) external raw_state : unit -> int = "flan_program_state" external raw_rerun : unit -> int = "flan_program_rerun" let state () = match raw_state () with 0 -> Running | 1 -> Parked | _ -> Absent (* Run [main] again, on the thread that ran it before. The thread is not negotiable and is why there is no [Thread.create] here: a window belongs to the thread that opened it, and on macOS to the first thread of the process, so a second [main] anywhere else would draw nothing. This only leaves a request and wakes the sleeper, which is the same rule the agent already follows for everything the game thread has to do. The refusal is the program's to make, not this end's. Checking the state here and signalling after would leave a gap for the program to finish or start in; the C does both under one lock, so a request is either taken or told the program is running, and never both. *) let rerun () = match raw_rerun () with | 0 -> Ok () | 1 -> Error "the program is already running; a re-run starts main again, and two \ mains in one process would be writing the same globals at once. Close \ its window, or let it finish, and ask again" | _ -> Error "this session's program is a process of its own, so there is no parked \ thread here to send round again; it is the merged build that can re-run \ a program, not --two-process"