ludus/pkg/ludus.js
2025-07-06 18:43:35 -04:00

201 lines
4.9 KiB
JavaScript

import {p5} from "./p5.js"
import {svg as svg_2} from "./svg.js"
import {get_code} from "./keys.js"
if (window) window.ludus = {run, kill, flush_stdout, stdout, p5, flush_commands, commands, result, flush_result, input, is_running, key_down, key_up, is_starting_up, p5, svg}
const worker_url = new URL("worker.js", import.meta.url)
const worker = new Worker(worker_url, {type: "module"})
let outbox = []
let ludus_console = ""
let ludus_commands = []
let ludus_result = null
let code = null
let running = false
let ready = false
let io_interval_id = null
let keys_down = new Set();
worker.onmessage = handle_messages
async function handle_messages (e) {
let msgs
try {
msgs = JSON.parse(e.data)
} catch {
console.log(e.data)
throw Error("Main: bad json from Ludus")
}
for (const msg of msgs) {
switch (msg.verb) {
case "Complete": {
console.log("Main: ludus completed with => ", msg.data)
ludus_result = msg.data
running = false
ready = false
outbox = []
break
}
case "Error": {
console.log("Main: ludus errored with => ", msg.data)
ludus_result = msg.data
running = false
ready = false
outbox = []
break
}
// TODO: do more than report these
case "Console": {
let new_lines = msg.data.join("\n");
ludus_console = ludus_console + new_lines
console.log("Main: ludus says => ", new_lines)
break
}
case "Commands": {
console.log("Main: ludus commands => ", msg.data)
for (const command of msg.data) {
// commands will arrive asynchronously; ensure correct ordering
ludus_commands[command[1]] = command
}
break
}
case "Fetch": {
console.log("Main: ludus requests => ", msg.data)
const res = await fetch(msg.data, {mode: "cors"})
const text = await res.text()
console.log("Main: js responds => ", text)
outbox.push({verb: "Fetch", data: [msg.data, res.status, text]})
}
case "Ready": {
console.log("Main: ludus is ready")
ready = true
}
}
}
}
function io_poller () {
if (io_interval_id && !running) {
// flush the outbox one last time
// (presumably, with the kill message)
worker.postMessage(outbox)
// cancel the poller
clearInterval(io_interval_id)
outbox = []
}
if (ready && running) {
bundle_keys()
worker.postMessage(outbox)
outbox = []
}
}
function bundle_keys () {
outbox.push({verb: "Keys", data: Array.from(keys_down)})
}
function start_io_polling () {
io_interval_id = setInterval(io_poller, 100)
}
// runs a ludus script; does not return the result
// the result must be explicitly polled with `result`
export function run (source) {
if (running || ready) {
console.log("Main: received bouncy `run` call");
return "TODO: handle this? should not be running"
}
// start the vm
// wrapping the Run message in an array for the worker
worker.postMessage([{verb: "Run", data: source}])
// update state for this run
code = source
running = true
// reset the rest of my state
outbox = []
ludus_console = ""
ludus_commands = []
ludus_result = null
ready = false
keys_down = new Set();
// start the polling loop
start_io_polling()
}
export function is_starting_up() {
return running && !ready
}
// tells if the ludus script is still running
export function is_running() {
return running && ready
}
// kills a ludus script
export function kill () {
running = false
outbox.push({verb: "Kill"})
console.log("Main: Killed Ludus")
}
// sends text into ludus (status: not working)
export function input (text) {
console.log("Main: calling `input` with ", text)
outbox.push({verb: "Input", data: text})
}
// returns the contents of the ludus console and resets the console
export function flush_stdout () {
let out = ludus_console
ludus_console = ""
return out
}
// returns the contents of the ludus console, retaining them
export function stdout () {
return ludus_console
}
// returns the array of turtle commands
export function commands () {
return ludus_commands
}
// returns the array of turtle commands and clears it
export function flush_commands () {
let out = ludus_commands
ludus_commands = []
return out
}
// returns the ludus result
// this is effectively Option<String>:
// null if no result has been returned, or
// a string representation of the result
export function result () {
return ludus_result
}
export function flush_result () {
let out = ludus_result
ludus_result = null
return out
}
export function key_down (key) {
if (is_running()) keys_down.add(get_code(key))
}
export function key_up (key) {
if (is_running()) keys_down.delete(get_code(key))
}
export {p5} from "./p5.js"
export function svg (commands) {
console.log("generating svg for ${code}")
return svg_2(commands, code)
}