ludus/pkg/ludus.js

201 lines
4.9 KiB
JavaScript
Raw Permalink Normal View History

import {p5} from "./p5.js"
import {svg as svg_2} from "./svg.js"
2025-07-06 05:40:03 +00:00
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}
2025-06-26 02:56:39 +00:00
2025-07-01 20:30:17 +00:00
const worker_url = new URL("worker.js", import.meta.url)
const worker = new Worker(worker_url, {type: "module"})
2025-06-25 21:46:00 +00:00
2025-06-30 22:59:59 +00:00
let outbox = []
2025-07-01 18:35:36 +00:00
let ludus_console = ""
let ludus_commands = []
let ludus_result = null
let code = null
let running = false
let ready = false
2025-07-01 18:35:36 +00:00
let io_interval_id = null
2025-07-04 19:09:02 +00:00
let keys_down = new Set();
2025-07-01 18:35:36 +00:00
worker.onmessage = handle_messages
2025-06-25 21:46:00 +00:00
2025-07-01 22:52:03 +00:00
async function handle_messages (e) {
2025-06-30 22:59:59 +00:00
let msgs
try {
msgs = JSON.parse(e.data)
} catch {
console.log(e.data)
2025-07-01 16:54:11 +00:00
throw Error("Main: bad json from Ludus")
2025-06-30 22:59:59 +00:00
}
for (const msg of msgs) {
switch (msg.verb) {
2025-07-01 20:59:42 +00:00
case "Complete": {
2025-07-01 16:54:11 +00:00
console.log("Main: ludus completed with => ", msg.data)
2025-07-01 18:35:36 +00:00
ludus_result = msg.data
2025-06-30 22:59:59 +00:00
running = false
2025-07-02 20:05:06 +00:00
ready = false
outbox = []
2025-06-30 22:59:59 +00:00
break
}
2025-07-04 00:22:11 +00:00
case "Error": {
console.log("Main: ludus errored with => ", msg.data)
ludus_result = msg.data
2025-07-04 00:22:11 +00:00
running = false
ready = false
outbox = []
break
}
2025-07-01 18:35:36 +00:00
// TODO: do more than report these
2025-07-01 20:59:42 +00:00
case "Console": {
2025-07-04 00:22:11 +00:00
let new_lines = msg.data.join("\n");
ludus_console = ludus_console + new_lines
2025-07-04 19:57:16 +00:00
console.log("Main: ludus says => ", new_lines)
2025-07-01 18:35:36 +00:00
break
}
2025-07-01 20:59:42 +00:00
case "Commands": {
2025-07-01 18:35:36 +00:00
console.log("Main: ludus commands => ", msg.data)
2025-07-02 00:07:02 +00:00
for (const command of msg.data) {
// commands will arrive asynchronously; ensure correct ordering
2025-07-02 00:07:02 +00:00
ludus_commands[command[1]] = command
2025-07-01 18:35:36 +00:00
}
2025-06-30 22:59:59 +00:00
break
}
2025-07-01 22:52:03 +00:00
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
}
2025-06-30 22:59:59 +00:00
}
}
}
2025-07-01 18:35:36 +00:00
function io_poller () {
2025-06-30 22:59:59 +00:00
if (io_interval_id && !running) {
2025-07-01 16:54:11 +00:00
// flush the outbox one last time
// (presumably, with the kill message)
2025-07-01 16:54:11 +00:00
worker.postMessage(outbox)
// cancel the poller
2025-06-30 22:59:59 +00:00
clearInterval(io_interval_id)
outbox = []
}
if (ready && running) {
2025-07-06 22:43:35 +00:00
bundle_keys()
2025-07-01 16:54:11 +00:00
worker.postMessage(outbox)
outbox = []
2025-06-30 22:59:59 +00:00
}
}
2025-07-06 03:33:39 +00:00
function bundle_keys () {
2025-07-06 05:40:03 +00:00
outbox.push({verb: "Keys", data: Array.from(keys_down)})
2025-07-06 03:33:39 +00:00
}
2025-07-01 18:35:36 +00:00
function start_io_polling () {
2025-07-04 21:24:54 +00:00
io_interval_id = setInterval(io_poller, 100)
2025-06-30 22:59:59 +00:00
}
2025-06-25 21:46:00 +00:00
2025-07-01 18:35:36 +00:00
// runs a ludus script; does not return the result
// the result must be explicitly polled with `result`
2025-06-26 02:56:39 +00:00
export function run (source) {
2025-07-02 20:05:06 +00:00
if (running || ready) {
2025-07-04 21:24:54 +00:00
console.log("Main: received bouncy `run` call");
return "TODO: handle this? should not be running"
}
2025-07-04 21:24:54 +00:00
// start the vm
2025-07-06 03:33:39 +00:00
// wrapping the Run message in an array for the worker
worker.postMessage([{verb: "Run", data: source}])
2025-07-06 03:33:39 +00:00
// update state for this run
code = source
running = true
// reset the rest of my state
2025-07-04 21:24:54 +00:00
outbox = []
ludus_console = ""
ludus_commands = []
ludus_result = null
ready = false
keys_down = new Set();
2025-07-06 03:33:39 +00:00
// start the polling loop
2025-07-01 18:35:36 +00:00
start_io_polling()
}
2025-07-04 19:29:44 +00:00
export function is_starting_up() {
return running && !ready
}
2025-07-01 18:35:36 +00:00
// tells if the ludus script is still running
export function is_running() {
return running && ready
2025-06-30 22:59:59 +00:00
}
2025-07-01 18:35:36 +00:00
// kills a ludus script
2025-06-30 22:59:59 +00:00
export function kill () {
running = false
2025-07-01 20:59:42 +00:00
outbox.push({verb: "Kill"})
console.log("Main: Killed Ludus")
2025-06-30 22:59:59 +00:00
}
2025-07-01 18:35:36 +00:00
// sends text into ludus (status: not working)
2025-06-30 22:59:59 +00:00
export function input (text) {
2025-07-02 20:20:22 +00:00
console.log("Main: calling `input` with ", text)
2025-07-01 20:59:42 +00:00
outbox.push({verb: "Input", data: text})
2025-06-25 21:46:00 +00:00
}
2025-07-01 18:35:36 +00:00
// returns the contents of the ludus console and resets the console
export function flush_stdout () {
let out = ludus_console
ludus_console = ""
2025-07-04 19:37:39 +00:00
return out
2025-07-01 18:35:36 +00:00
}
// 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
2025-06-25 21:46:00 +00:00
}
2025-07-01 18:35:36 +00:00
// returns the array of turtle commands and clears it
export function flush_commands () {
let out = ludus_commands
ludus_commands = []
2025-07-04 19:37:39 +00:00
return out
2025-06-25 21:46:00 +00:00
}
2025-07-01 18:35:36 +00:00
// returns the ludus result
// this is effectively Option<String>:
// null if no result has been returned, or
// a string representation of the result
2025-06-26 02:56:39 +00:00
export function result () {
2025-07-01 18:35:36 +00:00
return ludus_result
2025-06-25 21:46:00 +00:00
}
2025-07-04 19:37:39 +00:00
export function flush_result () {
let out = ludus_result
ludus_result = null
return out
}
2025-07-06 05:40:03 +00:00
export function key_down (key) {
if (is_running()) keys_down.add(get_code(key))
2025-07-04 19:09:02 +00:00
}
2025-07-06 05:40:03 +00:00
export function key_up (key) {
if (is_running()) keys_down.delete(get_code(key))
2025-07-04 19:09:02 +00:00
}
export {p5} from "./p5.js"
2025-06-25 21:46:00 +00:00
2025-06-26 02:56:39 +00:00
export function svg (commands) {
console.log("generating svg for ${code}")
return svg_2(commands, code)
}