diff --git a/assets/test_prelude.ld b/assets/test_prelude.ld index 4b8d707..adba258 100644 --- a/assets/test_prelude.ld +++ b/assets/test_prelude.ld @@ -1034,7 +1034,7 @@ box turtle_state = turtle_init fn apply_command fn add_command! (command) -> { - update! (turtle_commands, append (_, command)) + update! (turtle_commands, append (_, (:turtle_0, command))) let prev = unbox (turtle_state) let curr = apply_command (prev, command) store! (turtle_state, curr) diff --git a/may_2025_thoughts.md b/may_2025_thoughts.md index 77fdf25..4445ca5 100644 --- a/may_2025_thoughts.md +++ b/may_2025_thoughts.md @@ -1182,7 +1182,7 @@ Happy Canada day! After a really rough evening, I seem to have the actor model not only working in Ludus, but reasonably debugged in Rust. We've got one bug to address in Firefox before I continue: -* [ ] the event loop isn't returning once something is done, which makes no sense +* [x] the event loop isn't returning once something is done, which makes no sense - What seems to be happening is that the javascript behaviour is subtly different - Current situation is that synchronous scripts work just fine - But async scripts work ONCE, and then not again @@ -1192,7 +1192,7 @@ We've got one bug to address in Firefox before I continue: After that: * [ ] implement other verbs beside `console`: - - [ ] `command` + - [x] `command` - [ ] `input` * [ ] js->rust->ludus buffer (in Rust code) * [ ] ludus abstractions around this buffer (in Ludus code) diff --git a/pkg/ludus.js b/pkg/ludus.js index cf4ef2f..e1efdc9 100644 --- a/pkg/ludus.js +++ b/pkg/ludus.js @@ -1,10 +1,18 @@ -if (window) window.ludus = {run, kill, flush_console, p5, svg, turtle_commands, result, input} +if (window) window.ludus = {run, kill, flush_stdout, stdout, p5, svg, flush_commands, commands, result, input, is_running} const worker = new Worker("worker.js", {type: "module"}) let outbox = [] +let ludus_console = "" +let ludus_commands = [] +let ludus_result = null +let code = null +let running = false +let io_interval_id = null -worker.onmessage = async (e) => { +worker.onmessage = handle_messages + +function handle_messages (e) { let msgs try { msgs = JSON.parse(e.data) @@ -16,24 +24,28 @@ worker.onmessage = async (e) => { switch (msg.verb) { case "complete": { console.log("Main: ludus completed with => ", msg.data) - res = msg.data + ludus_result = msg.data running = false break } + // TODO: do more than report these case "console": { console.log("Main: ludus says => ", msg.data) + ludus_console = ludus_console + msg.data + break + } + case "commands": { + console.log("Main: ludus commands => ", msg.data) + for (const command of msg.data) { + ludus_commands.push(command) + } break } } } } -let res = null -let code = null -let running = false -let io_interval_id = null - -const io_poller = () => { +function io_poller () { if (io_interval_id && !running) { // flush the outbox one last time worker.postMessage(outbox) @@ -45,41 +57,70 @@ const io_poller = () => { } } -function poll_io () { +function start_io_polling () { io_interval_id = setInterval(io_poller, 10) } +// runs a ludus script; does not return the result +// the result must be explicitly polled with `result` export function run (source) { if (running) "TODO: handle this? should not be running" running = true code = source - outbox.push({verb: "run", data: source}) - poll_io() + outbox = [{verb: "run", data: source}] + start_io_polling() } +// tells if the ludus script is still running +export function is_running() { + return running +} + +// kills a ludus script export function kill () { running = false outbox.push({verb: "kill"}) } +// sends text into ludus (status: not working) export function input (text) { outbox.push({verb: "input", data: text}) } -export function flush_console () { - if (!res) return "" - return res.io.stdout.data +// returns the contents of the ludus console and resets the console +export function flush_stdout () { + let out = ludus_console + ludus_console = "" + out } -export function turtle_commands () { - if (!res) return [] - return res.io.turtle.data +// 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 = [] + out +} + +// returns the ludus result +// this is effectively Option: +// null if no result has been returned, or +// a string representation of the result export function result () { - return res + return ludus_result } +//////////// turtle plumbing below +// TODO: refactor this out into modules const turtle_init = { position: [0, 0], heading: 0, @@ -134,8 +175,9 @@ function unit_of (heading) { return [Math.cos(radians), Math.sin(radians)] } -function command_to_state (prev_state, curr_command) { - const verb = curr_command[0] +function command_to_state (prev_state, command) { + const [_target, curr_command] = command + const [verb] = curr_command switch (verb) { case "goto": { const [_, x, y] = curr_command @@ -352,7 +394,7 @@ export function svg (commands) { return accum }, {maxX: 0, maxY: 0, minX: 0, minY: 0}) - const [r, g, b, a] = resolve_color(background_color) + const [r, g, b] = resolve_color(background_color) if ((r+g+b)/3 > 128) turtle_color = [0, 0, 0, 150] const view_width = (maxX - minX) * 1.2 const view_height = (maxY - minY) * 1.2 diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index 0e42a14..2c9353f 100644 --- a/pkg/rudus.d.ts +++ b/pkg/rudus.d.ts @@ -14,8 +14,8 @@ export interface InitOutput { readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_export_6: WebAssembly.Table; - readonly closure343_externref_shim: (a: number, b: number, c: any) => void; - readonly closure367_externref_shim: (a: number, b: number, c: any, d: any) => void; + readonly closure323_externref_shim: (a: number, b: number, c: any) => void; + readonly closure336_externref_shim: (a: number, b: number, c: any, d: any) => void; readonly __wbindgen_start: () => void; } diff --git a/pkg/rudus.js b/pkg/rudus.js index 2af7198..e89f8ba 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -17,22 +17,6 @@ function handleError(f, args) { } } -function logError(f, args) { - try { - return f.apply(this, args); - } catch (e) { - let error = (function () { - try { - return e instanceof Error ? `${e.message}\n\nStack:\n${e.stack}` : e.toString(); - } catch(_) { - return ""; - } - }()); - console.error("wasm-bindgen: imported JS function that was not marked as `catch` threw an error:", error); - throw e; - } -} - const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; @@ -70,8 +54,6 @@ const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' function passStringToWasm0(arg, malloc, realloc) { - if (typeof(arg) !== 'string') throw new Error(`expected a string argument, found ${typeof(arg)}`); - if (realloc === undefined) { const buf = cachedTextEncoder.encode(arg); const ptr = malloc(buf.length, 1) >>> 0; @@ -100,7 +82,7 @@ function passStringToWasm0(arg, malloc, realloc) { ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); const ret = encodeString(arg, view); - if (ret.read !== arg.length) throw new Error('failed to pass whole string'); + offset += ret.written; ptr = realloc(ptr, len, offset, 1) >>> 0; } @@ -122,12 +104,6 @@ function isLikeNone(x) { return x === undefined || x === null; } -function _assertBoolean(n) { - if (typeof(n) !== 'boolean') { - throw new Error(`expected a boolean argument, found ${typeof(n)}`); - } -} - const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined') ? { register: () => {}, unregister: () => {} } : new FinalizationRegistry(state => { @@ -158,71 +134,6 @@ function makeMutClosure(arg0, arg1, dtor, f) { CLOSURE_DTORS.register(real, state, state); return real; } - -function debugString(val) { - // primitive types - const type = typeof val; - if (type == 'number' || type == 'boolean' || val == null) { - return `${val}`; - } - if (type == 'string') { - return `"${val}"`; - } - if (type == 'symbol') { - const description = val.description; - if (description == null) { - return 'Symbol'; - } else { - return `Symbol(${description})`; - } - } - if (type == 'function') { - const name = val.name; - if (typeof name == 'string' && name.length > 0) { - return `Function(${name})`; - } else { - return 'Function'; - } - } - // objects - if (Array.isArray(val)) { - const length = val.length; - let debug = '['; - if (length > 0) { - debug += debugString(val[0]); - } - for(let i = 1; i < length; i++) { - debug += ', ' + debugString(val[i]); - } - debug += ']'; - return debug; - } - // Test for built-in - const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val)); - let className; - if (builtInMatches && builtInMatches.length > 1) { - className = builtInMatches[1]; - } else { - // Failed to match the standard '[object ClassName]' - return toString.call(val); - } - if (className == 'Object') { - // we're a user defined class or Object - // JSON.stringify avoids problems with cycles, and is generally much - // easier than looping through ownProperties of `val`. - try { - return 'Object(' + JSON.stringify(val) + ')'; - } catch (_) { - return 'Object'; - } - } - // errors - if (val instanceof Error) { - return `${val.name}: ${val.message}\n${val.stack}`; - } - // TODO we could test for more things here, like `Set`s and `Map`s. - return className; -} /** * @param {string} src * @returns {Promise} @@ -234,19 +145,12 @@ export function ludus(src) { return ret; } -function _assertNum(n) { - if (typeof(n) !== 'number') throw new Error(`expected a number argument, found ${typeof(n)}`); -} -function __wbg_adapter_22(arg0, arg1, arg2) { - _assertNum(arg0); - _assertNum(arg1); - wasm.closure343_externref_shim(arg0, arg1, arg2); +function __wbg_adapter_20(arg0, arg1, arg2) { + wasm.closure323_externref_shim(arg0, arg1, arg2); } -function __wbg_adapter_52(arg0, arg1, arg2, arg3) { - _assertNum(arg0); - _assertNum(arg1); - wasm.closure367_externref_shim(arg0, arg1, arg2, arg3); +function __wbg_adapter_48(arg0, arg1, arg2, arg3) { + wasm.closure336_externref_shim(arg0, arg1, arg2, arg3); } async function __wbg_load(module, imports) { @@ -291,7 +195,7 @@ function __wbg_get_imports() { const ret = arg0.call(arg1, arg2); return ret; }, arguments) }; - imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function() { return logError(function (arg0, arg1) { + imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function(arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -301,8 +205,8 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }, arguments) }; - imports.wbg.__wbg_io_4b41f8089de924df = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_io_5a3c8ea72d8c6ea3 = function() { return handleError(function (arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -314,7 +218,7 @@ function __wbg_get_imports() { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } }, arguments) }; - imports.wbg.__wbg_log_86d603e98cc11395 = function() { return logError(function (arg0, arg1) { + imports.wbg.__wbg_log_86d603e98cc11395 = function(arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -324,11 +228,8 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }, arguments) }; - imports.wbg.__wbg_log_9e426f8e841e42d3 = function() { return logError(function (arg0, arg1) { - console.log(getStringFromWasm0(arg0, arg1)); - }, arguments) }; - imports.wbg.__wbg_log_edeb598b620f1ba2 = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_log_edeb598b620f1ba2 = function(arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -338,15 +239,15 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }, arguments) }; - imports.wbg.__wbg_new_23a2665fac83c611 = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_new_23a2665fac83c611 = function(arg0, arg1) { try { var state0 = {a: arg0, b: arg1}; var cb0 = (arg0, arg1) => { const a = state0.a; state0.a = 0; try { - return __wbg_adapter_52(a, state0.b, arg0, arg1); + return __wbg_adapter_48(a, state0.b, arg0, arg1); } finally { state0.a = a; } @@ -356,65 +257,65 @@ function __wbg_get_imports() { } finally { state0.a = state0.b = 0; } - }, arguments) }; - imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { return logError(function () { + }; + imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { const ret = new Error(); return ret; - }, arguments) }; - imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function(arg0, arg1) { const ret = new Function(getStringFromWasm0(arg0, arg1)); return ret; - }, arguments) }; - imports.wbg.__wbg_now_8dddb61fa4928554 = function() { return logError(function () { + }; + imports.wbg.__wbg_now_8dddb61fa4928554 = function() { const ret = Date.now(); return ret; - }, arguments) }; - imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function(arg0) { queueMicrotask(arg0); - }, arguments) }; - imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function(arg0) { const ret = arg0.queueMicrotask; return ret; - }, arguments) }; - imports.wbg.__wbg_random_57c118f142535bb6 = function() { return logError(function () { + }; + imports.wbg.__wbg_random_57c118f142535bb6 = function() { const ret = Math.random(); return ret; - }, arguments) }; - imports.wbg.__wbg_resolve_4851785c9c5f573d = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_resolve_4851785c9c5f573d = function(arg0) { const ret = Promise.resolve(arg0); return ret; - }, arguments) }; - imports.wbg.__wbg_stack_0ed75d68575b0f3c = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_stack_0ed75d68575b0f3c = function(arg0, arg1) { const ret = arg1.stack; const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); const len1 = WASM_VECTOR_LEN; getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); - }, arguments) }; - imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { const ret = typeof global === 'undefined' ? null : global; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { const ret = typeof globalThis === 'undefined' ? null : globalThis; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { const ret = typeof self === 'undefined' ? null : self; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { const ret = typeof window === 'undefined' ? null : window; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_then_44b73946d2fb3e7d = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_then_44b73946d2fb3e7d = function(arg0, arg1) { const ret = arg0.then(arg1); return ret; - }, arguments) }; - imports.wbg.__wbg_then_48b406749878a531 = function() { return logError(function (arg0, arg1, arg2) { + }; + imports.wbg.__wbg_then_48b406749878a531 = function(arg0, arg1, arg2) { const ret = arg0.then(arg1, arg2); return ret; - }, arguments) }; + }; imports.wbg.__wbindgen_cb_drop = function(arg0) { const obj = arg0.original; if (obj.cnt-- == 1) { @@ -422,19 +323,11 @@ function __wbg_get_imports() { return true; } const ret = false; - _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper7649 = function() { return logError(function (arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 344, __wbg_adapter_22); + imports.wbg.__wbindgen_closure_wrapper969 = function(arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 324, __wbg_adapter_20); return ret; - }, arguments) }; - imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { - const ret = debugString(arg1); - const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); - const len1 = WASM_VECTOR_LEN; - getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); - getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); }; imports.wbg.__wbindgen_init_externref_table = function() { const table = wasm.__wbindgen_export_2; @@ -448,12 +341,10 @@ function __wbg_get_imports() { }; imports.wbg.__wbindgen_is_function = function(arg0) { const ret = typeof(arg0) === 'function'; - _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_is_undefined = function(arg0) { const ret = arg0 === undefined; - _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_string_get = function(arg0, arg1) { diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index 82a74b6..d9ca655 100644 Binary files a/pkg/rudus_bg.wasm and b/pkg/rudus_bg.wasm differ diff --git a/pkg/rudus_bg.wasm.d.ts b/pkg/rudus_bg.wasm.d.ts index 9c99a54..99af8eb 100644 --- a/pkg/rudus_bg.wasm.d.ts +++ b/pkg/rudus_bg.wasm.d.ts @@ -9,6 +9,6 @@ export const __wbindgen_free: (a: number, b: number, c: number) => void; export const __wbindgen_malloc: (a: number, b: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_export_6: WebAssembly.Table; -export const closure343_externref_shim: (a: number, b: number, c: any) => void; -export const closure367_externref_shim: (a: number, b: number, c: any, d: any) => void; +export const closure323_externref_shim: (a: number, b: number, c: any) => void; +export const closure336_externref_shim: (a: number, b: number, c: any, d: any) => void; export const __wbindgen_start: () => void; diff --git a/src/world.rs b/src/world.rs index e1c5276..cbbf094 100644 --- a/src/world.rs +++ b/src/world.rs @@ -253,7 +253,7 @@ impl Zoo { #[derive(Debug, Clone, PartialEq)] pub struct Buffers { console: Value, - // commands: Value, + commands: Value, // fetch_outbox: Value, // fetch_inbox: Value, input: Value, @@ -263,7 +263,7 @@ impl Buffers { pub fn new (prelude: imbl::HashMap<&'static str, Value>) -> Buffers { Buffers { console: prelude.get("console").unwrap().clone(), - // commands: prelude.get("commands").unwrap().clone(), + commands: prelude.get("turtle_commands").unwrap().clone(), // fetch_outbox: prelude.get("fetch_outbox").unwrap().clone(), // fetch_inbox: prelude.get("fetch_inbox").unwrap().clone(), input: prelude.get("input").unwrap().clone(), @@ -278,9 +278,10 @@ impl Buffers { self.input.as_box() } - // pub fn commands (&self) -> Rc> { - // self.commands.as_box() - // } + pub fn commands (&self) -> Rc> { + self.commands.as_box() + } + // pub fn fetch_outbox (&self) -> Rc> { // self.fetch_outbox.as_box() // } @@ -365,6 +366,9 @@ impl World { if let Some(console) = self.flush_console() { outbox.push(console); } + if let Some(commands) = self.flush_commands() { + outbox.push(commands); + } outbox } @@ -377,6 +381,18 @@ impl World { Some(MsgOut::Console(working_value.clone())) } + fn flush_commands(&self) -> Option { + let commands = self.buffers.commands(); + let working_copy = RefCell::new(Value::new_list()); + commands.swap(&working_copy); + let commands = working_copy.borrow(); + if commands.as_list().is_empty() { + None + } else { + Some(MsgOut::Commands(commands.clone())) + } + } + fn complete_main(&mut self) -> Vec { let mut outbox = self.flush_buffers(); // TODO: if we have a panic, actually add the panic message to the console