middleware should now handle multiple turtles

This commit is contained in:
Scott Richmond 2025-07-05 15:26:48 -04:00
parent 369f8a54f4
commit ff6aaf5cdf
2 changed files with 60 additions and 18 deletions

View File

@ -1233,9 +1233,21 @@ Anyway, here's the tl;dr for basic process linking:
* `monitor/1` means we can monitor exit messages, these will be `(:exit, pid, (:ok, value))` in the case of successful return, or `(:exit, pid, (:err, msg))` in the case of a panic. These are sent as normal messages to the monitoring process. * `monitor/1` means we can monitor exit messages, these will be `(:exit, pid, (:ok, value))` in the case of successful return, or `(:exit, pid, (:err, msg))` in the case of a panic. These are sent as normal messages to the monitoring process.
Erlang/Elixir has a lot more, but that's basically it for Ludus for now. Erlang/Elixir has a lot more, but that's basically it for Ludus for now.
I don't think we need more than the binary: `:ok`/`:err`, or return/panic as how processes exit.
`monitor/1` makes it easy to write an `await`. `monitor/1` makes it easy to write an `await`.
We would need a way of killing processes remotely if indeed we wanted to write robust distributed applications.
But I'm less interested in that than in the cognitive modelling here.
As I'm thinking about this, I actually want to roll back `spawn` as a special form. As I'm thinking about this, I actually want to roll back `spawn` as a special form.
I think it's actually a good cognitive shift to understand that functions are reified, deferred computations. I think it's actually a good cognitive shift to understand that functions are reified, deferred computations.
And that processes take this a long way. And that processes take this a long way.
We can revisit the idea of a special spawn form later. We can revisit the idea of a special spawn form later.
=> done
And now, we build capacity for building projects.
* [ ] allow for multiple turtles
- [ ] rework p5 function in `ludus.js` to properly parse commands targeted at different turtles
- [ ] write ludus code to spawn & manipulate turtle actors
* [ ] do some sample multiturtle sketches

View File

@ -1,4 +1,4 @@
if (window) window.ludus = {run, kill, flush_stdout, stdout, p5, svg, flush_commands, commands, result, flush_result, input, is_running, key_down, key_up, is_starting_up} if (window) window.ludus = {run, kill, flush_stdout, stdout, p5, new_p5, svg, flush_commands, commands, result, flush_result, input, is_running, key_down, key_up, is_starting_up}
const worker_url = new URL("worker.js", import.meta.url) const worker_url = new URL("worker.js", import.meta.url)
const worker = new Worker(worker_url, {type: "module"}) const worker = new Worker(worker_url, {type: "module"})
@ -51,7 +51,7 @@ async function handle_messages (e) {
case "Commands": { case "Commands": {
console.log("Main: ludus commands => ", msg.data) console.log("Main: ludus commands => ", msg.data)
for (const command of msg.data) { for (const command of msg.data) {
// attempt to solve out-of-order command bug // commands will arrive asynchronously; ensure correct ordering
ludus_commands[command[1]] = command ludus_commands[command[1]] = command
} }
break break
@ -236,69 +236,71 @@ function unit_of (heading) {
return [Math.cos(radians), Math.sin(radians)] return [Math.cos(radians), Math.sin(radians)]
} }
function command_to_state (prev_state, command) { function command_to_state (state, command) {
const [_target, _id, curr_command] = command const [target, _id, curr_command] = command
const state_stack = state[target] ?? [turtle_init]
const prev_state = state_stack[state_stack.length - 1]
const [verb] = curr_command const [verb] = curr_command
switch (verb) { switch (verb) {
case "goto": { case "goto": {
const [_, x, y] = curr_command const [_, x, y] = curr_command
return {...prev_state, position: [x, y]} return [target, {...prev_state, position: [x, y]}]
} }
case "home": { case "home": {
return {...prev_state, position: [0, 0], heading: 0} return [target, {...prev_state, position: [0, 0], heading: 0}]
} }
case "right": { case "right": {
const [_, angle] = curr_command const [_, angle] = curr_command
const {heading} = prev_state const {heading} = prev_state
return {...prev_state, heading: heading + angle} return [target, {...prev_state, heading: heading + angle}]
} }
case "left": { case "left": {
const [_, angle] = curr_command const [_, angle] = curr_command
const {heading} = prev_state const {heading} = prev_state
return {...prev_state, heading: heading - angle} return [target, {...prev_state, heading: heading - angle}]
} }
case "forward": { case "forward": {
const [_, steps] = curr_command const [_, steps] = curr_command
const {heading, position} = prev_state const {heading, position} = prev_state
const unit = unit_of(heading) const unit = unit_of(heading)
const move = mult(unit, steps) const move = mult(unit, steps)
return {...prev_state, position: add(position, move)} return [target, {...prev_state, position: add(position, move)}]
} }
case "back": { case "back": {
const [_, steps] = curr_command const [_, steps] = curr_command
const {heading, position} = prev_state const {heading, position} = prev_state
const unit = unit_of(heading) const unit = unit_of(heading)
const move = mult(unit, -steps) const move = mult(unit, -steps)
return {...prev_state, position: add(position, move)} return [target, {...prev_state, position: add(position, move)}]
} }
case "penup": { case "penup": {
return {...prev_state, pendown: false} return [target, {...prev_state, pendown: false}]
} }
case "pendown": { case "pendown": {
return {...prev_state, pendown: true} return [target, {...prev_state, pendown: true}]
} }
case "penwidth": { case "penwidth": {
const [_, width] = curr_command const [_, width] = curr_command
return {...prev_state, penwidth: width} return [target, {...prev_state, penwidth: width}]
} }
case "pencolor": { case "pencolor": {
const [_, color] = curr_command const [_, color] = curr_command
return {...prev_state, pencolor: color} return [target, {...prev_state, pencolor: color}]
} }
case "setheading": { case "setheading": {
const [_, heading] = curr_command const [_, heading] = curr_command
return {...prev_state, heading: heading} return [target, {...prev_state, heading: heading}]
} }
case "loadstate": { case "loadstate": {
// console.log("LOADSTATE: ", curr_command) // console.log("LOADSTATE: ", curr_command)
const [_, [x, y], heading, visible, pendown, penwidth, pencolor] = curr_command const [_, [x, y], heading, visible, pendown, penwidth, pencolor] = curr_command
return {position: [x, y], heading, visible, pendown, penwidth, pencolor} return [target, {position: [x, y], heading, visible, pendown, penwidth, pencolor}]
} }
case "show": { case "show": {
return {...prev_state, visible: true} return [target, {...prev_state, visible: true}]
} }
case "hide": { case "hide": {
return {...prev_state, visible: false} return [target, {...prev_state, visible: false}]
} }
case "background": { case "background": {
background_color = curr_command[1] background_color = curr_command[1]
@ -532,4 +534,32 @@ export function p5 (commands) {
return p5_calls return p5_calls
} }
export function new_p5 (commands) {
const all_states = {}
commands.reduce((prev_state, command) => {
const [turtle_id, new_state] = command_to_state(prev_state, command)
if (!all_states[turtle_id]) all_states[turtle_id] = [turtle_init]
all_states[turtle_id].push(new_state)
return new_state
}, all_states)
const [r, g, b, _] = resolve_color(background_color)
if ((r + g + b)/3 > 128) turtle_color = [0, 0, 0, 150]
const p5_calls = [...p5_call_root()]
for (const states of Object.values(all_states)) {
console.log(states)
for (let i = 1; i < states.length; ++i) {
const prev = states[i - 1]
const curr = states[i]
const calls = states_to_call(prev, curr)
for (const call of calls) {
p5_calls.push(call)
}
p5_calls[0] = ["background", ...resolve_color(background_color)]
p5_render_turtle(states[states.length - 1], p5_calls)
}
}
p5_calls.push(["pop"])
return p5_calls
}