201 lines
4.9 KiB
JavaScript
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)
|
|
}
|
|
|