use std::any::{Any, TypeId};
use std::collections::HashMap;
use std::sync::Arc;
pub struct Container {
services: HashMap<TypeId, Box<dyn Any + Send + Sync>>,
named: HashMap<(TypeId, String), Box<dyn Any + Send + Sync>>,
hooks: Vec<Hook>,
started: Arc<std::sync::atomic::AtomicUsize>,
running: Arc<std::sync::atomic::AtomicBool>,
}
type StartHook = Box<dyn Fn(&Container) -> Result<(), String> + Send + Sync>;
type StopHook = Box<dyn Fn(&Container) + Send + Sync>;
struct Hook {
name: String,
start: Option<StartHook>,
stop: Option<StopHook>,
}
pub trait Lifecycle: Send + Sync {
fn start(&self) -> Result<(), String> {
Ok(())
}
fn stop(&self) {}
}
impl Default for Container {
fn default() -> Self {
Self::new()
}
}
impl Container {
pub fn new() -> Self {
Container {
services: HashMap::new(),
named: HashMap::new(),
hooks: Vec::new(),
started: Arc::new(std::sync::atomic::AtomicUsize::new(0)),
running: Arc::new(std::sync::atomic::AtomicBool::new(false)),
}
}
pub fn register<T: Send + Sync + 'static>(&mut self, service: T) -> &mut Self {
self.services
.insert(TypeId::of::<T>(), Box::new(Arc::new(service)));
self
}
pub fn provide<T>(&mut self, service: Arc<T>) -> &mut Self
where
T: ?Sized + Send + Sync + 'static,
{
let boxed: Box<dyn Any + Send + Sync> = Box::new(service);
self.services.insert(TypeId::of::<T>(), boxed);
self
}
pub fn register_named<T: Send + Sync + 'static>(
&mut self,
name: impl Into<String>,
service: T,
) -> &mut Self {
self.named.insert(
(TypeId::of::<T>(), name.into()),
Box::new(Arc::new(service)),
);
self
}
pub fn provide_named<T>(
&mut self,
name: impl Into<String>,
service: Arc<T>,
) -> &mut Self
where
T: ?Sized + Send + Sync + 'static,
{
let boxed: Box<dyn Any + Send + Sync> = Box::new(service);
self.named.insert((TypeId::of::<T>(), name.into()), boxed);
self
}
pub fn get<T>(&self) -> Option<Arc<T>>
where
T: ?Sized + 'static,
Arc<T>: Clone,
{
self.services
.get(&TypeId::of::<T>())
.and_then(|b| b.downcast_ref::<Arc<T>>())
.cloned()
}
pub fn get_named<T>(&self, name: &str) -> Option<Arc<T>>
where
T: ?Sized + 'static,
Arc<T>: Clone,
{
self.named
.get(&(TypeId::of::<T>(), name.to_string()))
.and_then(|b| b.downcast_ref::<Arc<T>>())
.cloned()
}
pub fn contains<T: ?Sized + 'static>(&self) -> bool {
self.services.contains_key(&TypeId::of::<T>())
}
pub fn contains_named<T: ?Sized + 'static>(&self, name: &str) -> bool {
self.named
.contains_key(&(TypeId::of::<T>(), name.to_string()))
}
pub fn len(&self) -> usize {
self.services.len()
}
pub fn is_empty(&self) -> bool {
self.services.is_empty()
}
pub fn on_start(&mut self, name: &str, hook: impl Fn(&Container) -> Result<(), String> + Send + Sync + 'static) -> &mut Self {
self.hooks.push(Hook { name: name.to_string(), start: Some(Box::new(hook)), stop: None });
self
}
pub fn on_stop(&mut self, name: &str, hook: impl Fn(&Container) + Send + Sync + 'static) -> &mut Self {
self.hooks.push(Hook { name: name.to_string(), start: None, stop: Some(Box::new(hook)) });
self
}
pub fn register_managed<T: Lifecycle + 'static>(&mut self, service: T) -> &mut Self {
self.register(service);
let name = std::any::type_name::<T>().to_string();
self.hooks.push(Hook {
name,
start: Some(Box::new(|c: &Container| c.get::<T>().map_or(Ok(()), |s| s.start()))),
stop: Some(Box::new(|c: &Container| {
if let Some(s) = c.get::<T>() {
s.stop();
}
})),
});
self
}
pub fn start(&self) -> Result<(), String> {
use std::sync::atomic::Ordering;
if self.running.load(Ordering::SeqCst) {
return Ok(());
}
for (i, hook) in self.hooks.iter().enumerate() {
if let Some(start) = &hook.start {
if let Err(e) = start(self) {
self.stop_first(i);
return Err(format!("{}: {e}", hook.name));
}
}
}
self.started.store(self.hooks.len(), Ordering::SeqCst);
self.running.store(true, Ordering::SeqCst);
Ok(())
}
pub fn stop(&self) {
if self.running.swap(false, std::sync::atomic::Ordering::SeqCst) {
let n = self.started.swap(0, std::sync::atomic::Ordering::SeqCst);
self.stop_first(n);
}
}
fn stop_first(&self, n: usize) {
for hook in self.hooks[..n.min(self.hooks.len())].iter().rev() {
if let Some(stop) = &hook.stop {
stop(self);
}
}
}
pub fn is_started(&self) -> bool {
self.running.load(std::sync::atomic::Ordering::SeqCst)
}
pub fn into_arc(self) -> Arc<Self> {
Arc::new(self)
}
}
#[cfg(test)]
mod tests;