-u is no longer available as a global, only within the running actor code; enable passing args to use
Some checks failed
Build and Deploy / build-macos (push) Failing after 4s
Build and Deploy / build-windows (CLANG64) (push) Has been cancelled
Build and Deploy / package-dist (push) Has been cancelled
Build and Deploy / deploy-itch (push) Has been cancelled
Build and Deploy / deploy-gitea (push) Has been cancelled
Build and Deploy / build-linux (push) Has been cancelled

This commit is contained in:
2025-05-22 18:58:13 -05:00
parent 698dbd81ae
commit efd98460c5
19 changed files with 161 additions and 57 deletions

View File

@@ -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'});
}
});
```

View File

@@ -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'
});

View File

@@ -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.`)

View File

@@ -160,5 +160,5 @@ $_.receiver(e => {
}
for (var a of subscribers)
$_.send(a, e);
send(a, e);
});

View File

@@ -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();

View File

@@ -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);
}
})
})

View File

@@ -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()
})

View File

@@ -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)

View File

@@ -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

View File

@@ -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)
})

View File

@@ -95,7 +95,7 @@ var qr_img = graphics.from_surface("qr", bsurf)
img = frame_img
$_.send(ioguy, {
send(ioguy, {
type: "subscribe",
actor: $_
})

View File

@@ -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."
})
})

View File

@@ -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()
})

View File

@@ -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 $_");
}

18
tests/test_args.js Normal file
View File

@@ -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(", ");
}
};

View File

@@ -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 $_");
}

15
tests/test_module.js Normal file
View File

@@ -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
};

18
tests/test_use_args.js Normal file
View File

@@ -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);

View File

@@ -44,7 +44,7 @@ var ioguy = {
}
}
$_.send(ioguy, {
send(ioguy, {
type: "subscribe",
actor: $_
})