106 lines
3.7 KiB
Rust
106 lines
3.7 KiB
Rust
//! ## 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<RefCell<VecDeque<Action>>>);
|
|
|
|
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<BoardAction>, 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::<ObjQueue>("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<f64, &str> = 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())
|
|
}
|
|
});
|
|
}
|
|
} |