problem 1 - no idea this is an issue of the boring kind that I would happily delegate to an AI
i've come back to the issue of modeling https://clojurians.slack.com/archives/CL85MBPEF/p1752396898983449?thread_ts=1752314760.885599&cid=CL85MBPEF but this time with runnable code and a distilled problem statement. would really appreciate any and all input on this!
in short:
• imagine you're given a game client api https://github.com/hokomo/missionary-lab/blob/da1168a9f8dec9436746cb94cc8c03037bad4bf6/src/missionary_lab/examples/client/core.clj#L153-L169. it's a java swing/edt-style api: the client has a main loop that runs somewhere in the background, but you can register listeners for events (`register!` and unregister!) or schedule invocations of arbitrary thunks (`invoke!`). all dispatching is done from the client thread and the client state MUST be accessed (`state`) only from the client thread, otherwise you have a data race.
• you can missionarify the api https://github.com/hokomo/missionary-lab/blob/da1168a9f8dec9436746cb94cc8c03037bad4bf6/src/missionary_lab/examples/client/wrapper.clj#L38-L48. listener registrations can be turned into flows (`client-events`) that transfer from the client thread. thunk invocations can be used execute arbitrary code and take control of the client thread whenever necessary (`m/via`, to-client; more on the implementation of to-client below).
• with the above setup, the goal is to implement a https://github.com/hokomo/missionary-lab/blob/da1168a9f8dec9436746cb94cc8c03037bad4bf6/src/missionary_lab/examples/client_machine.clj#L9-L28. note the most important requirements: it must access the client's state correctly and not have data races, and it must be able to manage asynchronous jobs that execute independently of the client thread. all of this should be easily cancellable and should clean up properly when something goes wrong.
my implementation works roughly like this:
• a generic synchronous state https://github.com/hokomo/missionary-lab/blob/da1168a9f8dec9436746cb94cc8c03037bad4bf6/src/missionary_lab/examples/client_machine.clj#L50-L78 is modeled as a reduction over a flow of events. at each step the machine has a "behavior" which is the function that will receive an event and return the next behavior.
• the machine has an accompanying https://github.com/hokomo/missionary-lab/blob/da1168a9f8dec9436746cb94cc8c03037bad4bf6/src/missionary_lab/examples/client_machine.clj#L32-L46 running concurrently with its top-level reduction loop which can be used to execute tasks asynchronously with the flow of events (in our case, asynchronously with the client thread).
• the https://github.com/hokomo/missionary-lab/blob/da1168a9f8dec9436746cb94cc8c03037bad4bf6/src/missionary_lab/examples/client_machine.clj#L82-L137 we have to implement is then just a heap of behaviors that switch control between one another and pass state around. you can jack in and https://github.com/hokomo/missionary-lab/blob/da1168a9f8dec9436746cb94cc8c03037bad4bf6/src/missionary_lab/examples/client_machine.clj#L139-L150 to see it in action.
some questions and thoughts:
• can you think of cleaner or more composable approaches? are there any useful or obvious generic behavior combinators that you can think of when you look at the machine?
• execution of asynchronous jobs in the machine is somewhat "low-level" and has to be managed completely manually. this is understandable since we cannot block the machine loop and since the jobs' lifetimes transcend that of a single behavior, but i keep wondering if there are better and/or more composable way to do it. i don't know exactly what it would look like or if it's even possible. thoughts?
• is my definition of https://github.com/hokomo/missionary-lab/blob/da1168a9f8dec9436746cb94cc8c03037bad4bf6/src/missionary_lab/util.clj#L22-L27 (and therefore https://github.com/hokomo/missionary-lab/blob/da1168a9f8dec9436746cb94cc8c03037bad4bf6/src/missionary_lab/examples/client/wrapper.clj#L21-L24) guaranteed to be correct? the idea is that by (ab)using m/via we can execute a noop thunk on the target thread and then resume our coroutine from exactly that thread, due to how missionary's context switches (are guaranteed to?) work. essentially we can "switch to" and take control of any desired thread.
• i'm not too fond of having to https://github.com/hokomo/missionary-lab/blob/da1168a9f8dec9436746cb94cc8c03037bad4bf6/src/missionary_lab/examples/client_machine.clj#L44-L46 to implement cancellation of tasks started on the machine's executor. is there some better way to bridge the gap between tasks and processes?