use std::collections::{BTreeMap, BTreeSet};
use std::path::Path;
use tear_types::{DefinitionId, InstanceId};
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct InstanceRegistry {
live: BTreeMap<DefinitionId, BTreeSet<InstanceId>>,
owner: BTreeMap<InstanceId, DefinitionId>,
}
impl InstanceRegistry {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn register(&mut self, def: DefinitionId, instance: InstanceId) -> bool {
self.owner.insert(instance, def);
self.live.entry(def).or_default().insert(instance)
}
#[must_use]
pub fn instances_of(&self, def: DefinitionId) -> impl Iterator<Item = InstanceId> + '_ {
self.live.get(&def).into_iter().flatten().copied()
}
#[must_use]
pub fn instance_count(&self, def: DefinitionId) -> usize {
self.live.get(&def).map_or(0, BTreeSet::len)
}
#[must_use]
pub fn first_instance(&self, def: DefinitionId) -> Option<InstanceId> {
self.live.get(&def).and_then(|s| s.iter().next().copied())
}
#[must_use]
pub fn def_for(&self, instance: InstanceId) -> Option<DefinitionId> {
self.owner.get(&instance).copied()
}
#[must_use]
pub fn is_live(&self, instance: InstanceId) -> bool {
self.owner.contains_key(&instance)
}
pub fn reap(&mut self, instance: InstanceId) -> Option<DefinitionId> {
let def = self.owner.remove(&instance)?;
if let Some(set) = self.live.get_mut(&def) {
set.remove(&instance);
if set.is_empty() {
self.live.remove(&def);
}
}
Some(def)
}
#[must_use]
pub fn live_definitions(&self) -> impl Iterator<Item = DefinitionId> + '_ {
self.live.keys().copied()
}
#[must_use]
pub fn total_instances(&self) -> usize {
self.owner.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.owner.is_empty()
}
pub fn from_project_bindings<'a, I>(bindings: I) -> Self
where
I: IntoIterator<Item = (&'a Path, InstanceId)>,
{
let mut reg = Self::new();
for (root, instance) in bindings {
reg.register(DefinitionId::from_project(root), instance);
}
reg
}
}
#[cfg(test)]
mod tests {
use super::*;
use tear_types::SessionId;
#[test]
fn one_definition_holds_many_instances() {
let mut reg = InstanceRegistry::new();
let def = DefinitionId::from_project(Path::new("/code/mado"));
reg.register(def, SessionId(1));
reg.register(def, SessionId(2));
reg.register(def, SessionId(3));
assert_eq!(reg.instance_count(def), 3);
assert_eq!(reg.instances_of(def).collect::<Vec<_>>(), vec![SessionId(1), SessionId(2), SessionId(3)]);
assert_eq!(reg.first_instance(def), Some(SessionId(1)));
}
#[test]
fn reap_prunes_definition_when_last_instance_dies() {
let mut reg = InstanceRegistry::new();
let def = DefinitionId::from_project(Path::new("/x"));
reg.register(def, SessionId(1));
reg.register(def, SessionId(2));
assert_eq!(reg.reap(SessionId(1)), Some(def));
assert_eq!(reg.instance_count(def), 1);
assert!(reg.is_live(SessionId(2)));
assert_eq!(reg.reap(SessionId(2)), Some(def));
assert_eq!(reg.instance_count(def), 0);
assert_eq!(reg.live_definitions().count(), 0);
assert!(reg.is_empty());
}
#[test]
fn def_for_reverses_the_mapping() {
let mut reg = InstanceRegistry::new();
let a = DefinitionId::from_project(Path::new("/a"));
let b = DefinitionId::from_project(Path::new("/b"));
reg.register(a, SessionId(10));
reg.register(b, SessionId(20));
assert_eq!(reg.def_for(SessionId(10)), Some(a));
assert_eq!(reg.def_for(SessionId(20)), Some(b));
assert_eq!(reg.def_for(SessionId(99)), None);
}
#[test]
fn register_is_idempotent() {
let mut reg = InstanceRegistry::new();
let def = DefinitionId::from_project(Path::new("/x"));
assert!(reg.register(def, SessionId(1)));
assert!(!reg.register(def, SessionId(1))); assert_eq!(reg.instance_count(def), 1);
}
#[test]
fn migration_from_one_to_one_bindings_is_lossless() {
let bindings: Vec<(&Path, InstanceId)> = vec![
(Path::new("/a"), SessionId(1)),
(Path::new("/b"), SessionId(2)),
];
let reg = InstanceRegistry::from_project_bindings(bindings);
assert_eq!(reg.def_for(SessionId(1)), Some(DefinitionId::from_project(Path::new("/a"))));
assert_eq!(reg.def_for(SessionId(2)), Some(DefinitionId::from_project(Path::new("/b"))));
assert_eq!(reg.total_instances(), 2);
}
}