//! ## Queue API //! //! `queue.length`, `queue.clear()`, `queue.delay()` use std::cell::RefCell; use std::collections::VecDeque; use std::rc::Rc; use rhai::{Dynamic, Engine}; use crate::action::{Action, BoardAction}; use crate::script::Registerable; use crate::utils::{LogSink, ObjectId}; /// A single object's output queue. #[derive(Clone)] pub struct ObjQueue(Rc>>); impl ObjQueue { pub fn new() -> Self { Self(Rc::new(RefCell::new(VecDeque::new()))) } /// Add a delay to the tail of the queue, or alter the value already there by the given amount. /// A delay action's value can never be less than 0.0 /// TODO This really ought to be Duration pub fn delay(&mut self, delay: f64) { let mut q = self.0.borrow_mut(); if let Some(Action::Delay(r)) = q.back_mut() { *r = (*r + delay).max(0.0) } else { q.push_back(Action::Delay(delay.max(0.0))) } } /// Add an action to the tail of the queue pub fn act(&mut self, action: Action) { self.0.borrow_mut().push_back(action); } /// promote the most recently enqueued action to the front. pub fn now(&mut self) { let mut q = self.0.borrow_mut(); if let Some(back) = q.pop_back() { q.push_front(back); } } /// Drains object `i`'s output queue into `target`: first advances leading /// `Delay` actions by dt, then moves the run of ready actions into `target` /// until we run out (or hit another delay). Each ready action is tagged with /// `source` so [`GameState`](crate::game::GameState) knows who issued it. pub fn drain(&mut self, source: ObjectId, target: &mut Vec, mut dt: f64) { let mut queue = self.0.borrow_mut(); loop { match queue.front_mut() { None => break, // No more actions, we're done Some(Action::Delay(time)) => { // A delay, decrease it by dt if dt < *time { // Delay is too long, bail out *time -= dt; break; } else { // dt eats the delay, pop it and continue dt -= *time; queue.pop_front(); } } Some(_) => { let action = queue.pop_front().unwrap(); target.push(BoardAction { source, action }); } } } } /// Return whether this queue has an `Action::Delay` in the front pub fn waiting(&mut self) -> bool { matches!(self.0.borrow().front(), Some(Action::Delay(_))) } pub fn clear(&mut self) { self.0.borrow_mut().clear(); } } impl Registerable for ObjQueue { fn register(engine: &mut Engine, log_sink: LogSink) { engine.register_type_with_name::("Queue"); engine.register_get("length", |q: &mut ObjQueue| q.0.borrow().len() as i64); engine.register_fn("clear", ObjQueue::clear); // Appends a [`Action::Delay`] to the back of `queue`, merging with an existing // trailing delay to prevent adjacent delays from accumulating. engine.register_fn("delay", move |q: &mut ObjQueue, secs: Dynamic| { let secs: Result = if secs.is_float() { secs.as_float() } else if secs.is_int() { secs.as_int().map(|i| i as f64) } else { Err("delay() must be an int or float") }; match secs { Ok(secs) => q.delay(secs), Err(msg) => log_sink.error(msg.to_string()) } }); } }