use std::collections::{HashMap, HashSet};
use actionqueue_core::actor::ActorRegistration;
use actionqueue_core::ids::{ActorId, TenantId};
use tracing;
struct ActorEntry {
registration: ActorRegistration,
active: bool,
}
#[derive(Default)]
pub struct ActorRegistry {
actors: HashMap<ActorId, ActorEntry>,
actors_by_tenant: HashMap<TenantId, HashSet<ActorId>>,
}
impl ActorRegistry {
pub fn new() -> Self {
Self::default()
}
pub fn register(&mut self, registration: ActorRegistration) {
let actor_id = registration.actor_id();
tracing::debug!(%actor_id, "actor registered");
if let Some(tenant_id) = registration.tenant_id() {
self.actors_by_tenant.entry(tenant_id).or_default().insert(actor_id);
}
self.actors.insert(actor_id, ActorEntry { registration, active: true });
}
pub fn deregister(&mut self, actor_id: ActorId) {
tracing::debug!(%actor_id, "actor deregistered");
if let Some(entry) = self.actors.get_mut(&actor_id) {
entry.active = false;
}
}
pub fn is_active(&self, actor_id: ActorId) -> bool {
self.actors.get(&actor_id).is_some_and(|e| e.active)
}
pub fn get(&self, actor_id: ActorId) -> Option<&ActorRegistration> {
self.actors.get(&actor_id).map(|e| &e.registration)
}
pub fn identity(&self, actor_id: ActorId) -> Option<&str> {
self.actors.get(&actor_id).map(|e| e.registration.identity())
}
pub fn active_actors_for_tenant(&self, tenant_id: TenantId) -> Vec<ActorId> {
let Some(ids) = self.actors_by_tenant.get(&tenant_id) else {
return Vec::new();
};
ids.iter().copied().filter(|&id| self.is_active(id)).collect()
}
pub fn all_actor_ids(&self) -> impl Iterator<Item = ActorId> + '_ {
self.actors.keys().copied()
}
}
#[cfg(test)]
mod tests {
use actionqueue_core::actor::{ActorCapabilities, ActorRegistration};
use actionqueue_core::ids::{ActorId, TenantId};
use super::ActorRegistry;
fn make_registration(actor_id: ActorId) -> ActorRegistration {
let caps = ActorCapabilities::new(vec!["compute".to_string()]).unwrap();
ActorRegistration::new(actor_id, "test-actor", caps, 30)
}
#[test]
fn register_and_is_active() {
let mut registry = ActorRegistry::new();
let id = ActorId::new();
registry.register(make_registration(id));
assert!(registry.is_active(id));
assert!(registry.get(id).is_some());
}
#[test]
fn deregister_marks_inactive() {
let mut registry = ActorRegistry::new();
let id = ActorId::new();
registry.register(make_registration(id));
registry.deregister(id);
assert!(!registry.is_active(id));
assert!(registry.get(id).is_some());
}
#[test]
fn active_actors_for_tenant_filters_correctly() {
let mut registry = ActorRegistry::new();
let tenant = TenantId::new();
let active_id = ActorId::new();
let deregistered_id = ActorId::new();
let other_tenant_id = ActorId::new();
let caps = ActorCapabilities::new(vec!["c".to_string()]).unwrap();
registry
.register(ActorRegistration::new(active_id, "a", caps.clone(), 30).with_tenant(tenant));
registry.register(
ActorRegistration::new(deregistered_id, "b", caps.clone(), 30).with_tenant(tenant),
);
registry.register(ActorRegistration::new(other_tenant_id, "c", caps, 30));
registry.deregister(deregistered_id);
let active = registry.active_actors_for_tenant(tenant);
assert_eq!(active.len(), 1);
assert!(active.contains(&active_id));
}
#[test]
fn identity_returns_lease_owner_string() {
let mut registry = ActorRegistry::new();
let id = ActorId::new();
let caps = ActorCapabilities::new(vec!["c".to_string()]).unwrap();
registry.register(ActorRegistration::new(id, "caelum-vessel-1", caps, 30));
assert_eq!(registry.identity(id), Some("caelum-vessel-1"));
}
}