#![doc = include_str!("../README.md")]
use std::collections::{HashMap, HashSet};
use std::sync::Mutex;
use anyhow::{Context, Result};
use futures_util::StreamExt;
use futures_util::stream::FuturesUnordered;
use crate::__private::{INIT, InitFn};
mod error;
mod init_static;
pub use init_static_macro::init_static;
pub use crate::error::InitError;
pub use crate::init_static::{InitStatic, Symbol};
struct InitOptions {
debug: bool,
}
static INIT_OPTIONS: Mutex<Option<InitOptions>> = Mutex::new(Some(InitOptions { debug: false }));
pub fn set_debug(debug: bool) {
INIT_OPTIONS
.lock()
.unwrap()
.as_mut()
.expect("INIT_OPTIONS can only be modified before `init_static` is called.")
.debug = debug;
}
pub fn is_initialized() -> bool {
INIT_OPTIONS.lock().unwrap().is_none()
}
pub async fn init_static() -> Result<()> {
let options = INIT_OPTIONS
.lock()
.unwrap()
.take()
.expect("`init_static` can only be called once.");
let mut symbol_map: HashMap<&'static Symbol, usize> = HashMap::new();
for (i, init) in INIT.iter().enumerate() {
if symbol_map.insert(init.symbol, i).is_some() {
return Err(InitError::Ambiguous { symbol: init.symbol }.into());
}
}
let mut adjacent = INIT
.iter()
.enumerate()
.map(|(i, init)| {
let deps = (init.deps)()
.into_iter()
.filter_map(|symbol| Some(*symbol_map.get(symbol?)?))
.collect::<HashSet<_>>();
(i, deps)
})
.collect::<Vec<_>>();
let mut join_set = FuturesUnordered::new();
while !adjacent.is_empty() || !join_set.is_empty() {
let layer = adjacent
.extract_if(.., |(_, deps)| deps.is_empty())
.map(|(i, _)| i)
.collect::<HashSet<_>>();
let mut has_sync = false;
for i in layer {
match &INIT[i].init {
InitFn::Sync(f) => {
has_sync = true;
if options.debug {
eprintln!("init_static: sync {}", INIT[i].symbol);
}
f().with_context(|| format!("failed to initialize {}", INIT[i].symbol))?;
for (_, deps) in &mut adjacent {
deps.remove(&i);
}
}
InitFn::Async(f) => join_set.push(async move {
if options.debug {
eprintln!("init_static: async begin {}", INIT[i].symbol);
}
let output = f()
.await
.map(|_| i)
.with_context(|| format!("failed to initialize {}", INIT[i].symbol));
if options.debug {
eprintln!("init_static: async end {}", INIT[i].symbol);
}
output
}),
}
}
if has_sync {
continue;
}
if join_set.is_empty() {
return Err(InitError::Circular {
symbols: adjacent.iter().map(|(i, _)| INIT[*i].symbol).collect(),
}
.into());
}
let i = join_set.next().await.unwrap()?;
for (_, deps) in &mut adjacent {
deps.remove(&i);
}
}
Ok(())
}
#[doc(hidden)]
pub mod __private {
use std::pin::Pin;
pub use {anyhow, linkme};
use crate::Symbol;
pub use crate::init_static::MaybeInitStatic;
pub type BoxFuture<T> = Pin<Box<dyn Future<Output = T>>>;
pub enum InitFn {
Sync(fn() -> anyhow::Result<()>),
Async(fn() -> BoxFuture<anyhow::Result<()>>),
}
pub struct Init {
pub symbol: &'static Symbol,
pub init: InitFn,
pub deps: fn() -> Vec<Option<&'static Symbol>>,
}
#[linkme::distributed_slice]
pub static INIT: [Init];
}