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.
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`.
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.
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.
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 = new Worker(worker_url, {type: "module"})
@ -51,7 +51,7 @@ async function handle_messages (e) {
case "Commands": {
console.log("Main: ludus commands => ", 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
}
break
@ -236,69 +236,71 @@ function unit_of (heading) {
return [Math.cos(radians), Math.sin(radians)]
}
function command_to_state (prev_state, command) {
const [_target, _id, curr_command] = command
function command_to_state (state, 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
switch (verb) {
case "goto": {
const [_, x, y] = curr_command
return {...prev_state, position: [x, y]}
return [target, {...prev_state, position: [x, y]}]
}
case "home": {
return {...prev_state, position: [0, 0], heading: 0}
return [target, {...prev_state, position: [0, 0], heading: 0}]
}
case "right": {
const [_, angle] = curr_command
const {heading} = prev_state
return {...prev_state, heading: heading + angle}
return [target, {...prev_state, heading: heading + angle}]
}
case "left": {
const [_, angle] = curr_command
const {heading} = prev_state
return {...prev_state, heading: heading - angle}
return [target, {...prev_state, heading: heading - angle}]
}
case "forward": {
const [_, steps] = curr_command
const {heading, position} = prev_state
const unit = unit_of(heading)
const move = mult(unit, steps)
return {...prev_state, position: add(position, move)}
return [target, {...prev_state, position: add(position, move)}]
}
case "back": {
const [_, steps] = curr_command
const {heading, position} = prev_state
const unit = unit_of(heading)
const move = mult(unit, -steps)
return {...prev_state, position: add(position, move)}
return [target, {...prev_state, position: add(position, move)}]
}
case "penup": {
return {...prev_state, pendown: false}
return [target, {...prev_state, pendown: false}]
}
case "pendown": {
return {...prev_state, pendown: true}
return [target, {...prev_state, pendown: true}]
}
case "penwidth": {
const [_, width] = curr_command
return {...prev_state, penwidth: width}
return [target, {...prev_state, penwidth: width}]
}
case "pencolor": {
const [_, color] = curr_command
return {...prev_state, pencolor: color}
return [target, {...prev_state, pencolor: color}]
}
case "setheading": {
const [_, heading] = curr_command
return {...prev_state, heading: heading}
return [target, {...prev_state, heading: heading}]
}
case "loadstate": {
// console.log("LOADSTATE: ", 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": {
return {...prev_state, visible: true}
return [target, {...prev_state, visible: true}]
}
case "hide": {
return {...prev_state, visible: false}
return [target, {...prev_state, visible: false}]
}
case "background": {
background_color = curr_command[1]
@ -532,4 +534,32 @@ export function p5 (commands) {
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
}