diff --git a/src/cli.rs b/src/cli.rs index 47b9516..1447a12 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -191,7 +191,7 @@ fn run_proj_tty>>(project_name: &str, server: String let update_flag = update_flag.clone(); Some(Rc::new(move |_, _, key, command, proc| match command { Command::Print { style: _, value } => { - let entity = &*proc.current_entity().borrow(); + let entity = &*proc.get_call_stack().last().unwrap().entity.borrow(); if let Some(value) = value { print!("{entity:?} > {value:?}\r\n"); update_flag.set(true); @@ -207,7 +207,7 @@ fn run_proj_tty>>(project_name: &str, server: String let input_queries = input_queries.clone(); Some(Rc::new(move |_, _, key, request, proc| match request { Request::Input { prompt } => { - let entity = &*proc.current_entity().borrow(); + let entity = &*proc.get_call_stack().last().unwrap().entity.borrow(); input_queries.borrow_mut().push_back((format!("{entity:?} {prompt:?} > "), key)); update_flag.set(true); RequestStatus::Handled @@ -301,7 +301,7 @@ fn run_proj_non_tty>>(project_name: &str, server: St request: None, command: Some(Rc::new(move |_, _, key, command, proc| match command { Command::Print { style: _, value } => { - let entity = &*proc.current_entity().borrow(); + let entity = &*proc.get_call_stack().last().unwrap().entity.borrow(); if let Some(value) = value { println!("{entity:?} > {value:?}") } key.complete(Ok(())); CommandStatus::Handled @@ -387,7 +387,7 @@ fn run_server>>(nb_server: String, addr: String, por request: None, command: Some(Rc::new(move |_, _, key, command, proc| match command { Command::Print { style: _, value } => { - let entity = &*proc.current_entity().borrow(); + let entity = &*proc.get_call_stack().last().unwrap().entity.borrow(); if let Some(value) = value { tee_println!(weak_state.upgrade() => "{entity:?} > {value:?}") } key.complete(Ok(())); CommandStatus::Handled diff --git a/src/process.rs b/src/process.rs index 2b50104..51293d2 100644 --- a/src/process.rs +++ b/src/process.rs @@ -83,11 +83,10 @@ impl ErrorSummary { let mut trace = Vec::with_capacity(process.call_stack.len()); for (pos, locals) in iter::zip(process.call_stack[1..].iter().map(|x| x.called_from).chain(iter::once(error.pos)), process.call_stack.iter().map(|x| &x.locals)) { - if let Some(loc) = locations.lookup(pos) { - trace.push(TraceEntry { location: loc.clone(), locals: summarize_symbols(locals) }); + if let Some(location) = locations.lookup(pos) { + trace.push(TraceEntry { location, locals: summarize_symbols(locals) }); } } - debug_assert_eq!(trace.len(), process.call_stack.len()); Self { entity, cause, globals, fields, trace } } @@ -106,6 +105,8 @@ pub struct ExecError, S: System> { } /// Result of stepping through a [`Process`]. +#[derive(Educe)] +#[educe(Debug)] pub enum ProcessStep<'gc, C: CustomTypes, S: System> { /// The process was not running. Idle, @@ -144,15 +145,15 @@ pub enum ProcessStep<'gc, C: CustomTypes, S: System> { /// This contains information about the call origin and local variables defined in the called context. #[derive(Collect)] #[collect(no_drop, bound = "")] -struct CallStackEntry<'gc, C: CustomTypes, S: System> { - #[collect(require_static)] called_from: usize, - #[collect(require_static)] return_to: usize, - entity: Gc<'gc, RefLock>>, - locals: SymbolTable<'gc, C, S>, - - #[collect(require_static)] warp_counter: usize, - #[collect(require_static)] value_stack_size: usize, - #[collect(require_static)] handler_stack_size: usize, +pub struct CallStackEntry<'gc, C: CustomTypes, S: System> { + #[collect(require_static)] pub called_from: usize, + #[collect(require_static)] return_to: usize, + pub entity: Gc<'gc, RefLock>>, + pub locals: SymbolTable<'gc, C, S>, + + #[collect(require_static)] warp_counter: usize, + #[collect(require_static)] value_stack_size: usize, + #[collect(require_static)] handler_stack_size: usize, } struct Handler { @@ -199,7 +200,6 @@ pub struct Process<'gc, C: CustomTypes, S: System> { pub global_context: Gc<'gc, RefLock>>, #[collect(require_static)] pub state: C::ProcessState, #[collect(require_static)] pos: usize, - #[collect(require_static)] running: bool, #[collect(require_static)] barrier: Option, #[collect(require_static)] reply_key: Option, #[collect(require_static)] warp_counter: usize, @@ -217,7 +217,6 @@ impl<'gc, C: CustomTypes, S: System> Process<'gc, C, S> { pub fn new(context: ProcContext<'gc, C, S>) -> Self { Self { global_context: context.global_context, - running: true, barrier: context.barrier, reply_key: context.reply_key, pos: context.start_pos, @@ -244,21 +243,27 @@ impl<'gc, C: CustomTypes, S: System> Process<'gc, C, S> { /// Checks if the process is currently running. /// Note that the process will not run on its own (see [`Process::step`]). pub fn is_running(&self) -> bool { - self.running + self.pos != usize::MAX } - /// Gets the current active entity of this process. - /// Note that this is not necessarily the same entity that was used to construct the process, - /// as blocks such as "tell _ to _" and "ask _ for _" can change the context entity. - pub fn current_entity(&self) -> Gc<'gc, RefLock>> { - self.call_stack.last().unwrap().entity + /// Gets a reference to the current call stack. + /// This is a sequence of every call frame, including the current context entity, local variables in scope, and other hidden state information. + /// Due to the delicate state involved, a mutable option is not supported. + pub fn get_call_stack(&self) -> &[CallStackEntry<'gc, C, S>] { + &self.call_stack } /// Executes a single bytecode instruction. /// The return value can be used to determine what additional effects the script has requested, /// as well as to retrieve the return value or execution error in the event that the process terminates. /// /// The process transitions to the idle state (see [`Process::is_running`]) upon failing with [`Err`] or succeeding with [`ProcessStep::Terminate`]. + /// + /// This function is not re-entrant, so calling it from the mutable handle of, e.g., [`Config`] will likely lead to panics. pub fn step(&mut self, mc: &Mutation<'gc>) -> Result, ExecError> { - let mut res = self.step_impl(mc); + if !self.is_running() { + return Ok(ProcessStep::Idle); + } + + let mut res = self.step_impl(mc).map_err(|cause| ExecError { cause, pos: self.pos }); if let Err(err) = &res { if let Some(Handler { pos, var, warp_counter, call_stack_size, value_stack_size }) = self.handler_stack.last() { self.warp_counter = *warp_counter; @@ -267,9 +272,9 @@ impl<'gc, C: CustomTypes, S: System> Process<'gc, C, S> { debug_assert_eq!(self.call_stack.len(), *call_stack_size); debug_assert_eq!(self.value_stack.len(), *value_stack_size); - let msg = match err { + let msg = match &err.cause { ErrorCause::Custom { msg } => msg.clone(), - _ => format!("{err:?}"), + x => format!("{x:?}"), }; self.call_stack.last_mut().unwrap().locals.define_or_redefine(var, Shared::Unique(Value::String(Rc::new(msg)))); self.pos = *pos; @@ -278,11 +283,13 @@ impl<'gc, C: CustomTypes, S: System> Process<'gc, C, S> { } if let Ok(ProcessStep::Terminate { .. }) | Err(_) = &res { - self.running = false; + self.pos = usize::MAX; self.barrier = None; self.reply_key = None; + self.defer = None; } - res.map_err(|cause| ExecError { cause, pos: self.pos }) + + res } fn step_impl(&mut self, mc: &Mutation<'gc>) -> Result, ErrorCause> { fn process_result<'gc, C: CustomTypes, S: System, T>(result: Result, error_scheme: ErrorScheme, stack: Option<&mut Vec>>, last_ok: Option<&mut Option>>, last_err: Option<&mut Option>>, to_value: fn(T) -> Option>) -> Result<(), ErrorCause> { @@ -471,7 +478,7 @@ impl<'gc, C: CustomTypes, S: System> Process<'gc, C, S> { None => return Err(ErrorCause::UndefinedEntity { name: name.into() }), } Instruction::PushSelf => { - self.value_stack.push(self.current_entity().into()); + self.value_stack.push(self.call_stack.last().unwrap().entity.into()); self.pos = aft_pos; } Instruction::PopValue => { @@ -867,7 +874,7 @@ impl<'gc, C: CustomTypes, S: System> Process<'gc, C, S> { Instruction::Watcher { create, var } => { let watcher = Watcher { - entity: Gc::downgrade(self.current_entity()), + entity: Gc::downgrade(self.call_stack.last().unwrap().entity), name: var.to_owned(), value: Gc::downgrade(lookup_var!(mut var).alias_inner(mc)), }; @@ -905,7 +912,7 @@ impl<'gc, C: CustomTypes, S: System> Process<'gc, C, S> { warp_counter: self.warp_counter, value_stack_size: self.value_stack.len(), handler_stack_size: self.handler_stack.len(), - entity: self.current_entity(), + entity: self.call_stack.last().unwrap().entity, locals, }); self.pos = pos; @@ -925,7 +932,7 @@ impl<'gc, C: CustomTypes, S: System> Process<'gc, C, S> { Instruction::CallClosure { new_entity, args } => { let (closure_pos, locals) = prep_call_closure(mc, &mut self.value_stack, args)?; let entity = match new_entity { - false => self.current_entity(), + false => self.call_stack.last().unwrap().entity, true => self.value_stack.pop().unwrap().as_entity()?, }; @@ -943,7 +950,7 @@ impl<'gc, C: CustomTypes, S: System> Process<'gc, C, S> { Instruction::ForkClosure { args } => { let (closure_pos, locals) = prep_call_closure(mc, &mut self.value_stack, args)?; self.pos = aft_pos; - return Ok(ProcessStep::Fork { pos: closure_pos, locals, entity: self.current_entity() }); + return Ok(ProcessStep::Fork { pos: closure_pos, locals, entity: self.call_stack.last().unwrap().entity }); } Instruction::Return => { let CallStackEntry { called_from, return_to, warp_counter, value_stack_size, handler_stack_size, .. } = self.call_stack.last().unwrap(); @@ -1178,23 +1185,23 @@ impl<'gc, C: CustomTypes, S: System> Process<'gc, C, S> { }); } Instruction::PushCostume => { - let entity = self.current_entity().borrow(); + let entity = self.call_stack.last().unwrap().entity.borrow(); self.value_stack.push(entity.costume.clone().map(|x| Value::Image(x)).unwrap_or_else(|| Value::String(empty_string()))); self.pos = aft_pos; } Instruction::PushCostumeNumber => { - let entity = self.current_entity().borrow(); + let entity = self.call_stack.last().unwrap().entity.borrow(); let res = entity.costume.as_ref().and_then(|x| entity.costume_list.iter().enumerate().find(|c| Rc::ptr_eq(x, &c.1.1))).map(|x| x.0 + 1).unwrap_or(0); self.value_stack.push(Value::Number(Number::new(res as f64)?)); self.pos = aft_pos; } Instruction::PushCostumeList => { - let entity = self.current_entity().borrow(); + let entity = self.call_stack.last().unwrap().entity.borrow(); self.value_stack.push(Value::List(Gc::new(mc, RefLock::new(entity.costume_list.iter().map(|x| Value::Image(x.1.clone())).collect())))); self.pos = aft_pos; } Instruction::SetCostume => { - let mut entity_raw = self.current_entity().borrow_mut(mc); + let mut entity_raw = self.call_stack.last().unwrap().entity.borrow_mut(mc); let entity = &mut *entity_raw; let new_costume = match self.value_stack.pop().unwrap() { @@ -1223,7 +1230,7 @@ impl<'gc, C: CustomTypes, S: System> Process<'gc, C, S> { } } Instruction::NextCostume => { - let mut entity_raw = self.current_entity().borrow_mut(mc); + let mut entity_raw = self.call_stack.last().unwrap().entity.borrow_mut(mc); let entity = &mut *entity_raw; match entity.costume.as_ref().and_then(|x| entity.costume_list.iter().enumerate().find(|c| Rc::ptr_eq(x, &c.1.1))).map(|x| x.0) { diff --git a/src/runtime.rs b/src/runtime.rs index 9caa362..066312d 100644 --- a/src/runtime.rs +++ b/src/runtime.rs @@ -1128,7 +1128,7 @@ impl<'gc, C: CustomTypes, S: System> fmt::Debug for Watcher<'gc, C, S> { /// /// This is effectively equivalent to [`Gc`] except that it performs no dynamic allocation /// for the [`Shared::Unique`] case, which is assumed to be significantly more likely than [`Shared::Aliased`]. -#[derive(Collect)] +#[derive(Collect, Debug)] #[collect(no_drop)] pub enum Shared<'gc, T: 'gc + Collect> { /// A shared resource which has only (this) single unique handle. @@ -1192,7 +1192,7 @@ impl<'a, T> Deref for SharedRef<'a, T> { /// Simple methods are provided to perform value lookups in the table. #[derive(Collect, Educe)] #[collect(no_drop, bound = "")] -#[educe(Default)] +#[educe(Default, Debug)] pub struct SymbolTable<'gc, C: CustomTypes, S: System>(BTreeMap>>); impl<'gc, C: CustomTypes, S: System> Clone for SymbolTable<'gc, C, S> { /// Creates a shallow (non-aliasing) copy of all variables currently stored in this symbol table. @@ -1307,11 +1307,11 @@ pub enum ErrorScheme { /// as well as being stored in a corresponding last-error process-local variable. Soft, /// Emit errors as hard errors. This treats certain classes of typically soft errors as hard errors that - /// must be caught or else terminate the [`Process`](crate::process::Process) (not the entire VM). + /// must be caught or else terminate the [`Process`] (not the entire VM). Hard, } -/// Settings to use for a [`Process`](crate::process::Process). +/// Settings to use for a [`Process`]. #[derive(Clone, Copy)] pub struct Settings { /// The maximum depth of the call stack (default `1024`). diff --git a/src/std_system.rs b/src/std_system.rs index db5901a..adee8cf 100644 --- a/src/std_system.rs +++ b/src/std_system.rs @@ -13,7 +13,7 @@ use alloc::borrow::ToOwned; use alloc::vec::Vec; use alloc::rc::Rc; -use std::time::{Instant, SystemTime, UNIX_EPOCH}; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; use std::sync::{Arc, Mutex}; use std::sync::mpsc::{Sender, Receiver, channel}; use std::thread; @@ -32,7 +32,7 @@ use crate::json::*; use crate::gc::*; use crate::*; -const MESSAGE_REPLY_TIMEOUT_MS: u32 = 1500; +const MESSAGE_REPLY_TIMEOUT: Duration = Duration::from_millis(1500); /// A [`StdSystem`] key type used to await a reply message from an external source. #[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq)] @@ -63,7 +63,7 @@ struct RpcRequest>> { key: RequestKey, } struct ReplyEntry { - timestamp: Instant, + expiry: OffsetDateTime, value: Option, } @@ -404,17 +404,31 @@ impl>> StdSystem { } #[cfg(debug_assertions)] - fn check_entity_borrowing<'gc>(mc: &Mutation<'gc>, entity: &mut Entity<'gc, C, Self>) { - if let Some(original) = entity.original { - Self::check_entity_borrowing(mc, &mut *original.borrow_mut(mc)); + fn check_runtime_borrows<'gc>(mc: &Mutation<'gc>, proc: &mut Process<'gc, C, Self>) { + fn check_symbols<'gc, C: CustomTypes>>(mc: &Mutation<'gc>, symbols: &mut SymbolTable<'gc, C, StdSystem>) { + for symbol in symbols { + match &*symbol.1.get() { + Value::Bool(_) | Value::Number(_) | Value::String(_) | Value::Audio(_) | Value::Image(_) | Value::Native(_) => (), + Value::List(x) => { x.borrow_mut(mc); } + Value::Closure(x) => { x.borrow_mut(mc); } + Value::Entity(x) => { x.borrow_mut(mc); } + } + } + } + fn check_entity<'gc, C: CustomTypes>>(mc: &Mutation<'gc>, entity: &mut Entity<'gc, C, StdSystem>) { + check_symbols(mc, &mut entity.fields); + if let Some(original) = entity.original { + check_entity(mc, &mut *original.borrow_mut(mc)); + } + } + + let mut global_context = proc.global_context.borrow_mut(mc); + check_symbols(mc, &mut global_context.globals); + for entry in proc.get_call_stack() { + check_entity(mc, &mut entry.entity.borrow_mut(mc)); } - } - #[cfg(debug_assertions)] - fn check_proc_borrowing<'gc>(mc: &Mutation<'gc>, proc: &mut Process<'gc, C, Self>) { - Self::check_entity_borrowing(mc, &mut *proc.current_entity().borrow_mut(mc)); - let global_context = proc.global_context.borrow_mut(mc); for entity in global_context.entities.iter() { - Self::check_entity_borrowing(mc, &mut *entity.1.borrow_mut(mc)); + check_entity(mc, &mut *entity.1.borrow_mut(mc)); } } } @@ -435,7 +449,7 @@ impl>> System for StdSystem { fn perform_request<'gc>(&self, mc: &Mutation<'gc>, request: Request<'gc, C, Self>, proc: &mut Process<'gc, C, Self>) -> Result> { #[cfg(debug_assertions)] - Self::check_proc_borrowing(mc, proc); + Self::check_runtime_borrows(mc, proc); Ok(match self.config.request.as_ref() { Some(handler) => { @@ -450,7 +464,7 @@ impl>> System for StdSystem { } fn poll_request<'gc>(&self, mc: &Mutation<'gc>, key: &Self::RequestKey, proc: &mut Process<'gc, C, Self>) -> Result, String>>, ErrorCause> { #[cfg(debug_assertions)] - Self::check_proc_borrowing(mc, proc); + Self::check_runtime_borrows(mc, proc); Ok(match key.poll() { AsyncResult::Completed(Ok(x)) => AsyncResult::Completed(Ok(C::from_intermediate(mc, x)?)), @@ -462,7 +476,7 @@ impl>> System for StdSystem { fn perform_command<'gc>(&self, mc: &Mutation<'gc>, command: Command<'gc, '_, C, Self>, proc: &mut Process<'gc, C, Self>) -> Result> { #[cfg(debug_assertions)] - Self::check_proc_borrowing(mc, proc); + Self::check_runtime_borrows(mc, proc); Ok(match self.config.command.as_ref() { Some(handler) => { @@ -477,7 +491,7 @@ impl>> System for StdSystem { } fn poll_command<'gc>(&self, mc: &Mutation<'gc>, key: &Self::CommandKey, proc: &mut Process<'gc, C, Self>) -> Result>, ErrorCause> { #[cfg(debug_assertions)] - Self::check_proc_borrowing(mc, proc); + Self::check_runtime_borrows(mc, proc); Ok(key.poll()) } @@ -487,7 +501,8 @@ impl>> System for StdSystem { false => (OutgoingMessage::Normal { msg_type, values, targets }, None), true => { let reply_key = ExternReplyKey { request_id: Uuid::new_v4().to_string() }; - self.message_replies.lock().unwrap().insert(reply_key.clone(), ReplyEntry { timestamp: Instant::now(), value: None }); + let expiry = self.clock.read(Precision::Medium) + MESSAGE_REPLY_TIMEOUT; + self.message_replies.lock().unwrap().insert(reply_key.clone(), ReplyEntry { expiry, value: None }); (OutgoingMessage::Blocking { msg_type, values, targets, reply_key: reply_key.clone() }, Some(reply_key)) } }; @@ -500,7 +515,7 @@ impl>> System for StdSystem { if entry.value.is_some() { return AsyncResult::Completed(message_replies.remove(key).unwrap().value); } - if entry.timestamp.elapsed().as_millis() as u32 >= MESSAGE_REPLY_TIMEOUT_MS { + if self.clock.read(Precision::Low) > entry.expiry { message_replies.remove(key).unwrap(); return AsyncResult::Completed(None); } diff --git a/src/test/mod.rs b/src/test/mod.rs index b8dd098..80b8041 100644 --- a/src/test/mod.rs +++ b/src/test/mod.rs @@ -47,14 +47,14 @@ impl From<&Entity<'_, C, StdSystem>> for ProcessState { fn default_properties_config() -> Config> { Config { request: Some(Rc::new(|_, _, key, request, proc| { - let entity = proc.current_entity().borrow(); + let entity = proc.get_call_stack().last().unwrap().entity.borrow(); match request { Request::Property { prop } => entity.state.props.perform_get_property(key, prop), _ => RequestStatus::UseDefault { key, request }, } })), command: Some(Rc::new(|_, mc, key, command, proc| { - let mut entity = proc.current_entity().borrow_mut(mc); + let mut entity = proc.get_call_stack().last().unwrap().entity.borrow_mut(mc); match command { Command::SetProperty { prop, value } => entity.state.props.perform_set_property(key, prop, value), Command::ChangeProperty { prop, delta } => entity.state.props.perform_change_property(key, prop, delta), diff --git a/src/test/process.rs b/src/test/process.rs index bfca0d5..a29b8e8 100644 --- a/src/test/process.rs +++ b/src/test/process.rs @@ -13,7 +13,6 @@ use crate::json::*; use crate::real_time::*; use crate::bytecode::*; use crate::runtime::*; -use crate::process::*; use crate::std_system::*; use super::*; @@ -51,6 +50,19 @@ fn run_till_term(env: &mut EnvArena, and_then: F) where F: for<'gc> FnOnce(&M let mut proc = env.proc.borrow_mut(mc); assert!(proc.is_running()); + fn assert_done<'gc>(mc: &Mutation<'gc>, proc: &mut Process<'gc, C, StdSystem>) { + assert!(!proc.is_running()); + assert_ne!(proc.get_call_stack().len(), 0); + for _ in 0..16 { + match proc.step(mc) { + Ok(ProcessStep::Idle) => (), + x => panic!("{x:?}"), + } + assert!(!proc.is_running()); + assert_ne!(proc.get_call_stack().len(), 0); + } + } + let mut yields = 0; let ret = loop { match proc.step(mc) { @@ -64,13 +76,14 @@ fn run_till_term(env: &mut EnvArena, and_then: F) where F: for<'gc> FnOnce(&M Ok(ProcessStep::Fork { .. }) => panic!("proc tests should not fork"), Ok(ProcessStep::Pause) => panic!("proc tests should not pause"), Err(e) => { + assert_done(mc, &mut *proc); drop(proc); // so handler can borrow the proc if needed return and_then(mc, env, Err(e)); } } }; - assert!(!proc.is_running()); + assert_done(mc, &mut *proc); drop(proc); // so handler can borrow the proc if needed and_then(mc, env, Ok((ret, yields))); });