use crate::chunk::Chunk; use crate::value::Value; use crate::vm::{Creature, Panic}; use crate::io::{MsgOut, MsgIn, do_io}; use ran::ran_u8; use std::cell::RefCell; use std::collections::{HashMap, HashSet}; use std::mem::swap; use std::rc::Rc; use std::time::{Duration, Instant}; const ANIMALS: [&str; 24] = [ "tortoise", "hare", "squirrel", "hawk", "woodpecker", "cardinal", "coyote", "raccoon", "rat", "axolotl", "cormorant", "duck", "orca", "humbpack", "tern", "quokka", "koala", "kangaroo", "zebra", "hyena", "giraffe", "leopard", "lion", "hippopotamus", ]; #[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, (Instant, Duration)>, 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 = ran_u8() as usize % 24; let idx = self.procs.len(); format!("{}_{idx}", ANIMALS[rand]) } 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 = ran_u8() as usize % 24; 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: usize) { self.sleeping .insert(id, (Instant::now(), Duration::from_millis(ms as u64))); } 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(|_, (instant, duration)| instant.elapsed() < *duration); 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]); } // Removed because, well, we shouldn't have creatures we don't know about // And since zoo.next now cleans (and thus kills) before the world releases its active process // We'll die if we execute this check // else { // unreachable!("tried to return a process the world doesn't know about"); // } } 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.active_idx = (self.active_idx + 1) % self.procs.len(); while !self.is_available() { self.clean_up(); self.active_idx = (self.active_idx + 1) % self.procs.len(); println!( "testing process availability: {}", self.procs[self.active_idx] ); } 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 World { zoo: Rc>, active: Option, main: &'static str, pub result: Option>, } impl World { pub fn new(chunk: Chunk, debug: bool) -> World { let zoo = Rc::new(RefCell::new(Zoo::new())); let main = Creature::new(chunk, zoo.clone(), debug); let id = zoo.as_ref().borrow_mut().put(main); World { zoo, active: None, main: id, result: None, } } fn next(&mut self) { let mut active = None; swap(&mut active, &mut self.active); let mut zoo = self.zoo.as_ref().borrow_mut(); zoo.release(active.unwrap()); let new_active_id = zoo.next(); 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); } pub fn activate_main(&mut self) { let main = self.zoo.as_ref().borrow_mut().catch(self.main); self.active = Some(main); } pub fn active_id(&mut self) -> &'static str { self.active.as_ref().unwrap().pid } pub fn kill_active(&mut self) { let id = self.active_id(); self.zoo.as_ref().borrow_mut().kill(id); } pub fn active_result(&mut self) -> &Option> { &self.active.as_ref().unwrap().result } // TODO: add memory io places to this signature // * console // * input // * commands // * slurp pub async fn run( &mut self, console: Value, // input: Value, // commands: Value, // slurp_out: Value, // slurp_in: Value, ) { self.activate_main(); // let Value::Box(input) = input else {unreachable!()}; // let Value::Box(commands) = commands else {unreachable!()}; // let Value::Box(slurp) = slurp else { // unreachable!()}; let mut last_io = Instant::now(); let mut kill_signal = false; loop { if kill_signal { // TODO: send a last message to the console println!("received KILL signal"); return; } println!( "entering world loop; active process is {}", self.active_id() ); self.active.as_mut().unwrap().interpret(); println!("yielded from {}", self.active_id()); match self.active_result() { None => (), Some(_) => { if self.active_id() == self.main { let result = self.active_result().clone().unwrap(); self.result = Some(result.clone()); //TODO: capture any remaining console or command values do_io(vec![MsgOut::Complete(result)]); return; } println!( "process {} died with {:?}", self.active_id(), self.active_result().clone() ); self.kill_active(); } } println!("getting next process"); self.next(); // TODO:: if enough time has elapsed (how much?) run i/o // 10 ms is 100hz, so that's a nice refresh rate if Instant::now().duration_since(last_io) > Duration::from_millis(10) { // gather io // compile it into messages // serialize it let mut outbox = vec![]; if let Some(console) = flush_console(&console) { outbox.push(console); }; // TODO: slurp // TODO: commands // send it // await the response let inbox = do_io(outbox).await; // unpack the response into messages for msg in inbox { match msg { MsgIn::Kill => kill_signal = true, _ => todo!() } } // update last_io = Instant::now(); } } } } fn flush_console(console: &Value) -> Option { let console = console.as_box(); 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())) }