start work on actor model

This commit is contained in:
Scott Richmond 2025-06-26 01:28:33 -04:00
parent 0c17b64fd7
commit b5528ced8f
3 changed files with 217 additions and 15 deletions

View File

@ -8,6 +8,7 @@ const DEBUG_PRELUDE_COMPILE: bool = false;
const DEBUG_PRELUDE_RUN: bool = false; const DEBUG_PRELUDE_RUN: bool = false;
mod base; mod base;
mod world;
mod spans; mod spans;
use crate::spans::Spanned; use crate::spans::Spanned;
@ -34,7 +35,7 @@ mod value;
use value::Value; use value::Value;
mod vm; mod vm;
use vm::Vm; use vm::Creature;
const PRELUDE: &str = include_str!("../assets/test_prelude.ld"); const PRELUDE: &str = include_str!("../assets/test_prelude.ld");
@ -81,7 +82,7 @@ fn prelude() -> HashMap<&'static str, Value> {
compiler.compile(); compiler.compile();
let chunk = compiler.chunk; let chunk = compiler.chunk;
let mut vm = Vm::new(chunk, DEBUG_PRELUDE_RUN); let mut vm = Creature::new(chunk, DEBUG_PRELUDE_RUN);
let prelude = vm.run().clone().unwrap(); let prelude = vm.run().clone().unwrap();
match prelude { match prelude {
Value::Dict(hashmap) => *hashmap, Value::Dict(hashmap) => *hashmap,
@ -143,7 +144,7 @@ pub fn ludus(src: String) -> String {
let vm_chunk = compiler.chunk; let vm_chunk = compiler.chunk;
let mut vm = Vm::new(vm_chunk, DEBUG_SCRIPT_RUN); let mut vm = Creature::new(vm_chunk, DEBUG_SCRIPT_RUN);
let result = vm.run(); let result = vm.run();
let console = postlude.get("console").unwrap(); let console = postlude.get("console").unwrap();

View File

@ -12,14 +12,6 @@ use std::mem::swap;
use std::rc::Rc; use std::rc::Rc;
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
// pub struct Panic {
// pub input: &'static str,
// pub src: &'static str,
// pub msg: String,
// pub span: SimpleSpan,
// pub trace: Vec<Trace>,
// pub extra: String,
// }
pub enum Panic { pub enum Panic {
Str(&'static str), Str(&'static str),
String(String), String(String),
@ -44,6 +36,7 @@ pub struct Trace {
pub src: &'static str, pub src: &'static str,
} }
#[derive(Debug, Clone, PartialEq)]
pub struct CallFrame { pub struct CallFrame {
pub function: Value, pub function: Value,
pub arity: u8, pub arity: u8,
@ -80,7 +73,8 @@ fn combine_bytes(high: u8, low: u8) -> usize {
out as usize out as usize
} }
pub struct Vm { #[derive(Debug, Clone, PartialEq)]
pub struct Creature {
pub stack: Vec<Value>, pub stack: Vec<Value>,
pub call_stack: Vec<CallFrame>, pub call_stack: Vec<CallFrame>,
pub frame: CallFrame, pub frame: CallFrame,
@ -91,10 +85,11 @@ pub struct Vm {
pub result: Option<Result<Value, Panic>>, pub result: Option<Result<Value, Panic>>,
debug: bool, debug: bool,
last_code: usize, last_code: usize,
pub id: &'static str,
} }
impl Vm { impl Creature {
pub fn new(chunk: Chunk, debug: bool) -> Vm { pub fn new(chunk: Chunk, debug: bool) -> Creature {
let lfn = LFn::Defined { let lfn = LFn::Defined {
name: "user script", name: "user script",
doc: None, doc: None,
@ -110,7 +105,7 @@ impl Vm {
ip: 0, ip: 0,
arity: 0, arity: 0,
}; };
Vm { Creature {
stack: vec![], stack: vec![],
call_stack: Vec::with_capacity(64), call_stack: Vec::with_capacity(64),
frame: base_frame, frame: base_frame,
@ -121,6 +116,7 @@ impl Vm {
result: None, result: None,
debug, debug,
last_code: 0, last_code: 0,
id: "",
} }
} }

205
src/world.rs Normal file
View File

@ -0,0 +1,205 @@
use crate::value::Value;
use crate::vm::{Creature, Panic};
use ran::ran_u8;
use std::collections::{HashMap, VecDeque};
const ANIMALS: [&str; 24] = [
"turtle",
"tortoise",
"hare",
"squirrel",
"hawk",
"woodpecker",
"cardinal",
"coyote",
"raccoon",
"rat",
"axolotl",
"cormorant",
"duck",
"orca",
"humbpack",
"tern",
"quokka",
"koala",
"kangaroo",
"zebra",
"hyena",
"giraffe",
"leopard",
"lion",
];
#[derive(Debug, Clone, PartialEq)]
enum Status {
Empty,
Borrowed,
Nested(Creature),
}
#[derive(Debug, Clone, PartialEq)]
struct Zoo {
procs: Vec<Status>,
empty: Vec<usize>,
ids: HashMap<&'static str, usize>,
dead: Vec<&'static str>,
}
impl Zoo {
pub fn new() -> Zoo {
Zoo {
procs: vec![],
empty: vec![],
ids: HashMap::new(),
dead: vec![],
}
}
pub fn put(&mut self, mut proc: Creature) -> &'static str {
if self.empty.is_empty() {
let rand = ran_u8() as usize % 24;
let idx = self.procs.len();
let id = format!("{}_{idx}", ANIMALS[rand]).leak();
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) {
if let Some(idx) = self.ids.get(id) {
self.procs[*idx] = Status::Empty;
self.empty.push(*idx);
self.ids.remove(&id);
self.dead.push(id);
}
}
pub fn catch(&mut self, id: &'static str) -> Creature {
if let Some(idx) = self.ids.get(id) {
let mut proc = Status::Borrowed;
std::mem::swap(&mut proc, &mut self.procs[*idx]);
let Status::Nested(proc) = proc else {
unreachable!("tried to borrow an empty or already-borrowed process");
};
proc
} else {
unreachable!("tried to borrow a non-existent process");
}
}
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);
std::mem::swap(&mut proc, &mut self.procs[*idx]);
} else {
unreachable!("tried to return a process the world doesn't know about");
}
}
}
pub struct World {
procs: Zoo,
mbxes: HashMap<String, VecDeque<Value>>,
active: Creature,
// TODO: we need a lifetime here
main: &'static str,
}
impl World {
pub fn new(proc: Creature) -> World {
let mut creatures = Zoo::new();
let id = creatures.put(proc);
let caught = creatures.catch(id);
World {
procs: creatures,
mbxes: HashMap::new(),
active: caught,
main: id,
}
}
pub fn spawn(&mut self, proc: Creature) -> Value {
let id = self.procs.put(proc);
Value::Keyword(id)
}
pub fn send_msg(&mut self, id: &'static str, msg: Value) {
let mbx = self.mbxes.get_mut(id).unwrap();
mbx.push_back(msg);
}
pub fn sleep(&mut self, id: &'static str) {
// check if the id is the actually active process
// return it to the nursery
// get the next process from the nursery
}
pub fn get_msg(&self, id: &'static str) -> Option<(usize, Value)> {
// check if the id is of the active process
todo!()
}
pub fn match_msg(&mut self, id: &'static str, idx: usize) {
// again, check for activity
// delete the message at idx, which we gave along with the value as the tuple in get_msg
}
pub fn r#yield(&mut self, id: &'static str) {
// check if the id is active
// swap out the currently active process for the next one
}
pub fn panic(&mut self, id: &'static str) {
// TODO: devise some way of linking processes (study the BEAM on this)
// check if the id is active
// check if the process is `main`, and crash the program if it is
// kill the process
// swap out this process for the next one
}
pub fn complete(&mut self, id: &'static str, value: Value) {
// check if the id is active
// check if the process is main
// if it is: stash the value somehow and exit the program cleanly
}
pub fn run(&mut self) -> Result<Value, Panic> {
todo!()
}
// 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<RefCell<PostOffice>>
// 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.