import java.util.concurrent.CompletableFuture; import java.util.function.LongSupplier; /// Drives one automaton over FFI. /// /// The automaton is either the effect-routine greeter or the capabilities /// greeter; both are spawned into the same table on the Rust side and answer /// to the same calls, so the only difference here is which `spawn` runs. /// /// Each `resume` returns a batch of effects. Fire-and-forget effects are /// performed inline; the awaited effect (always last) is answered with the /// matching `effectRoutineReply*` call on the automaton handle, and the next /// `resume` is scheduled — immediately for answers the host has to hand, later /// for `Sleep`. Every step is a fresh pool task, so the automaton hops threads. final class EffectRoutineDriver implements Scheduler.Machine { private final Native lib; private final Host host; private final long handle; private final CompletableFuture done = new CompletableFuture<>(); EffectRoutineDriver(Native lib, Host host) { this(lib, host, lib::effectRoutineSpawn); } EffectRoutineDriver(Native lib, Host host, LongSupplier spawn) { this.lib = lib; this.host = host; this.handle = spawn.getAsLong(); } @Override public CompletableFuture run(Scheduler scheduler) { scheduler.submit(() -> step(scheduler)); return done; } private void step(Scheduler scheduler) { var out = lib.effectRoutineResume(handle); if (out.status() < 0) { host.write(""); lib.effectRoutineFree(handle); done.completeExceptionally(new IllegalStateException(Native.describe(out.status()))); return; } Runnable next = () -> step(scheduler); long sleepMillis = -1; for (var effect : Codec.decode(out.bytes())) { switch (effect) { case Codec.Effect.Write w -> host.write(w.text()); case Codec.Effect.ReadLine r -> check(lib.effectRoutineReplyString(handle, host.readLine())); case Codec.Effect.Lookup l -> check(lib.effectRoutineReplyString(handle, Host.greetingFor(l.name()))); case Codec.Effect.Count c -> check(lib.effectRoutineReplyU32(handle, host.count())); case Codec.Effect.Sleep s -> { // Fulfil the reply when the timer fires, then resume. sleepMillis = s.millis(); next = () -> { check(lib.effectRoutineReplyUnit(handle)); step(scheduler); }; } } } switch (out.status()) { case Native.AWAITING -> { if (sleepMillis >= 0) { scheduler.after(sleepMillis, next); } else { scheduler.submit(next); } } case Native.COMPLETE -> { lib.effectRoutineFree(handle); done.complete(null); } default -> { host.write(""); lib.effectRoutineFree(handle); done.completeExceptionally(new IllegalStateException("stalled")); } } } private static void check(int code) { if (code < 0) { throw new IllegalStateException("reply failed: " + Native.describe(code)); } } }