From efd98460c51b1be7cd9af4d77a508918ad67fb83 Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Thu, 22 May 2025 18:58:13 -0500 Subject: [PATCH] -u is no longer available as a global, only within the running actor code; enable passing args to use --- CLAUDE.md | 16 ++++----- examples/chess/main.js | 18 +++++------ scripts/core/engine.js | 66 +++++++++++++++++++++++--------------- scripts/core/io.js | 2 +- scripts/modules/moth.js | 13 +++++++- tests/comments.js | 6 ++-- tests/contact.js | 2 +- tests/overling.js | 2 +- tests/parseq.js | 2 +- tests/portal.js | 6 ++-- tests/qr_drag.js | 2 +- tests/reply.js | 2 +- tests/send.js | 2 +- tests/test_actor_access.js | 18 +++++++++++ tests/test_args.js | 18 +++++++++++ tests/test_child_actor.js | 8 +++++ tests/test_module.js | 15 +++++++++ tests/test_use_args.js | 18 +++++++++++ tests/window.js | 2 +- 19 files changed, 161 insertions(+), 57 deletions(-) create mode 100644 tests/test_actor_access.js create mode 100644 tests/test_args.js create mode 100644 tests/test_child_actor.js create mode 100644 tests/test_module.js create mode 100644 tests/test_use_args.js diff --git a/CLAUDE.md b/CLAUDE.md index fc9d1345..dc33f24c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -43,7 +43,7 @@ Prosperon is an actor-based game engine inspired by Douglas Crockford's Misty sy ### Core Systems 1. **Actor System** (scripts/core/engine.js) - - Message passing via `$_.send()`, `$_.receive()` + - Message passing via `send()`, `$_.receive()` - Actor spawning/management - Register-based component system (update, draw, gui, etc.) @@ -267,12 +267,12 @@ Portals must reply with an actor object, not application data: ```javascript // CORRECT: Portal replies with actor $_.portal(e => { - $_.send(e, $_); // Reply with server actor + send(e, $_); // Reply with server actor }, 5678); // WRONG: Portal sends application data $_.portal(e => { - $_.send(e, {type: 'game_start'}); // This breaks the pattern + send(e, {type: 'game_start'}); // This breaks the pattern }, 5678); ``` @@ -297,7 +297,7 @@ Messages contain `__HEADER__` information that can cause issues: $_.receiver(e => { if (e.type === 'join_game') { var opponent = e.__HEADER__.replycc; // Clean actor reference - $_.send(opponent, {type: 'game_start'}); + send(opponent, {type: 'game_start'}); } }); @@ -323,7 +323,7 @@ Actor objects must be completely opaque black boxes that work identically regard // - Network communication (uses ENet) // The actor shouldn't know or care about the transport mechanism -$_.send(opponent, {type: 'move', from: [0,0], to: [1,1]}); +send(opponent, {type: 'move', from: [0,0], to: [1,1]}); ``` **Key Implementation Details:** @@ -343,14 +343,14 @@ $_.send(opponent, {type: 'move', from: [0,0], to: [1,1]}); ```javascript // Server: Portal setup $_.portal(e => { - $_.send(e, $_); // Just reply with actor + send(e, $_); // Just reply with actor }, 5678); // Client: Two-phase connection $_.contact((actor, reason) => { if (actor) { opponent = actor; - $_.send(opponent, {type: 'join_game'}); // Phase 2: app messaging + send(opponent, {type: 'join_game'}); // Phase 2: app messaging } }, {address: "localhost", port: 5678}); @@ -358,7 +358,7 @@ $_.contact((actor, reason) => { $_.receiver(e => { if (e.type === 'join_game') { opponent = e.__HEADER__.replycc; - $_.send(opponent, {type: 'game_start', your_color: 'black'}); + send(opponent, {type: 'game_start', your_color: 'black'}); } }); ``` diff --git a/examples/chess/main.js b/examples/chess/main.js index 72038ab5..3c0761bd 100644 --- a/examples/chess/main.js +++ b/examples/chess/main.js @@ -1,6 +1,6 @@ /* main.js – runs the demo with your prototype-based grid */ -var moth = use('moth') +var moth = use('moth', $_.delay) var json = use('json') var res = 160 @@ -88,7 +88,7 @@ prosperon.on('mouse_button_down', function(e) { holdingPiece = true; // Send pickup notification to opponent if (opponent) { - $_.send(opponent, { + send(opponent, { type: 'piece_pickup', pos: c }); @@ -120,7 +120,7 @@ prosperon.on('mouse_button_up', function(e) { console.log("Made move from", selectPos, "to", c); // Send move to opponent console.log("Sending move to opponent:", opponent); - $_.send(opponent, { + send(opponent, { type: 'move', from: selectPos, to: c @@ -135,7 +135,7 @@ prosperon.on('mouse_button_up', function(e) { // Send piece drop notification to opponent if (opponent) { - $_.send(opponent, { + send(opponent, { type: 'piece_drop' }); } @@ -155,7 +155,7 @@ prosperon.on('mouse_motion', function(e) { // Send mouse position to opponent in real-time if (opponent && gameState === 'connected') { - $_.send(opponent, { + send(opponent, { type: 'mouse_move', pos: c, holding: holdingPiece, @@ -292,7 +292,7 @@ function startServer() { console.log("Portal received contact message"); // Reply with this actor to establish connection console.log (json.encode($_)) - $_.send(e, $_); + send(e, $_); console.log("Portal replied with server actor"); }, 5678); } @@ -308,7 +308,7 @@ function joinServer() { console.log("Connection established with server, sending join request"); // Send a greet message with our actor object - $_.send(opponent, { + send(opponent, { type: 'greet', client_actor: $_ }); @@ -335,7 +335,7 @@ var ioguy = { } }; -$_.send(ioguy, { +send(ioguy, { type: "subscribe", actor: $_ }); @@ -355,7 +355,7 @@ $_.receiver(e => { // Send game_start to the client console.log("Sending game_start to client"); - $_.send(opponent, { + send(opponent, { type: 'game_start', your_color: 'black' }); diff --git a/scripts/core/engine.js b/scripts/core/engine.js index e5b2b880..32a2d332 100644 --- a/scripts/core/engine.js +++ b/scripts/core/engine.js @@ -91,7 +91,7 @@ prosperon.PATH = [ ] // path is the path of a module or script to resolve -var script_fn = function script_fn(path) { +var script_fn = function script_fn(path, args) { var parsed = {} var file = resources.find_script(path) @@ -108,16 +108,19 @@ var script_fn = function script_fn(path) { parsed.module_ret ??= {} if (parsed.module) { - var mod_script = `(function setup_${module_name}_module(){ var self = this; var $ = this; var exports = {}; var module = {exports: exports}; var define = undefined; ${parsed.module}})` + // Create a context object with args + var context = Object.create(parsed.module_ret) + context.__args__ = args || [] + var mod_script = `(function setup_${module_name}_module(){ var self = this; var $ = this; var exports = {}; var module = {exports: exports}; var define = undefined; var arg = this.__args__; ${parsed.module}})` var module_fn = js.eval(file, mod_script) - parsed.module_ret = module_fn.call(parsed.module_ret) + parsed.module_ret = module_fn.call(context) if (parsed.module_ret === undefined || parsed.module_ret === null) throw new Error(`Module ${module_name} must return a value`) parsed.module_fn = module_fn } parsed.program ??= "" - var prog_script = `(function use_${module_name}() { var self = this; var $ = this.__proto__; ${parsed.program}})` + var prog_script = `(function use_${module_name}($_) { var self = this; var $ = this.__proto__; ${parsed.program}})` parsed.prog_fn = js.eval(file, prog_script) return parsed @@ -297,7 +300,7 @@ var use_cache = {} var inProgress = {} var loadingStack = [] -globalThis.use = function use(file) { +globalThis.use = function use(file, ...args) { // If we've already begun loading this file in this chain, show the cycle if (loadingStack.includes(file)) { // Find where in the stack this file first appeared @@ -328,8 +331,8 @@ globalThis.use = function use(file) { // Push onto loading stack for chain tracking loadingStack.push(file) - // Actually load the module - var mod = script_fn(file) + // Actually load the module with arguments + var mod = script_fn(file, args) // Done loading, remove from the chain and mark as loaded loadingStack.pop() @@ -468,7 +471,7 @@ function cant_kill() { actor.toString = function() { return this[FILE] } -actor.spawn = function spawn(script, config) { +actor.spawn = function spawn(script, config, actor_context) { if (this[DEAD]) throw new Error("Attempting to spawn on a dead actor") var prog if (!script) { @@ -488,6 +491,15 @@ actor.spawn = function spawn(script, config) { underling[TIMERS] = [] underling[UNDERLINGS] = new Set() + // Make $_ available to the actor (either passed context or the engine's $_) + var actor_dollar = actor_context || $_ + Object.defineProperty(underling, '$_', { + value: actor_dollar, + writable:false, + enumerable:false, + configurable:false + }) + Object.defineProperty(underling, 'overling', { get() { return this[OVERLING] }, enumerable:true, @@ -501,7 +513,9 @@ actor.spawn = function spawn(script, config) { }) Object.defineProperty(underling, 'spawn', { - value: actor.spawn, + value: function(script, config) { + return actor.spawn.call(this, script, config, actor_dollar) + }, writable:false, enumerable:true, configurable:false @@ -522,7 +536,8 @@ actor.spawn = function spawn(script, config) { }) try { - prog.prog_fn.call(underling) + // Pass $_ as a parameter to actor scripts + prog.prog_fn.call(underling, actor_dollar) } catch(e) { throw e; } if (underling[DEAD]) return undefined; @@ -542,8 +557,6 @@ actor.spawn = function spawn(script, config) { return underling } - - actor.clear = function actor_clear() { this[UNDERLINGS].forEach(p => { p.kill() @@ -577,8 +590,6 @@ actor.delay = function(fn, seconds) { } actor.delay.doc = `Call 'fn' after 'seconds' with 'this' set to the actor.` - - var act = use('actor') actor[UNDERLINGS] = new Set() @@ -639,12 +650,12 @@ var underlings = new Set() var overling = undefined var root = undefined -globalThis.$_ = $_ +// Don't make $_ global - it should only be available to actor scripts var receive_fn = undefined var greeters = {} -$_.is_actor = function(actor) { +function is_actor(actor) { return actor.__ACTORDATA__ } @@ -748,7 +759,7 @@ function handle_host(e) { var contactor = undefined $_.contact = function(callback, record) { - $_.send(create_actor(record), record, callback) + send(create_actor(record), record, callback) } $_.contact[prosperon.DOC] = `The contact function sends a message to a portal on another machine to obtain an actor object. @@ -779,7 +790,7 @@ $_.stop = function(actor) { destroyself() return } - if (!$_.is_actor(actor)) + if (!is_actor(actor)) throw new Error('Can only call stop on an actor.') if (!underlings.has(actor.__ACTORDATA__.id)) throw new Error('Can only call stop on an underling or self.') @@ -811,7 +822,7 @@ function actor_prep(actor, send) { } function actor_send(actor, message) { - if (!$_.is_actor(actor)) throw new Error(`Must send to an actor object. Attempted send to ${json.encode(actor)}`) + if (!is_actor(actor)) throw new Error(`Must send to an actor object. Attempted send to ${json.encode(actor)}`) if (typeof message !== 'object') throw new Error('Must send an object record.') if (actor.__ACTORDATA__.id === prosperon.id) { @@ -865,14 +876,14 @@ function send_messages() { var replies = {} -$_.send = function(actor, message, reply) { +function _send(actor, message, reply) { if (typeof message !== 'object') throw new Error('Message must be an object') var send = {type:"user", data: message} if (actor[HEADER] && actor[HEADER].replycc) { var header = actor[HEADER] - if (!header.replycc || !$_.is_actor(header.replycc)) + if (!header.replycc || !is_actor(header.replycc)) throw new Error(`Supplied actor had a return, but it's not a valid actor! ${json.encode(actor[HEADER])}`) actor = header.replycc @@ -883,15 +894,19 @@ $_.send = function(actor, message, reply) { var id = util.guid() replies[id] = reply send.reply = id - send.replycc = $_ + send.replycc = $_ // This still references the engine's internal $_ } // Instead of sending immediately, queue it actor_prep(actor,send); } -$_.send[prosperon.DOC] = "sends a message to another actor..." -$_.blast = $_.send; +Object.defineProperty(globalThis, 'send', { + value: _send, + writable: false, + configurable: false, + enumerable: true +}); var cmd = use('cmd') cmd.process(prosperon.argv.slice()) @@ -924,7 +939,8 @@ if (!prosperon.args.program) if (typeof prosperon.args.program !== 'string') prosperon.args.program = 'main.js'; -actor.spawn(prosperon.args.program) +// Spawn the root actor with access to $_ +actor.spawn.call({[UNDERLINGS]: new Set()}, prosperon.args.program, undefined) function destroyself() { console.log(`Got the message to destroy self.`) diff --git a/scripts/core/io.js b/scripts/core/io.js index ac53fa72..b461c301 100644 --- a/scripts/core/io.js +++ b/scripts/core/io.js @@ -160,5 +160,5 @@ $_.receiver(e => { } for (var a of subscribers) - $_.send(a, e); + send(a, e); }); diff --git a/scripts/modules/moth.js b/scripts/modules/moth.js index 028b1e9a..f2417dc4 100644 --- a/scripts/modules/moth.js +++ b/scripts/modules/moth.js @@ -2,6 +2,17 @@ * Moth Game Framework * Higher-level game development framework built on top of Prosperon */ + +// Check if we received the required delay function +if (!arg || arg.length === 0 || typeof arg[0] !== 'function') { + throw new Error("moth module requires $_.delay function as first argument. Usage: use('moth', $_.delay)"); +} + +// Verify it's actually a delay function by checking if it has the expected behavior +var delay = arg[0]; +if (!delay.name || (delay.name !== 'delay' && !delay.name.includes('delay'))) { + console.warn("Warning: The function passed to moth module may not be $_.delay"); +} var os = use('os'); var io = use('io'); @@ -89,7 +100,7 @@ function startGameLoop() { } // Schedule next frame - $_.delay(loop, Math.max(0, targetFrameTime - (os.now() - now))); + delay(loop, Math.max(0, targetFrameTime - (os.now() - now))); } loop(); diff --git a/tests/comments.js b/tests/comments.js index aa26d76c..1284ddb8 100644 --- a/tests/comments.js +++ b/tests/comments.js @@ -21,7 +21,7 @@ $_.receiver(tree => { for (child of tree.children) { requestors.push(cb => { var child_actor = $_.start(undefined, "tests/comments.js") - $_.send(child_actor, child, cb) + send(child_actor, child, cb) }) } @@ -33,9 +33,9 @@ $_.receiver(tree => { id: tree.id, children: children_results } - $_.send(tree, full_comment) + send(tree, full_comment) } else { - $_.send(tree, reason); + send(tree, reason); } }) }) \ No newline at end of file diff --git a/tests/contact.js b/tests/contact.js index c7563693..fae05370 100644 --- a/tests/contact.js +++ b/tests/contact.js @@ -4,7 +4,7 @@ function contact_fn(actor,reason) { if (actor) { console.log(`Got an actor: ${json.encode(actor)}`) - $_.send(actor, {greet: "Hello!"}, e => { + send(actor, {greet: "Hello!"}, e => { console.log(`Got the response ${json.encode(e)}. Goodbye!`); $_.stop() }) diff --git a/tests/overling.js b/tests/overling.js index ee6ac06e..1a9f6055 100644 --- a/tests/overling.js +++ b/tests/overling.js @@ -4,7 +4,7 @@ $_.start(e => { console.log(json.encode(e)) $_.connection(e => console.log(json.encode(e)), e.actor) // get connection info - $_.send(e.actor, {message: "Hello!"}) + send(e.actor, {message: "Hello!"}) $_.couple(e.actor) diff --git a/tests/parseq.js b/tests/parseq.js index 83006997..5a041360 100644 --- a/tests/parseq.js +++ b/tests/parseq.js @@ -19,7 +19,7 @@ var tree = { var actor = $_.start(undefined, "tests/comments.js") -$_.send(actor, tree, (result, reason) => { +send(actor, tree, (result, reason) => { if (reason) console.log(reason) else diff --git a/tests/portal.js b/tests/portal.js index 26dc58c7..97ea7e91 100644 --- a/tests/portal.js +++ b/tests/portal.js @@ -4,13 +4,13 @@ var password = "abc123" $_.portal(e => { if (e.password !== password) - $_.send(e, {reason:"Password does not match."}); + send(e, {reason:"Password does not match."}); else - $_.send(e, $_) + send(e, $_) }, 5678); $_.receiver(e => { console.log(`Got message: ${json.encode(e)}`) - $_.send(e, {greet: "Hello back!"}) + send(e, {greet: "Hello back!"}) $_.delay(_ => $_.stop(), 0.2) }) diff --git a/tests/qr_drag.js b/tests/qr_drag.js index 6f12e22f..bdfc9bfd 100644 --- a/tests/qr_drag.js +++ b/tests/qr_drag.js @@ -95,7 +95,7 @@ var qr_img = graphics.from_surface("qr", bsurf) img = frame_img -$_.send(ioguy, { +send(ioguy, { type: "subscribe", actor: $_ }) diff --git a/tests/reply.js b/tests/reply.js index 9fda4969..da1c8290 100644 --- a/tests/reply.js +++ b/tests/reply.js @@ -3,7 +3,7 @@ var os = use('os') $_.receiver(e => { console.log(`Got a message: ${json.encode(e)}`) - $_.send(e, { + send(e, { message: "Good to go." }) }) diff --git a/tests/send.js b/tests/send.js index a6f854fe..3c5ad924 100644 --- a/tests/send.js +++ b/tests/send.js @@ -2,7 +2,7 @@ var os = use('os') $_.start(e => { console.log(json.encode(e.actor)) - $_.send(e.actor, { message: "Hello! Good to go?" }, msg => { + send(e.actor, { message: "Hello! Good to go?" }, msg => { console.log(`Original sender got message back: ${json.encode(msg)}. Stopping!`) $_.stop() }) diff --git a/tests/test_actor_access.js b/tests/test_actor_access.js new file mode 100644 index 00000000..ca60aa50 --- /dev/null +++ b/tests/test_actor_access.js @@ -0,0 +1,18 @@ +// Test that actors have access to $_ +console.log("Testing actor access to $_:"); + +// In an actor script, $_ should be available +if (typeof $_ !== 'undefined') { + console.log("✓ Actor has access to $_"); + console.log(" $_.random is a", typeof $_.random); + console.log(" $_.clock is a", typeof $_.clock); + + // Test spawning another actor + var child = this.spawn('test_child_actor.js'); + + // Test using a module + var testModule = use('test_module'); + console.log("✓ Module loaded, result:", testModule.test()); +} else { + console.error("✗ Actor does NOT have access to $_"); +} \ No newline at end of file diff --git a/tests/test_args.js b/tests/test_args.js new file mode 100644 index 00000000..f5896924 --- /dev/null +++ b/tests/test_args.js @@ -0,0 +1,18 @@ +// Test module to verify argument passing + +console.log("Test args module loaded"); +console.log("Number of arguments:", arg.length); +console.log("Arguments received:", arg); + +function createMessage(prefix) { + prefix = prefix || "default"; + return prefix + ": " + arg.join(", "); +} + +return { + args: arg, + message: createMessage(arg[0]), + allArgs: function() { + return "All arguments: " + arg.join(", "); + } +}; \ No newline at end of file diff --git a/tests/test_child_actor.js b/tests/test_child_actor.js new file mode 100644 index 00000000..5a55a775 --- /dev/null +++ b/tests/test_child_actor.js @@ -0,0 +1,8 @@ +// Child actor test +console.log("Child actor spawned"); + +if (typeof $_ !== 'undefined') { + console.log("✓ Child actor has access to $_"); +} else { + console.error("✗ Child actor does NOT have access to $_"); +} \ No newline at end of file diff --git a/tests/test_module.js b/tests/test_module.js new file mode 100644 index 00000000..5dff7e41 --- /dev/null +++ b/tests/test_module.js @@ -0,0 +1,15 @@ +// Test module - should NOT have access to $_ + +function test() { + if (typeof $_ !== 'undefined') { + console.error("✗ Module incorrectly has access to $_!"); + return "ERROR: Module has $_ access"; + } else { + console.log("✓ Module correctly does NOT have access to $_"); + return "Module loaded without $_ access"; + } +} + +return { + test: test +}; \ No newline at end of file diff --git a/tests/test_use_args.js b/tests/test_use_args.js new file mode 100644 index 00000000..357e2a75 --- /dev/null +++ b/tests/test_use_args.js @@ -0,0 +1,18 @@ +// Test script to verify use() with arguments + +console.log("Testing use() with arguments:"); + +// Test 1: Load module without arguments +var module1 = use('test_args'); +console.log("Module 1 message:", module1.message); +console.log("Module 1 args:", module1.args); + +// Test 2: Load module with arguments +var module2 = use('test_args', 'hello', 'world', 123); +console.log("Module 2 message:", module2.message); +console.log("Module 2 all args:", module2.allArgs()); + +// Test 3: Verify modules are cached (should return same as module1) +var module3 = use('test_args'); +console.log("Module 3 (cached) message:", module3.message); +console.log("Are module1 and module3 the same?", module1 === module3); \ No newline at end of file diff --git a/tests/window.js b/tests/window.js index 58171215..e8712c4a 100644 --- a/tests/window.js +++ b/tests/window.js @@ -44,7 +44,7 @@ var ioguy = { } } -$_.send(ioguy, { +send(ioguy, { type: "subscribe", actor: $_ })