1#![doc = include_str!("../README.md")]
2
3use std::collections::{HashMap, HashSet};
4use std::sync::Mutex;
5
6use anyhow::{Context, Result};
7use futures_util::StreamExt;
8use futures_util::stream::FuturesUnordered;
9
10use crate::__private::{INIT, InitFn};
11
12mod error;
13mod init_static;
14
15pub use init_static_macro::init_static;
42
43pub use crate::error::InitError;
44pub use crate::init_static::{InitStatic, Symbol};
45
46struct InitOptions {
47 debug: bool,
48}
49
50static INIT_OPTIONS: Mutex<Option<InitOptions>> = Mutex::new(Some(InitOptions { debug: false }));
51
52pub fn set_debug(debug: bool) {
63 INIT_OPTIONS
64 .lock()
65 .unwrap()
66 .as_mut()
67 .expect("INIT_OPTIONS can only be modified before `init_static` is called.")
68 .debug = debug;
69}
70
71pub fn is_initialized() -> bool {
77 INIT_OPTIONS.lock().unwrap().is_none()
78}
79
80pub async fn init_static() -> Result<()> {
102 let options = INIT_OPTIONS
103 .lock()
104 .unwrap()
105 .take()
106 .expect("`init_static` can only be called once.");
107
108 let mut symbol_map: HashMap<&'static Symbol, usize> = HashMap::new();
109 for (i, init) in INIT.iter().enumerate() {
110 if symbol_map.insert(init.symbol, i).is_some() {
111 return Err(InitError::Ambiguous { symbol: init.symbol }.into());
112 }
113 }
114
115 let mut adjacent = INIT
116 .iter()
117 .enumerate()
118 .map(|(i, init)| {
119 let deps = (init.deps)()
120 .into_iter()
121 .filter_map(|symbol| Some(*symbol_map.get(symbol?)?))
122 .collect::<HashSet<_>>();
123 (i, deps)
124 })
125 .collect::<Vec<_>>();
126
127 let mut join_set = FuturesUnordered::new();
128 while !adjacent.is_empty() || !join_set.is_empty() {
129 let layer = adjacent
130 .extract_if(.., |(_, deps)| deps.is_empty())
131 .map(|(i, _)| i)
132 .collect::<HashSet<_>>();
133 let mut has_sync = false;
134 for i in layer {
135 match &INIT[i].init {
136 InitFn::Sync(f) => {
137 has_sync = true;
138 if options.debug {
139 eprintln!("init_static: sync {}", INIT[i].symbol);
140 }
141 f().with_context(|| format!("failed to initialize {}", INIT[i].symbol))?;
142 for (_, deps) in &mut adjacent {
143 deps.remove(&i);
144 }
145 }
146 InitFn::Async(f) => join_set.push(async move {
147 if options.debug {
148 eprintln!("init_static: async begin {}", INIT[i].symbol);
149 }
150 let output = f()
151 .await
152 .map(|_| i)
153 .with_context(|| format!("failed to initialize {}", INIT[i].symbol));
154 if options.debug {
155 eprintln!("init_static: async end {}", INIT[i].symbol);
156 }
157 output
158 }),
159 }
160 }
161 if has_sync {
162 continue;
163 }
164 if join_set.is_empty() {
165 return Err(InitError::Circular {
166 symbols: adjacent.iter().map(|(i, _)| INIT[*i].symbol).collect(),
167 }
168 .into());
169 }
170 let i = join_set.next().await.unwrap()?;
171 for (_, deps) in &mut adjacent {
172 deps.remove(&i);
173 }
174 }
175
176 Ok(())
177}
178
179#[doc(hidden)]
180pub mod __private {
181 use std::pin::Pin;
182
183 pub use {anyhow, linkme};
184
185 use crate::Symbol;
186 pub use crate::init_static::MaybeInitStatic;
187
188 pub type BoxFuture<T> = Pin<Box<dyn Future<Output = T>>>;
189
190 pub enum InitFn {
191 Sync(fn() -> anyhow::Result<()>),
192 Async(fn() -> BoxFuture<anyhow::Result<()>>),
193 }
194
195 pub struct Init {
196 pub symbol: &'static Symbol,
197 pub init: InitFn,
198 pub deps: fn() -> Vec<Option<&'static Symbol>>,
199 }
200
201 #[linkme::distributed_slice]
202 pub static INIT: [Init];
203}