architecture
Here's a high-level overview of how Learn::Raku::With works:
The
learn-rakufunction accepts user input and emits appropriate commands into the$server-cmdSupply.These commands are handled by the
main-serverfunction, which starts/stops a Cro server as needed and creates aframe-streamto produce new output. Eachframe-streamruns its own loop (basically the equivalent of a game loop, if you're familiar with game programming) by applying the user-suppliedmap-fnto the current Balls 60 times every second.Because the
frame-streamis applying user-written functions, it's possible for it to crash or produce invalid output. To handle this, we maintain a list of past known-good states and can kill/reset anyframe-streamthat gets itself into a bad state (this uses the "let it crash" pattern for building resilient concurrent systems, borrowed from Erlang ā and, incidentally, I'm really excited about how well Raku's concurrency system fits with Erlang techniques).Because the
frame-streamis generating frames at 60 fps, it's possible for it to generate frames faster than we can send them to the browser. Raku's Supplies deal with this by creating backpreasure ā that is, by slowing down the generation of new values. This is normally what you want, but for us it'd mean that any lag would result in the Balls moving slower. We'd rather drop frames, (even 30 fps is basically OK), so we use a Channel to accept frames and then discard any that the browser isn't ready for.Separately, Cro has accepted incoming connections for us and established a WebSocket connection to each client. Each connection requests frames from the
frame-stream, scales them based on info it gets from the browser about its current screen size, and then sends some JSON describing the Ball.The browser receives the JSON, parses it, and displays a ball every time the browser is ready to display another animation frame.