1#[doc = include_str!("../README.md")]
2use std::collections::{HashMap, HashSet};
3use std::error::Error;
4use std::fmt::{Debug, Display};
5use std::sync::Mutex;
6
7use futures_util::StreamExt;
8use futures_util::stream::FuturesUnordered;
9pub use init_static_macro::init_static;
10
11use crate::__private::INIT;
12pub use crate::init_static::{InitStatic, Symbol};
13
14mod init_static;
15
16struct InitOptionsInner {
17 debug: bool,
18}
19
20pub struct InitOptions {
21 inner: Mutex<Option<InitOptionsInner>>,
22}
23
24impl InitOptions {
25 pub fn debug(&self, debug: bool) {
26 self.inner
27 .lock()
28 .unwrap()
29 .as_mut()
30 .expect("INIT_OPTIONS can only be modified before `init_static` is called.")
31 .debug = debug;
32 }
33}
34
35pub static INIT_OPTIONS: InitOptions = InitOptions {
36 inner: Mutex::new(Some(InitOptionsInner { debug: false })),
37};
38
39pub async fn init_static() -> Result<(), InitError> {
63 let options = INIT_OPTIONS
64 .inner
65 .lock()
66 .unwrap()
67 .take()
68 .expect("`init_static` can only be called once.");
69
70 let mut symbol_map: HashMap<&'static Symbol, usize> = HashMap::new();
71 for (i, init) in INIT.iter().enumerate() {
72 if symbol_map.insert(init.symbol, i).is_some() {
73 return Err(InitError::Ambiguous { symbol: init.symbol });
74 }
75 }
76
77 let mut adjacent = INIT
78 .iter()
79 .enumerate()
80 .map(|(i, init)| {
81 let deps = (init.deps)()
82 .into_iter()
83 .filter_map(|symbol| Some(*symbol_map.get(symbol?)?))
84 .collect::<HashSet<_>>();
85 (i, deps)
86 })
87 .collect::<Vec<_>>();
88
89 let mut join_set = FuturesUnordered::new();
90 while !adjacent.is_empty() || !join_set.is_empty() {
91 let layer = adjacent
92 .extract_if(.., |(_, deps)| deps.is_empty())
93 .map(|(i, _)| i)
94 .collect::<HashSet<_>>();
95 join_set.extend(layer.into_iter().map(|i| async move {
96 if options.debug {
97 eprintln!("init_static: begin {}", INIT[i].symbol);
98 }
99 let output = (INIT[i].init)().await;
100 if options.debug {
101 eprintln!("init_static: end {}", INIT[i].symbol);
102 }
103 output.map(|_| i)
104 }));
105 if join_set.is_empty() {
106 return Err(InitError::Circular {
107 symbols: adjacent.iter().map(|(i, _)| INIT[*i].symbol).collect(),
108 });
109 }
110 match join_set.next().await.unwrap() {
111 Ok(i) => {
112 for (_, deps) in &mut adjacent {
113 deps.remove(&i);
114 }
115 }
116 Err(e) => return Err(InitError::Execution(e)),
117 }
118 }
119
120 Ok(())
121}
122
123#[derive(Debug)]
124pub enum InitError {
125 Ambiguous { symbol: &'static Symbol },
126 Circular { symbols: Vec<&'static Symbol> },
127 Execution(anyhow::Error),
128}
129
130impl Display for InitError {
131 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
132 match self {
133 Self::Ambiguous { symbol } => {
134 write!(f, "Symbol {symbol} is defined multiple times.")
135 }
136 Self::Circular { symbols } => {
137 writeln!(f, "Circular dependency detected among:")?;
138 for symbol in symbols {
139 writeln!(f, " {symbol}")?;
140 }
141 Ok(())
142 }
143 Self::Execution(e) => Display::fmt(e, f),
144 }
145 }
146}
147
148impl Error for InitError {
149 fn source(&self) -> Option<&(dyn Error + 'static)> {
150 match self {
151 Self::Execution(e) => Some(&**e),
152 _ => None,
153 }
154 }
155}
156
157#[doc(hidden)]
158pub mod __private {
159 use std::pin::Pin;
160
161 pub use {anyhow, linkme};
162
163 use crate::Symbol;
164 pub use crate::init_static::MaybeInitStatic;
165
166 pub type BoxFuture<T> = Pin<Box<dyn Future<Output = T>>>;
167
168 pub struct Init {
169 pub symbol: &'static Symbol,
170 pub init: fn() -> BoxFuture<anyhow::Result<()>>,
171 pub deps: fn() -> Vec<Option<&'static Symbol>>,
172 }
173
174 #[linkme::distributed_slice]
175 pub static INIT: [Init];
176}