first pass at multiturtles
This commit is contained in:
parent
f1f954de46
commit
bac3c29d1d
|
@ -1014,6 +1014,108 @@ fn assert! {
|
||||||
else panic! "Assert failed: {msg} with {value}"
|
else panic! "Assert failed: {msg} with {value}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
&&& processes: doing more than one thing
|
||||||
|
fn self {
|
||||||
|
"Returns the current process's pid, as a keyword."
|
||||||
|
() -> base :process (:self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send {
|
||||||
|
"Sends a message to the specified process and returns the message."
|
||||||
|
(pid as :keyword, msg) -> {
|
||||||
|
base :process (:send, pid, msg)
|
||||||
|
msg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn spawn! {
|
||||||
|
"Spawns a process. Takes a 0-argument (nullary) function that will be executed as the new process. Returns a keyword process ID (pid) of the newly spawned process."
|
||||||
|
(f as :fn) -> base :process (:spawn, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn yield! {
|
||||||
|
"Forces a process to yield."
|
||||||
|
() -> base :process (:yield)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn alive? {
|
||||||
|
"Tells if the passed keyword is the id for a live process."
|
||||||
|
(pid as :keyword) -> base :process (:alive, pid)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn link! {
|
||||||
|
"Creates a link between two processes. There are two types of links: `:report`, which sends a message to pid1 when pid2 dies; and `:panic`, which causes a panic in one when the other dies. The default is `:report`."
|
||||||
|
(pid1 as :keyword, pid2 as :keyword) -> link! (pid1, pid2, :report)
|
||||||
|
(pid1 as :keyword, pid2 as :keyword, :report) -> base :process (:link_report, pid1, pid2)
|
||||||
|
(pid1 as :keyword, pid2 as :keyword, :panic) -> base :process (:link_panic, pid1, pid2)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush! {
|
||||||
|
"Clears the current process's mailbox and returns all the messages."
|
||||||
|
() -> base :process (:flush)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sleep! {
|
||||||
|
"Puts the current process to sleep for at least the specified number of milliseconds."
|
||||||
|
(ms as :number) -> base :process (:sleep, ms)
|
||||||
|
}
|
||||||
|
|
||||||
|
& TODO: make this more robust, to handle multiple pending requests w/o data races
|
||||||
|
fn request_fetch! {
|
||||||
|
(pid as :keyword, url as :string) -> {
|
||||||
|
store! (fetch_outbox, url)
|
||||||
|
request_fetch! (pid)
|
||||||
|
}
|
||||||
|
(pid as :keyword) -> {
|
||||||
|
if empty? (unbox (fetch_inbox))
|
||||||
|
then {
|
||||||
|
yield! ()
|
||||||
|
request_fetch! (pid)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
send (pid, (:reply, unbox (fetch_inbox)))
|
||||||
|
store! (fetch_inbox, ())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fetch {
|
||||||
|
"Requests the contents of the URL passed in. Returns a result tuple of `(:ok, {contents})` or `(:err, {status code})`."
|
||||||
|
(url) -> {
|
||||||
|
let pid = self ()
|
||||||
|
spawn! (fn () -> request_fetch! (pid, url))
|
||||||
|
receive {
|
||||||
|
(:reply, response) -> response
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn input_reader! {
|
||||||
|
(pid as :keyword) -> {
|
||||||
|
if do input > unbox > not
|
||||||
|
then {
|
||||||
|
yield! ()
|
||||||
|
input_reader! (pid)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
send (pid, (:reply, unbox (input)))
|
||||||
|
store! (input, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_input {
|
||||||
|
"Waits until there is input in the input buffer, and returns it once there is."
|
||||||
|
() -> {
|
||||||
|
let pid = self ()
|
||||||
|
spawn! (fn () -> input_reader! (pid))
|
||||||
|
receive {
|
||||||
|
(:reply, response) -> response
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&&& Turtle & other graphics
|
&&& Turtle & other graphics
|
||||||
|
|
||||||
& some basic colors
|
& some basic colors
|
||||||
|
@ -1180,6 +1282,46 @@ fn loadstate! {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn turtle_listener () -> {
|
||||||
|
receive {
|
||||||
|
(:forward!, steps as :number) -> add_command! (self (), (:forward, steps))
|
||||||
|
(:back!, steps as :number) -> add_command! (self (), (:back, steps))
|
||||||
|
(:left!, turns as :number) -> add_command! (self (), (:left, turns))
|
||||||
|
(:right!, turns as :number) -> add_command! (self (), (:right, turns))
|
||||||
|
(:penup!) -> add_command!(self (), (:penup))
|
||||||
|
(:pendown!) -> add_command! (self (), (:pendown))
|
||||||
|
(:pencolor!, color as :keyword) -> add_command! (self (), (:pencolor, color))
|
||||||
|
(:pencolor!, gray as :number) -> add_command! (self (), (:pencolor, (gray, gray, gray, 255)))
|
||||||
|
(:pencolor!, (r as :number, g as :number, b as :number)) -> add_command! (self (), (:pencolor, (r, g, b, 255)))
|
||||||
|
(:pencolor!, (r as :number, g as :number, b as :number, a as :number)) -> add_command! (self (), (:pencolor, (r, g, b, a)))
|
||||||
|
(:penwidth!, width as :number) -> add_command! (self (), (:penwidth, width))
|
||||||
|
(:home!) -> add_command! (self (), (:home))
|
||||||
|
(:goto!, x as :number, y as :number) -> add_command! (self (), (:goto, (x, y)))
|
||||||
|
(:goto!, (x as :number, y as :number)) -> add_command! (self (), (:goto, (x, y)))
|
||||||
|
(:show!) -> add_command! (self (), (:show))
|
||||||
|
(:hide!) -> add_command! (self (), (:hide))
|
||||||
|
(:loadstate!, state) -> {
|
||||||
|
let #{position, heading, pendown?, pencolor, penwidth, visible?} = state
|
||||||
|
add_command! (self (), (:loadstate, position, heading, visible?, pendown?, penwidth, pencolor))
|
||||||
|
|
||||||
|
}
|
||||||
|
(:pencolor, pid) -> send (pid, (:reply, do turtle_states > unbox > self () > :pencolor))
|
||||||
|
(:position, pid) -> send (pid, (:reply, do turtle_states > unbox > self () > :position))
|
||||||
|
(:penwidth, pid) -> send (pid, (:reply, do turtle_states > unbox > self () > :penwidth))
|
||||||
|
(:heading, pid) -> send (pid, (:reply, do turtle_states > unbox > self () > :heading))
|
||||||
|
does_not_understand -> {
|
||||||
|
let pid = self ()
|
||||||
|
panic! "{pid} does not understand message: {does_not_understand}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
turtle_listener ()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn spawn_turtle! {
|
||||||
|
"Spawns a new turtle in a new process. Methods on the turtle process mirror those of turtle graphics functions in prelude. Returns the pid of the new turtle."
|
||||||
|
() -> spawn! (fn () -> turtle_listener ())
|
||||||
|
}
|
||||||
|
|
||||||
fn apply_command {
|
fn apply_command {
|
||||||
"Takes a turtle state and a command and calculates a new state."
|
"Takes a turtle state and a command and calculates a new state."
|
||||||
(state, command) -> {
|
(state, command) -> {
|
||||||
|
@ -1217,9 +1359,8 @@ fn apply_command {
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
& position () -> (x, y)
|
|
||||||
fn position {
|
fn position {
|
||||||
"Returns the turtle's current position."
|
"Returns the turtle's current position as an `(x, y)` vector tuple."
|
||||||
() -> do turtle_states > unbox > :turtle_0 > :position
|
() -> do turtle_states > unbox > :turtle_0 > :position
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1245,6 +1386,7 @@ fn penwidth {
|
||||||
() -> do turtle_states > unbox > :turtle_0 > :penwidth
|
() -> do turtle_states > unbox > :turtle_0 > :penwidth
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
&&& fake some lispisms with tuples
|
&&& fake some lispisms with tuples
|
||||||
fn cons {
|
fn cons {
|
||||||
"Old-timey lisp `cons`. `Cons`tructs a tuple out of two arguments."
|
"Old-timey lisp `cons`. `Cons`tructs a tuple out of two arguments."
|
||||||
|
@ -1266,108 +1408,6 @@ fn llist {
|
||||||
(...xs) -> foldr (cons, xs, nil)
|
(...xs) -> foldr (cons, xs, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
&&& processes
|
|
||||||
fn self {
|
|
||||||
"Returns the current process's pid, as a keyword."
|
|
||||||
() -> base :process (:self)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn send {
|
|
||||||
"Sends a message to the specified process and returns the message."
|
|
||||||
(pid as :keyword, msg) -> {
|
|
||||||
base :process (:send, pid, msg)
|
|
||||||
msg
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn spawn! {
|
|
||||||
"Spawns a process. Takes a 0-argument (nullary) function that will be executed as the new process. Returns a keyword process ID (pid) of the newly spawned process."
|
|
||||||
(f as :fn) -> base :process (:spawn, f)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn yield! {
|
|
||||||
"Forces a process to yield."
|
|
||||||
() -> base :process (:yield)
|
|
||||||
}
|
|
||||||
|
|
||||||
& TODO: implement these in the VM
|
|
||||||
fn alive? {
|
|
||||||
"Tells if the passed keyword is the id for a live process."
|
|
||||||
(pid as :keyword) -> base :process (:alive, pid)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn link! {
|
|
||||||
"Creates a link between two processes. There are two types of links: `:report`, which sends a message to pid1 when pid2 dies; and `:panic`, which causes a panic in one when the other dies. The default is `:report`."
|
|
||||||
(pid1 as :keyword, pid2 as :keyword) -> link! (pid1, pid2, :report)
|
|
||||||
(pid1 as :keyword, pid2 as :keyword, :report) -> base :process (:link_report, pid1, pid2)
|
|
||||||
(pid1 as :keyword, pid2 as :keyword, :panic) -> base :process (:link_panic, pid1, pid2)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn flush! {
|
|
||||||
"Clears the current process's mailbox and returns all the messages."
|
|
||||||
() -> base :process (:flush)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn sleep! {
|
|
||||||
"Puts the current process to sleep for at least the specified number of milliseconds."
|
|
||||||
(ms as :number) -> base :process (:sleep, ms)
|
|
||||||
}
|
|
||||||
|
|
||||||
& TODO: make this more robust, to handle multiple pending requests w/o data races
|
|
||||||
fn request_fetch! {
|
|
||||||
(pid as :keyword, url as :string) -> {
|
|
||||||
store! (fetch_outbox, url)
|
|
||||||
request_fetch! (pid)
|
|
||||||
}
|
|
||||||
(pid as :keyword) -> {
|
|
||||||
if empty? (unbox (fetch_inbox))
|
|
||||||
then {
|
|
||||||
yield! ()
|
|
||||||
request_fetch! (pid)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
send (pid, (:reply, unbox (fetch_inbox)))
|
|
||||||
store! (fetch_inbox, ())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fetch {
|
|
||||||
"Requests the contents of the URL passed in. Returns a result tuple of `(:ok, {contents})` or `(:err, {status code})`."
|
|
||||||
(url) -> {
|
|
||||||
let pid = self ()
|
|
||||||
spawn! (fn () -> request_fetch! (pid, url))
|
|
||||||
receive {
|
|
||||||
(:reply, response) -> response
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn input_reader! {
|
|
||||||
(pid as :keyword) -> {
|
|
||||||
if do input > unbox > not
|
|
||||||
then {
|
|
||||||
yield! ()
|
|
||||||
input_reader! (pid)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
send (pid, (:reply, unbox (input)))
|
|
||||||
store! (input, nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_input {
|
|
||||||
"Waits until there is input in the input buffer, and returns it once there is."
|
|
||||||
() -> {
|
|
||||||
let pid = self ()
|
|
||||||
spawn! (fn () -> input_reader! (pid))
|
|
||||||
receive {
|
|
||||||
(:reply, response) -> response
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#{
|
#{
|
||||||
& completed actor functions
|
& completed actor functions
|
||||||
self
|
self
|
||||||
|
@ -1380,6 +1420,7 @@ fn read_input {
|
||||||
|
|
||||||
& wip actor functions
|
& wip actor functions
|
||||||
& link!
|
& link!
|
||||||
|
spawn_turtle!
|
||||||
|
|
||||||
& shared memory w/ rust
|
& shared memory w/ rust
|
||||||
& `box`es are actually way cool
|
& `box`es are actually way cool
|
||||||
|
|
54
pkg/ludus.js
54
pkg/ludus.js
|
@ -1,4 +1,4 @@
|
||||||
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}
|
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}
|
||||||
|
|
||||||
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"})
|
||||||
|
@ -440,6 +440,7 @@ function svg_render_turtle (state) {
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: update this to match the new `p5` function
|
||||||
export function svg (commands) {
|
export function svg (commands) {
|
||||||
// console.log(commands)
|
// console.log(commands)
|
||||||
const states = [turtle_init]
|
const states = [turtle_init]
|
||||||
|
@ -509,32 +510,32 @@ function p5_render_turtle (state, calls) {
|
||||||
return calls
|
return calls
|
||||||
}
|
}
|
||||||
|
|
||||||
export function p5 (commands) {
|
// export function p5 (commands) {
|
||||||
const states = [turtle_init]
|
// const states = [turtle_init]
|
||||||
commands.reduce((prev_state, command) => {
|
// commands.reduce((prev_state, command) => {
|
||||||
const new_state = command_to_state(prev_state, command)
|
// const new_state = command_to_state(prev_state, command)
|
||||||
states.push(new_state)
|
// states.push(new_state)
|
||||||
return new_state
|
// return new_state
|
||||||
}, turtle_init)
|
// }, turtle_init)
|
||||||
// console.log(states)
|
// // console.log(states)
|
||||||
const [r, g, b, _] = resolve_color(background_color)
|
// const [r, g, b, _] = resolve_color(background_color)
|
||||||
if ((r + g + b)/3 > 128) turtle_color = [0, 0, 0, 150]
|
// if ((r + g + b)/3 > 128) turtle_color = [0, 0, 0, 150]
|
||||||
const p5_calls = [...p5_call_root()]
|
// const p5_calls = [...p5_call_root()]
|
||||||
for (let i = 1; i < states.length; ++i) {
|
// for (let i = 1; i < states.length; ++i) {
|
||||||
const prev = states[i - 1]
|
// const prev = states[i - 1]
|
||||||
const curr = states[i]
|
// const curr = states[i]
|
||||||
const calls = states_to_call(prev, curr)
|
// const calls = states_to_call(prev, curr)
|
||||||
for (const call of calls) {
|
// for (const call of calls) {
|
||||||
p5_calls.push(call)
|
// p5_calls.push(call)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
p5_calls[0] = ["background", ...resolve_color(background_color)]
|
// p5_calls[0] = ["background", ...resolve_color(background_color)]
|
||||||
p5_render_turtle(states[states.length - 1], p5_calls)
|
// p5_render_turtle(states[states.length - 1], p5_calls)
|
||||||
p5_calls.push(["pop"])
|
// p5_calls.push(["pop"])
|
||||||
return p5_calls
|
// return p5_calls
|
||||||
}
|
// }
|
||||||
|
|
||||||
export function new_p5 (commands) {
|
export function p5 (commands) {
|
||||||
const all_states = {}
|
const all_states = {}
|
||||||
commands.reduce((prev_state, command) => {
|
commands.reduce((prev_state, command) => {
|
||||||
const [turtle_id, new_state] = command_to_state(prev_state, command)
|
const [turtle_id, new_state] = command_to_state(prev_state, command)
|
||||||
|
@ -546,7 +547,6 @@ export function new_p5 (commands) {
|
||||||
if ((r + g + b)/3 > 128) turtle_color = [0, 0, 0, 150]
|
if ((r + g + b)/3 > 128) turtle_color = [0, 0, 0, 150]
|
||||||
const p5_calls = [...p5_call_root()]
|
const p5_calls = [...p5_call_root()]
|
||||||
for (const states of Object.values(all_states)) {
|
for (const states of Object.values(all_states)) {
|
||||||
console.log(states)
|
|
||||||
for (let i = 1; i < states.length; ++i) {
|
for (let i = 1; i < states.length; ++i) {
|
||||||
const prev = states[i - 1]
|
const prev = states[i - 1]
|
||||||
const curr = states[i]
|
const curr = states[i]
|
||||||
|
|
Binary file not shown.
Loading…
Reference in New Issue
Block a user