Skip to content

Commit

Permalink
Add tests and update docs
Browse files Browse the repository at this point in the history
  • Loading branch information
jasonasher committed Aug 28, 2024
1 parent 0fc7308 commit 3b47060
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 13 deletions.
35 changes: 34 additions & 1 deletion src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,8 @@ impl Context {
}
}

/// Add a plan to the future event list at the specified time
/// Add a plan to the future event list at the specified time with normal
/// priority
///
/// Returns an `Id` for the newly-added plan that can be used to cancel it
/// if needed.
Expand Down Expand Up @@ -305,6 +306,21 @@ mod tests {
})
}

fn add_plan_with_priority(
context: &mut Context,
time: f64,
value: u32,
priority: PlanPriority,
) -> Id {
context.add_plan_with_priority(
time,
move |context| {
context.get_data_container_mut(ComponentA).push(value);
},
priority,
)
}

#[test]
#[should_panic(expected = "Time is invalid")]
fn negative_plan_time() {
Expand Down Expand Up @@ -443,6 +459,23 @@ mod tests {
assert_eq!(*context.get_data_container_mut(ComponentA), vec![1, 2]);
}

#[test]
fn plans_at_same_time_follow_priority() {
let mut context = Context::new();
add_plan(&mut context, 1.0, 1);
add_plan_with_priority(&mut context, 1.0, 5, PlanPriority::Last);
add_plan_with_priority(&mut context, 1.0, 3, PlanPriority::First);
add_plan(&mut context, 1.0, 2);
add_plan_with_priority(&mut context, 1.0, 6, PlanPriority::Last);
add_plan_with_priority(&mut context, 1.0, 4, PlanPriority::First);
context.execute();
assert_eq!(context.get_current_time(), 1.0);
assert_eq!(
*context.get_data_container_mut(ComponentA),
vec![3, 4, 1, 2, 5, 6]
);
}

#[derive(Copy, Clone)]
struct Event {
pub data: usize,
Expand Down
47 changes: 35 additions & 12 deletions src/plan.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
//! A priority queue that stores arbitrary data sorted by time
//!
//! Defines a `Queue<T>` that is intended to store a queue of items of type T,
//! sorted by `f64` time, called 'plans'. This queue has methods for adding
//! plans, cancelling plans, and retrieving the earliest plan in the queue.
//! Adding a plan is *O*(log(*n*)) while cancellation and retrieval are *O*(1).
//! Defines a `Queue<T, Q>` that is intended to store a queue of items of type
//! T - sorted by `f64` time and definable priority `Q` - called 'plans'.
//! This queue has methods for adding plans, cancelling plans, and retrieving
//! the earliest plan in the queue. Adding a plan is *O*(log(*n*)) while
//! cancellation and retrieval are *O*(1).
//!
//! This queue is used by `Context` to store future events where some callback
//! closure `FnOnce(&mut Context)` will be executed at a given point in time.
Expand All @@ -16,12 +17,15 @@ use std::{
/// A priority queue that stores arbitrary data sorted by time
///
/// Items of type `T` are stored in order by `f64` time and called `Plan<T>`.
/// Plans can have priorities given by some specified orderable type `Q`.
/// When plans are created they are sequentially assigned an `Id` that is a
/// wrapped `u64`. If two plans are scheduled for the same time the plan that is
/// scheduled first (i.e., that has the lowest id) is placed earlier.
/// wrapped `u64`. If two plans are scheduled for the same time then the plan
/// with the lowest priority is placed earlier. If two plans have the same time
/// and priority then the plan that is scheduled first (i.e., that has the
/// lowest id) is placed earlier.
///
/// The pair of time and plan id are stored in a binary heap of `Entry` objects.
/// The data payload of the event is stored in a hash map by plan id.
/// The time, plan id, and priority are stored in a binary heap of `Entry<P>`
/// objects. The data payload of the event is stored in a hash map by plan id.
/// Plan cancellation occurs by removing the corresponding entry from the data
/// hash map.
pub struct Queue<T, P: Eq + PartialEq + Ord> {
Expand Down Expand Up @@ -96,9 +100,10 @@ impl<T, P: Eq + PartialEq + Ord> Default for Queue<T, P> {
}
}

/// A time and id pair used to order plans in the `Queue<T>`
/// A time, id, and priority object used to order plans in the `Queue<T>`
///
/// `Entry` objects are sorted in increasing order of time and then plan id
/// `Entry` objects are sorted in increasing order of time, priority and then
/// plan id
#[derive(PartialEq, Debug)]
struct Entry<P: Eq + PartialEq + Ord> {
time: f64,
Expand All @@ -114,7 +119,8 @@ impl<P: Eq + PartialEq + Ord> PartialOrd for Entry<P> {
}
}

/// Entry objects are ordered in increasing order by time and then plan id
/// Entry objects are ordered in increasing order by time, priority, and then
/// plan id
impl<P: Eq + PartialEq + Ord> Ord for Entry<P> {
fn cmp(&self, other: &Self) -> Ordering {
let time_ordering = self.time.partial_cmp(&other.time).unwrap().reverse();
Expand Down Expand Up @@ -181,7 +187,7 @@ mod tests {
}

#[test]
fn add_plans_at_same_time() {
fn add_plans_at_same_time_with_same_priority() {
let mut plan_queue = Queue::new();
plan_queue.add_plan(1.0, 1, ());
plan_queue.add_plan(1.0, 2, ());
Expand All @@ -197,6 +203,23 @@ mod tests {
assert!(plan_queue.get_next_plan().is_none());
}

#[test]
fn add_plans_at_same_time_with_different_priority() {
let mut plan_queue = Queue::new();
plan_queue.add_plan(1.0, 1, 1);
plan_queue.add_plan(1.0, 2, 0);

let next_plan = plan_queue.get_next_plan().unwrap();
assert_eq!(next_plan.time, 1.0);
assert_eq!(next_plan.data, 2);

let next_plan = plan_queue.get_next_plan().unwrap();
assert_eq!(next_plan.time, 1.0);
assert_eq!(next_plan.data, 1);

assert!(plan_queue.get_next_plan().is_none());
}

#[test]
fn add_and_cancel_plans() {
let mut plan_queue = Queue::new();
Expand Down

0 comments on commit 3b47060

Please sign in to comment.