use crate::chunk::Chunk; use crate::value::Value; use crate::vm::{Creature, Panic}; use crate::io::{MsgOut, MsgIn, do_io}; use std::cell::RefCell; use std::collections::{HashMap, HashSet}; use std::mem::swap; use std::rc::Rc; use wasm_bindgen::prelude::*; // Grab some JS stuff #[wasm_bindgen] extern "C" { #[wasm_bindgen(js_namespace = console)] fn log(s: &str); #[wasm_bindgen(js_namespace = Math)] fn random() -> f64; #[wasm_bindgen(js_namespace = Date)] fn now() -> f64; } const ANIMALS: [&str; 32] = [ "tortoise", "hare", "squirrel", "hawk", "woodpecker", "cardinal", "coyote", "raccoon", "rat", "axolotl", "cormorant", "duck", "orca", "humbpack", "tern", "quokka", "koala", "kangaroo", "zebra", "hyena", "giraffe", "hippopotamus", "capybara", "python", "gopher", "crab", "trout", "osprey", "lemur", "wobbegong", "walrus", "opossum", ]; #[derive(Debug, Clone, PartialEq)] enum Status { Empty, Borrowed, Nested(Creature), } impl std::fmt::Display for Status { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { Status::Empty => write!(f, "empty"), Status::Borrowed => write!(f, "borrowed"), Status::Nested(creature) => write!(f, "nested {creature}"), } } } impl Status { pub fn receive(&mut self, msg: Value) { match self { Status::Nested(creature) => creature.receive(msg), Status::Borrowed => println!("sending a message to a borrowed process"), Status::Empty => println!("sending a message to a dead process"), } } } #[derive(Debug, Clone, PartialEq)] pub struct Zoo { procs: Vec, empty: Vec, ids: HashMap<&'static str, usize>, dead: HashSet<&'static str>, kill_list: Vec<&'static str>, sleeping: HashMap<&'static str, f64>, active_idx: usize, active_id: &'static str, } impl Zoo { pub fn new() -> Zoo { Zoo { procs: vec![], empty: vec![], ids: HashMap::new(), kill_list: vec![], dead: HashSet::new(), sleeping: HashMap::new(), active_idx: 0, active_id: "", } } fn random_id(&self) -> String { let rand_idx = (random() * 32.0) as usize; let idx = self.procs.len(); format!("{}_{idx}", ANIMALS[rand_idx]) } fn new_id(&self) -> &'static str { let mut new = self.random_id(); while self.dead.iter().any(|old| *old == new) { new = self.random_id(); } new.leak() } pub fn put(&mut self, mut proc: Creature) -> &'static str { if self.empty.is_empty() { let id = self.new_id(); let idx = self.procs.len(); proc.pid = id; self.procs.push(Status::Nested(proc)); self.ids.insert(id, idx); id } else { let idx = self.empty.pop().unwrap(); let rand = (random() * 32.0) as usize; let id = format!("{}_{idx}", ANIMALS[rand]).leak(); proc.pid = id; self.ids.insert(id, idx); self.procs[idx] = Status::Nested(proc); id } } pub fn kill(&mut self, id: &'static str) { self.kill_list.push(id); } pub fn sleep(&mut self, id: &'static str, ms: f64) { self.sleeping .insert(id, now() + ms); } pub fn is_alive(&self, id: &'static str) -> bool { if self.kill_list.contains(&id) { return false; } let idx = self.ids.get(id); match idx { Some(idx) => match self.procs.get(*idx) { Some(proc) => match proc { Status::Empty => false, Status::Borrowed => true, Status::Nested(_) => true, }, None => false, }, None => false, } } pub fn clean_up(&mut self) { while let Some(id) = self.kill_list.pop() { if let Some(idx) = self.ids.get(id) { println!("buried process {id}"); self.procs[*idx] = Status::Empty; self.empty.push(*idx); self.ids.remove(id); self.dead.insert(id); } } self.sleeping .retain(|_, wakeup_time| now() < *wakeup_time); println!( "currently sleeping processes: {}", self.sleeping .keys() .map(|id| id.to_string()) .collect::>() .join(" | ") ); } pub fn catch(&mut self, id: &'static str) -> Creature { if let Some(idx) = self.ids.get(id) { let mut proc = Status::Borrowed; swap(&mut proc, &mut self.procs[*idx]); let Status::Nested(proc) = proc else { unreachable!("tried to borrow an empty or already-borrowed process {id}"); }; proc } else { unreachable!("tried to borrow a non-existent process {id}"); } } pub fn release(&mut self, proc: Creature) { let id = proc.pid; if let Some(idx) = self.ids.get(id) { let mut proc = Status::Nested(proc); swap(&mut proc, &mut self.procs[*idx]); } } pub fn is_available(&self) -> bool { match &self.procs[self.active_idx] { Status::Empty => false, Status::Borrowed => false, Status::Nested(proc) => !self.sleeping.contains_key(proc.pid), } } pub fn next(&mut self) -> &'static str { self.clean_up(); let starting_idx = self.active_idx; self.active_idx = (self.active_idx + 1) % self.procs.len(); while !self.is_available() { // we've gone round the process queue already // that means no process is active // but we may have processes that are alive and asleep // if nothing is active, yield back to the world's event loop if self.active_idx == starting_idx { return "" } self.active_idx = (self.active_idx + 1) % self.procs.len(); } match &self.procs[self.active_idx] { Status::Empty | Status::Borrowed => unreachable!(), Status::Nested(proc) => proc.pid, } } pub fn send_msg(&mut self, id: &'static str, msg: Value) { let Some(idx) = self.ids.get(id) else { return; }; self.procs[*idx].receive(msg); } } #[derive(Debug, Clone, PartialEq)] pub struct Buffers { console: Value, commands: Value, // fetch_outbox: Value, // fetch_inbox: Value, input: Value, } impl Buffers { pub fn new (prelude: imbl::HashMap<&'static str, Value>) -> Buffers { Buffers { console: prelude.get("console").unwrap().clone(), commands: prelude.get("turtle_commands").unwrap().clone(), // fetch_outbox: prelude.get("fetch_outbox").unwrap().clone(), // fetch_inbox: prelude.get("fetch_inbox").unwrap().clone(), input: prelude.get("input").unwrap().clone(), } } pub fn console (&self) -> Rc> { self.console.as_box() } pub fn input (&self) -> Rc> { self.input.as_box() } pub fn commands (&self) -> Rc> { self.commands.as_box() } // pub fn fetch_outbox (&self) -> Rc> { // self.fetch_outbox.as_box() // } // pub fn fetch_inbox (&self) -> Rc> { // self.fetch_inbox.as_box() // } } #[derive(Debug, Clone, PartialEq)] pub struct World { zoo: Rc>, active: Option, main: &'static str, pub result: Option>, buffers: Buffers, last_io: f64, kill_signal: bool, } impl World { pub fn new(chunk: Chunk, prelude: imbl::HashMap<&'static str, Value>, debug: bool) -> World { let zoo = Rc::new(RefCell::new(Zoo::new())); let main = Creature::new(chunk, zoo.clone(), debug); let id = zoo.borrow_mut().put(main); let buffers = Buffers::new(prelude); World { zoo, active: None, main: id, result: None, buffers, last_io: 0.0, kill_signal: false, } } fn next(&mut self) { let mut active = None; swap(&mut active, &mut self.active); let mut zoo = self.zoo.borrow_mut(); if let Some(active) = active { zoo.release(active); } let new_active_id = zoo.next(); if new_active_id.is_empty() { self.active = None; return; } let mut new_active_proc = zoo.catch(new_active_id); new_active_proc.reset_reductions(); let mut new_active_opt = Some(new_active_proc); swap(&mut new_active_opt, &mut self.active); } fn activate_main(&mut self) { let main = self.zoo.borrow_mut().catch(self.main); self.active = Some(main); } fn active_id(&mut self) -> Option<&'static str> { match &self.active { Some(creature) => Some(creature.pid), None => None, } } fn kill_active(&mut self) { if let Some(pid) = self.active_id() { self.zoo.borrow_mut().kill(pid); } } fn active_result(&mut self) -> &Option> { if self.active.is_none() { return &None; } &self.active.as_ref().unwrap().result } fn flush_buffers(&mut self) -> Vec { let mut outbox = vec![]; if let Some(console) = self.flush_console() { outbox.push(console); } if let Some(commands) = self.flush_commands() { outbox.push(commands); } outbox } fn flush_console(&self) -> Option { let console = self.buffers.console(); let working_copy = RefCell::new(Value::new_list()); console.swap(&working_copy); let working_value = working_copy.borrow(); if working_value.as_list().is_empty() { return None; } Some(MsgOut::Console(working_value.clone())) } fn flush_commands(&self) -> Option { let commands = self.buffers.commands(); let working_copy = RefCell::new(Value::new_list()); commands.swap(&working_copy); let commands = working_copy.borrow(); if commands.as_list().is_empty() { None } else { Some(MsgOut::Commands(commands.clone())) } } fn complete_main(&mut self) -> Vec { let mut outbox = self.flush_buffers(); // TODO: if we have a panic, actually add the panic message to the console let result = self.active_result().clone().unwrap(); self.result = Some(result.clone()); outbox.push(MsgOut::Complete(result)); outbox } fn interpret_active(&mut self) { self.active.as_mut().unwrap().interpret(); } async fn maybe_do_io(&mut self) { if self.last_io + 10.0 < now() { let outbox = self.flush_buffers(); let inbox = do_io(outbox).await; self.fill_buffers(inbox); self.last_io = now(); } } fn fill_input(&mut self, str: String) { let value = Value::string(str); let working = RefCell::new(value); let input = self.buffers.input(); input.swap(&working); } fn fill_buffers(&mut self, inbox: Vec) { for msg in inbox { match msg { MsgIn::Input(str) => self.fill_input(str), MsgIn::Kill => self.kill_signal = true, _ => todo!() } } } pub async fn run(&mut self) { self.activate_main(); loop { self.maybe_do_io().await; if self.kill_signal { let outbox = self.flush_buffers(); do_io(outbox).await; return; } if self.active.is_some() { self.interpret_active(); } if self.active_result().is_some() { if self.active_id().unwrap() == self.main { let outbox = self.complete_main(); do_io(outbox).await; return; } self.kill_active(); } self.next(); } } }