Centralize API for working set to a single struct
Rather than allow addressing tasks either by working set ID or uuid, with attendant performance issues, this moves the API for the working set to a single struct that just serves as a 1-1 mapping of indexes to UUIDs. It's up to the caller to use this information.
This commit is contained in:
132
taskchampion/src/workingset.rs
Normal file
132
taskchampion/src/workingset.rs
Normal file
@@ -0,0 +1,132 @@
|
||||
use std::collections::HashMap;
|
||||
use uuid::Uuid;
|
||||
|
||||
/// A WorkingSet represents a snapshot of the working set from a replica.
|
||||
///
|
||||
/// A replica's working set is a mapping from small integers to task uuids for all pending tasks.
|
||||
/// The small integers are meant to be stable, easily-typed identifiers for users to interact with
|
||||
/// important tasks.
|
||||
///
|
||||
/// IMPORTANT: the content of the working set may change at any time that a DB transaction is not
|
||||
/// in progress, and the data in this type will not be updated automatically. It is up to the
|
||||
/// caller to decide how long to keep this value, and how much to trust the accuracy of its
|
||||
/// contents. In practice, the answers are usually "a few milliseconds" and treating unexpected
|
||||
/// results as non-fatal.
|
||||
pub struct WorkingSet {
|
||||
by_index: Vec<Option<Uuid>>,
|
||||
by_uuid: HashMap<Uuid, usize>,
|
||||
}
|
||||
|
||||
impl WorkingSet {
|
||||
/// Create a new WorkingSet. Typically this is acquired via `replica.working_set()`
|
||||
pub(crate) fn new(by_index: Vec<Option<Uuid>>) -> Self {
|
||||
let mut by_uuid = HashMap::new();
|
||||
for (index, uuid) in by_index.iter().enumerate() {
|
||||
if let Some(uuid) = uuid {
|
||||
by_uuid.insert(*uuid, index);
|
||||
}
|
||||
}
|
||||
Self { by_index, by_uuid }
|
||||
}
|
||||
|
||||
/// Get the "length" of the working set: the total number of uuids in the set.
|
||||
pub fn len(&self) -> usize {
|
||||
self.by_index.iter().filter(|e| e.is_some()).count()
|
||||
}
|
||||
|
||||
/// True if the length is zero
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.by_index.iter().all(|e| e.is_none())
|
||||
}
|
||||
|
||||
/// Get the uuid with the given index, if any exists.
|
||||
pub fn by_index(&self, index: usize) -> Option<Uuid> {
|
||||
if let Some(Some(uuid)) = self.by_index.get(index) {
|
||||
Some(*uuid)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the index for the given uuid, if any
|
||||
pub fn by_uuid(&self, uuid: Uuid) -> Option<usize> {
|
||||
self.by_uuid.get(&uuid).copied()
|
||||
}
|
||||
|
||||
/// Iterate over pairs (index, uuid), in order by index.
|
||||
pub fn iter(&self) -> impl Iterator<Item = (usize, Uuid)> + '_ {
|
||||
self.by_index
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(index, uuid)| {
|
||||
if let Some(uuid) = uuid {
|
||||
Some((index, *uuid))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
fn make() -> (Uuid, Uuid, WorkingSet) {
|
||||
let uuid1 = Uuid::new_v4();
|
||||
let uuid2 = Uuid::new_v4();
|
||||
(
|
||||
uuid1,
|
||||
uuid2,
|
||||
WorkingSet::new(vec![None, Some(uuid1), None, Some(uuid2), None]),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_new() {
|
||||
let (_, uuid2, ws) = make();
|
||||
assert_eq!(ws.by_index[3], Some(uuid2));
|
||||
assert_eq!(ws.by_uuid.get(&uuid2), Some(&3));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_len_and_is_empty() {
|
||||
let (_, _, ws) = make();
|
||||
assert_eq!(ws.len(), 2);
|
||||
assert_eq!(ws.is_empty(), false);
|
||||
|
||||
let ws = WorkingSet::new(vec![]);
|
||||
assert_eq!(ws.len(), 0);
|
||||
assert_eq!(ws.is_empty(), true);
|
||||
|
||||
let ws = WorkingSet::new(vec![None, None, None]);
|
||||
assert_eq!(ws.len(), 0);
|
||||
assert_eq!(ws.is_empty(), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_by_index() {
|
||||
let (uuid1, uuid2, ws) = make();
|
||||
assert_eq!(ws.by_index(0), None);
|
||||
assert_eq!(ws.by_index(1), Some(uuid1));
|
||||
assert_eq!(ws.by_index(2), None);
|
||||
assert_eq!(ws.by_index(3), Some(uuid2));
|
||||
assert_eq!(ws.by_index(4), None);
|
||||
assert_eq!(ws.by_index(100), None); // past the end of the vector
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_by_uuid() {
|
||||
let (uuid1, uuid2, ws) = make();
|
||||
let nosuch = Uuid::new_v4();
|
||||
assert_eq!(ws.by_uuid(uuid1), Some(1));
|
||||
assert_eq!(ws.by_uuid(uuid2), Some(3));
|
||||
assert_eq!(ws.by_uuid(nosuch), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_iter() {
|
||||
let (uuid1, uuid2, ws) = make();
|
||||
assert_eq!(ws.iter().collect::<Vec<_>>(), vec![(1, uuid1), (3, uuid2),]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user