Skip to content

Commit

Permalink
stop procs refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
dragazo committed Nov 15, 2023
1 parent 52ddd9d commit 2d5f307
Show file tree
Hide file tree
Showing 5 changed files with 190 additions and 122 deletions.
20 changes: 8 additions & 12 deletions src/bytecode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ pub(crate) enum Relation {
}
#[derive(Clone, Copy, Debug, FromPrimitive)]
#[repr(u8)]
pub(crate) enum StopMode {
All, Process, Function, Others, MyOthers,
pub enum AbortMode {
All, Current, Others, MyOthers,
}
#[derive(Clone, Copy, Debug, FromPrimitive)]
#[repr(u8)]
Expand Down Expand Up @@ -105,7 +105,6 @@ pub(crate) enum UnaryOp {
UnicodeToChar, CharToUnicode,
}

impl From<StopMode> for Instruction<'_> { fn from(mode: StopMode) -> Self { Self::Stop { mode } } }
impl From<Relation> for Instruction<'_> { fn from(relation: Relation) -> Self { Self::Cmp { relation } } }
impl From<BinaryOp> for Instruction<'_> { fn from(op: BinaryOp) -> Self { Self::BinaryOp { op } } }
impl From<UnaryOp> for Instruction<'_> { fn from(op: UnaryOp) -> Self { Self::UnaryOp { op } } }
Expand Down Expand Up @@ -342,9 +341,8 @@ pub(crate) enum Instruction<'a> {
/// If the call stack is empty, this instead terminates the process
/// with the reported value being the (only) value remaining in the value stack.
Return,

/// Stops one of more processes/functions/etc. immediately without running any following code.
Stop { mode: StopMode },
/// Stops one of more processes immediately without running any following code.
Abort { mode: AbortMode },

/// Pushes a new error handler onto the handler stack.
PushHandler { pos: usize, var: &'a str },
Expand Down Expand Up @@ -499,7 +497,7 @@ macro_rules! read_write_u8_type {
}
)*}
}
read_write_u8_type! { PrintStyle, Property, Relation, TimeQuery, BinaryOp, UnaryOp, VariadicOp, BasicType, StopMode }
read_write_u8_type! { PrintStyle, Property, Relation, TimeQuery, BinaryOp, UnaryOp, VariadicOp, BasicType, AbortMode }

/// encodes values as a sequence of bytes of form [1: next][7: bits] in little-endian order.
/// `bytes` can be used to force a specific size (too small will panic), otherwise calculates and uses the smallest possible size.
Expand Down Expand Up @@ -799,8 +797,7 @@ impl<'a> BinaryRead<'a> for Instruction<'a> {
81 => read_prefixed!(Instruction::CallClosure { new_entity: true, } : args),
82 => read_prefixed!(Instruction::ForkClosure {} : args),
83 => read_prefixed!(Instruction::Return),

84 => read_prefixed!(Instruction::Stop {} : mode),
84 => read_prefixed!(Instruction::Abort {} : mode),

85 => read_prefixed!(Instruction::PushHandler {} : pos, var),
86 => read_prefixed!(Instruction::PopHandler),
Expand Down Expand Up @@ -992,8 +989,7 @@ impl BinaryWrite for Instruction<'_> {
Instruction::CallClosure { new_entity: true, args } => append_prefixed!(81: args),
Instruction::ForkClosure { args } => append_prefixed!(82: args),
Instruction::Return => append_prefixed!(83),

Instruction::Stop { mode } => append_prefixed!(84: mode),
Instruction::Abort { mode } => append_prefixed!(84: mode),

Instruction::PushHandler { pos, var } => append_prefixed!(85: move pos, move str var),
Instruction::PopHandler => append_prefixed!(86),
Expand Down Expand Up @@ -1715,7 +1711,7 @@ impl<'a: 'b, 'b> ByteCodeBuilder<'a, 'b> {
ast::StmtKind::ChangePenSize { delta } => self.append_simple_ins(entity, &[delta], Instruction::ChangeProperty { prop: Property::PenSize })?,
ast::StmtKind::NextCostume => self.ins.push(Instruction::NextCostume.into()),
ast::StmtKind::PenClear => self.ins.push(Instruction::ClearDrawings.into()),
ast::StmtKind::Stop { mode: ast::StopMode::ThisScript } => self.ins.push(Instruction::Stop { mode: StopMode::Process }.into()),
ast::StmtKind::Stop { mode: ast::StopMode::ThisScript } => self.ins.push(Instruction::Abort { mode: AbortMode::Current }.into()),
ast::StmtKind::SetCostume { costume } => match costume {
Some(x) => self.append_simple_ins(entity, &[x], Instruction::SetCostume)?,
None => {
Expand Down
69 changes: 35 additions & 34 deletions src/process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,9 +118,12 @@ pub enum ProcessStep<'gc, C: CustomTypes<S>, S: System<C>> {
/// Yielding is needed for executing an entire project's scripts so that they can appear to run simultaneously.
/// If instead you are explicitly only using a single sandboxed process, this can be treated equivalently to [`ProcessStep::Normal`].
Yield,
/// The process has successfully terminated with the given return value, or [`None`] if terminated by an (error-less) abort,
/// such as a stop script command or the death of the process's associated entity.
Terminate { result: Option<Value<'gc, C, S>> },
/// The process has successfully terminated with the given return value.
Terminate { result: Value<'gc, C, S> },
/// Aborts zero or more running processes, possibly including this one.
/// If this process is included in the abort set, this process is guaranteed to already be terminated.
/// For other processes, it is up to the receiver/scheduler to respect this abort request.
Abort { mode: AbortMode },
/// The process has requested to broadcast a message to all entities (if `target` is `None`) or to a specific `target`, which may trigger other code to execute.
Broadcast { msg_type: String, barrier: Option<Barrier>, targets: Option<Vec<Gc<'gc, RefLock<Entity<'gc, C, S>>>>> },
/// The process has requested to create or destroy a new watcher for a variable.
Expand Down Expand Up @@ -282,7 +285,7 @@ impl<'gc, C: CustomTypes<S>, S: System<C>> Process<'gc, C, S> {
}
}

if let Ok(ProcessStep::Terminate { .. }) | Err(_) = &res {
if let Ok(ProcessStep::Terminate { result: _ }) | Ok(ProcessStep::Abort { mode: AbortMode::Current | AbortMode::All }) | Err(_) = &res {
self.pos = usize::MAX;
self.barrier = None;
self.reply_key = None;
Expand Down Expand Up @@ -347,30 +350,6 @@ impl<'gc, C: CustomTypes<S>, S: System<C>> Process<'gc, C, S> {

Ok((closure.pos, locals))
}
fn do_return<'gc, C: CustomTypes<S>, S: System<C>>(return_value: Value<'gc, C, S>, proc: &mut Process<'gc, C, S>) -> Result<ProcessStep<'gc, C, S>, ErrorCause<C, S>> {
let CallStackEntry { called_from, return_to, warp_counter, value_stack_size, handler_stack_size, .. } = proc.call_stack.last().unwrap();

proc.pos = *return_to;
proc.warp_counter = *warp_counter;
proc.value_stack.drain(value_stack_size..);
proc.handler_stack.drain(handler_stack_size..);
debug_assert_eq!(proc.value_stack.len(), *value_stack_size);
debug_assert_eq!(proc.handler_stack.len(), *handler_stack_size);

if proc.call_stack.len() > 1 {
proc.call_stack.pop();
proc.value_stack.push(return_value);
Ok(ProcessStep::Normal)
} else {
debug_assert_eq!(proc.value_stack.len(), 0);
debug_assert_eq!(*called_from, usize::MAX);
debug_assert_eq!(*return_to, usize::MAX);
debug_assert_eq!(*warp_counter, 0);
debug_assert_eq!(*value_stack_size, 0);
debug_assert_eq!(*handler_stack_size, 0);
Ok(ProcessStep::Terminate { result: Some(return_value) })
}
}

let system = self.global_context.borrow().system.clone();
match self.defer.take() {
Expand Down Expand Up @@ -976,14 +955,36 @@ impl<'gc, C: CustomTypes<S>, S: System<C>> Process<'gc, C, S> {
self.pos = aft_pos;
return Ok(ProcessStep::Fork { pos: closure_pos, locals, entity: self.call_stack.last().unwrap().entity });
}
Instruction::Return => return do_return(self.value_stack.pop().unwrap(), self),
Instruction::Stop { mode } => {
self.pos = aft_pos;
Instruction::Return => {
let CallStackEntry { called_from, return_to, warp_counter, value_stack_size, handler_stack_size, .. } = self.call_stack.last().unwrap();
let return_value = self.value_stack.pop().unwrap();

self.pos = *return_to;
self.warp_counter = *warp_counter;
self.value_stack.drain(value_stack_size..);
self.handler_stack.drain(handler_stack_size..);
debug_assert_eq!(self.value_stack.len(), *value_stack_size);
debug_assert_eq!(self.handler_stack.len(), *handler_stack_size);

if self.call_stack.len() > 1 {
self.call_stack.pop();
self.value_stack.push(return_value);
} else {
debug_assert_eq!(self.value_stack.len(), 0);
debug_assert_eq!(*called_from, usize::MAX);
debug_assert_eq!(*return_to, usize::MAX);
debug_assert_eq!(*warp_counter, 0);
debug_assert_eq!(*value_stack_size, 0);
debug_assert_eq!(*handler_stack_size, 0);
return Ok(ProcessStep::Terminate { result: return_value });
}
}
Instruction::Abort { mode } => {
match mode {
StopMode::Process => return Ok(ProcessStep::Terminate { result: None }),
StopMode::Function => return do_return(empty_string().into(), self),
x => unimplemented!("{x:?}"),
AbortMode::Current | AbortMode::All => (),
AbortMode::Others | AbortMode::MyOthers => self.pos = aft_pos,
}
return Ok(ProcessStep::Abort { mode });
}
Instruction::PushHandler { pos, var } => {
self.handler_stack.push(Handler {
Expand Down
30 changes: 29 additions & 1 deletion src/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ pub enum ProjectStep<'gc, C: CustomTypes<S>, S: System<C>> {
/// The project had a running process which terminated successfully.
/// This can be though of as a special case of [`ProjectStep::Normal`],
/// but also returns the result and process so it can be queried for state information if needed.
ProcessTerminated { result: Option<Value<'gc, C, S>>, proc: Process<'gc, C, S> },
ProcessTerminated { result: Value<'gc, C, S>, proc: Process<'gc, C, S> },
/// The project had a running process, which encountered a runtime error.
/// The dead process is returned, which can be queried for diagnostic information.
Error { error: ExecError<C, S>, proc: Process<'gc, C, S> },
Expand Down Expand Up @@ -364,6 +364,34 @@ impl<'gc, C: CustomTypes<S>, S: System<C>> Project<'gc, C, S> {
all_contexts_consumer.do_once(self); // need to consume all contexts after dropping a process
ProjectStep::ProcessTerminated { result, proc }
}
ProcessStep::Abort { mode } => match mode {
AbortMode::Current => {
debug_assert!(!proc.is_running());
self.state.processes.remove(proc_key);
ProjectStep::Normal
}
AbortMode::All => {
debug_assert!(!proc.is_running());
self.state.processes.clear();
self.state.process_queue.clear();
ProjectStep::Normal
}
AbortMode::Others => {
debug_assert!(proc.is_running());
self.state.processes.retain_mut(|k, _| k == proc_key);
debug_assert_eq!(self.state.processes.len(), 1);
self.state.process_queue.clear();
self.state.process_queue.push_front(proc_key); // keep executing the calling process
ProjectStep::Normal
}
AbortMode::MyOthers => {
debug_assert!(proc.is_running());
let entity = proc.get_call_stack().first().unwrap().entity;
self.state.processes.retain_mut(|k, v| k == proc_key || !Gc::ptr_eq(entity, v.get_call_stack().first().unwrap().entity));
self.state.process_queue.push_front(proc_key); // keep executing the calling process
ProjectStep::Normal
}
}
ProcessStep::Idle => unreachable!(),
}
Err(error) => {
Expand Down
42 changes: 42 additions & 0 deletions src/slotmap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,24 @@ impl<K: Key, T> SlotMap<K, T> {

#[cfg(test)] assert!(self.invariant());
}
/// Retains only the values for which the predicate returns true.
/// For all retained values, any existing keys are still valid after this operation.
pub fn retain_mut<F: FnMut(K, &mut T) -> bool>(&mut self, mut f: F) {
#[cfg(test)] assert!(self.invariant());

for (i, slot) in self.slots.iter_mut().enumerate() {
if let Some(value) = &mut slot.value {
if !f(K::new(i, slot.generation), value) {
slot.value = None;
slot.generation += 1;
self.num_values -= 1;
self.empty_slots.push(i);
}
}
}

#[cfg(test)] assert!(self.invariant());
}
/// Get a reference to a value in the map.
pub fn get(&self, key: K) -> Option<&T> {
let slot = self.slots.get(key.get_slot())?;
Expand Down Expand Up @@ -332,4 +350,28 @@ fn test_slotmap() {
assert_eq!(map.remove(k2), Some(13));
assert_eq!(map.remove(k2), None);
assert_eq!(map.remove(k2), None);

map.clear();
let kv1 = map.insert(54);
let kv2 = map.insert(-4);
let kv3 = map.insert(51);
let kv4 = map.insert(-53);
let kv5 = map.insert(52);
let kv6 = map.insert(12);
let kv7 = map.insert(2);
assert_eq!(map.get(kv1).copied(), Some(54));
assert_eq!(map.get(kv2).copied(), Some(-4));
assert_eq!(map.get(kv3).copied(), Some(51));
assert_eq!(map.get(kv4).copied(), Some(-53));
assert_eq!(map.get(kv5).copied(), Some(52));
assert_eq!(map.get(kv6).copied(), Some(12));
assert_eq!(map.get(kv7).copied(), Some(2));
map.retain_mut(|k, v| k == kv6 || *v % 2 != 0);
assert_eq!(map.get(kv1).copied(), None);
assert_eq!(map.get(kv2).copied(), None);
assert_eq!(map.get(kv3).copied(), Some(51));
assert_eq!(map.get(kv4).copied(), Some(-53));
assert_eq!(map.get(kv5).copied(), None);
assert_eq!(map.get(kv6).copied(), Some(12));
assert_eq!(map.get(kv7).copied(), None);
}
Loading

0 comments on commit 2d5f307

Please sign in to comment.