use crate::chunk::Chunk; use crate::value::Value; use crate::vm::{Creature, Panic}; use ran::ran_u8; use std::cell::RefCell; use std::collections::HashMap; 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: Vec<&'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: vec![], 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.id = 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.id = 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.push(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.id; 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.id), } } 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.id, } } 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, } } // pub fn spawn(&mut self, proc: Creature) -> Value { // let id = self.zoo.put(proc); // Value::Keyword(id) // } // pub fn send_msg(&mut self, id: &'static str, msg: Value) { // let mut proc = self.zoo.catch(id); // proc.receive(msg); // self.zoo.release(proc); // } // 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()); // at the moment, active is None, process is released. // zoo should NOT need an id--it has a representation of the current active process // world has an active process for memory reasons // not for state-keeping reasons 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); } // fn old_next(&mut self) { // let id = self // .zoo // .as_ref() // .borrow_mut() // .next(self.active.as_ref().unwrap().id); // println!("next id is {id}"); // let mut active = None; // std::mem::swap(&mut active, &mut self.active); // let mut holding_pen = self.zoo.as_ref().borrow_mut().catch(id); // let mut active = active.unwrap(); // std::mem::swap(&mut active, &mut holding_pen); // println!("now in the holding pen: {}", holding_pen.id); // holding_pen.reset_reductions(); // self.zoo.as_ref().borrow_mut().release(holding_pen); // let mut active = Some(active); // std::mem::swap(&mut active, &mut self.active); // } // pub fn sleep(&mut self, id: &'static str) { // // check if the id is the actually active process // if self.active.id != id { // panic!("attempted to sleep a process from outside that process: active = {}; to sleep: = {id}", self.active.id); // } // self.next(id); // } // pub fn panic(&mut self, id: &'static str, panic: Panic) { // // TODO: devise some way of linking processes (study the BEAM on this) // // check if the id is active // if self.active.id != id { // panic!("attempted to panic from a process from outside that process: active = {}; panicking = {id}; panic = {panic}", self.active.id); // } // // check if the process is `main`, and crash the program if it is // if self.main == id { // self.result = self.active.result.clone(); // } // // kill the process // self.zoo.kill(id); // self.next(id); // } // pub fn complete(&mut self) { // if self.main == self.active.id { // self.result = self.active.result.clone(); // } // self.next(id); // } 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().id } 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 } pub fn run(&mut self) { self.activate_main(); loop { 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 { self.result = self.active_result().clone(); return; } println!("process died: {}", self.active_id()); self.kill_active(); } } println!("getting next process"); self.next(); // self.clean_up(); } } // TODO: // * [ ] Maybe I need to write this from the bottom up? // What do processes need to do? // - [ ] send a message to another process // - [ ] tell the world to spawn a new process, get the pid back // - [ ] receive its messages (always until something matches, or sleep if nothing matches) // - [ ] delete a message from the mbx if it's a match (by idx) // - [ ] yield // - [ ] panic // - [ ] complete // Thus the other side of this looks like: // * [x] Spawn a process // * [x] } // Okay, some more thinking // The world and process can't have mutable references to one another // They will each need an Rc> // All the message passing and world/proc communication will happen through there // And ownership goes World -> Process A -> World -> Process B // Both the world and a process will have an endless `loop`. // But I already have three terms: Zoo, Creature, and World // That should be enough indirection? // To solve tomorrow.