1#![allow(clippy::should_implement_trait, clippy::new_ret_no_self)]
3use std::sync::atomic::{AtomicUsize, Ordering};
4use std::{any::Any, any::TypeId, cell::RefCell, fmt, ops, ptr};
5
6type Key = (usize, TypeId);
7type HashMap<K, V> = std::collections::HashMap<K, V, foldhash::fast::RandomState>;
8
9thread_local! {
10 static DEFAULT_CFG: &'static Storage = Box::leak(Box::new(
11 Storage::new("--".to_string(), false)));
12 static MAPPING: RefCell<HashMap<Key, &'static dyn Any>> = {
13 RefCell::new(HashMap::default())
14 };
15}
16static IDX: AtomicUsize = AtomicUsize::new(0);
17
18#[derive(Debug)]
19struct Storage {
20 id: usize,
21 tag: String,
22 building: bool,
23 data: HashMap<TypeId, Box<dyn Any + Send + Sync>>,
24}
25
26impl Storage {
27 fn new(tag: String, building: bool) -> Self {
28 let id = IDX.fetch_add(1, Ordering::SeqCst);
29 Storage {
30 id,
31 tag,
32 building,
33 data: HashMap::default(),
34 }
35 }
36}
37
38pub trait Configuration: Default + Send + Sync + fmt::Debug + 'static {
39 const NAME: &'static str;
40
41 fn ctx(&self) -> &CfgContext;
42
43 fn set_ctx(&mut self, ctx: CfgContext);
44}
45
46#[derive(Copy, Clone, Debug)]
47pub struct CfgContext(&'static Storage);
48
49impl CfgContext {
50 #[inline]
51 pub fn id(&self) -> usize {
52 self.0.id
53 }
54
55 #[inline]
56 pub fn tag(&self) -> &'static str {
57 self.0.tag.as_ref()
58 }
59
60 #[inline]
61 pub fn shared(&self) -> SharedCfg {
62 SharedCfg(self.0)
63 }
64}
65
66impl Default for CfgContext {
67 #[inline]
68 fn default() -> Self {
69 CfgContext(DEFAULT_CFG.with(|cfg| *cfg))
70 }
71}
72
73#[derive(Debug)]
74pub struct Cfg<T: Configuration>(&'static T);
75
76impl<T: Configuration> Cfg<T> {
77 #[inline]
78 pub fn id(&self) -> usize {
79 self.0.ctx().0.id
80 }
81
82 #[inline]
83 pub fn tag(&self) -> &'static str {
84 self.0.ctx().tag()
85 }
86
87 #[inline]
88 pub fn shared(&self) -> SharedCfg {
89 self.0.ctx().shared()
90 }
91
92 #[inline]
93 pub fn into_static(&self) -> &'static T {
94 self.0
95 }
96}
97
98impl<T: Configuration> Copy for Cfg<T> {}
99
100impl<T: Configuration> Clone for Cfg<T> {
101 #[inline]
102 fn clone(&self) -> Self {
103 *self
104 }
105}
106
107impl<T: Configuration> ops::Deref for Cfg<T> {
108 type Target = T;
109
110 #[inline]
111 fn deref(&self) -> &'static T {
112 self.0
113 }
114}
115
116impl<T: Configuration> Default for Cfg<T> {
117 #[inline]
118 fn default() -> Self {
119 CfgContext::default().shared().get()
120 }
121}
122
123#[derive(Copy, Clone, Debug)]
124pub struct SharedCfg(&'static Storage);
126
127#[derive(Debug)]
128pub struct SharedCfgBuilder {
129 ctx: CfgContext,
130 storage: Option<Box<Storage>>,
131}
132
133impl Eq for SharedCfg {}
134
135impl PartialEq for SharedCfg {
136 fn eq(&self, other: &Self) -> bool {
137 ptr::from_ref(self.0) == ptr::from_ref(other.0)
138 }
139}
140
141impl SharedCfg {
142 pub fn new<T: AsRef<str>>(tag: T) -> SharedCfgBuilder {
144 SharedCfgBuilder::new(tag.as_ref().to_string())
145 }
146
147 #[inline]
148 pub fn id(&self) -> usize {
150 self.0.id
151 }
152
153 #[inline]
154 pub fn tag(&self) -> &'static str {
156 self.0.tag.as_ref()
157 }
158
159 pub fn get<T>(&self) -> Cfg<T>
163 where
164 T: Configuration,
165 {
166 if self.0.building {
167 panic!("{}: Cannot access shared config while building", self.tag());
168 }
169 let tp = TypeId::of::<T>();
170 self.0
171 .data
172 .get(&tp)
173 .and_then(|boxed| boxed.downcast_ref())
174 .map(Cfg)
175 .unwrap_or_else(|| {
176 MAPPING.with(|store| {
177 let key = (self.0.id, tp);
178 if let Some(boxed) = store.borrow().get(&key) {
179 Cfg(boxed.downcast_ref().unwrap())
180 } else {
181 log::info!(
182 "{}: Configuration {:?} does not exist, using default",
183 self.tag(),
184 T::NAME
185 );
186 let mut val = T::default();
187 val.set_ctx(CfgContext(self.0));
188 store.borrow_mut().insert(key, Box::leak(Box::new(val)));
189 Cfg(store.borrow().get(&key).unwrap().downcast_ref().unwrap())
190 }
191 })
192 })
193 }
194}
195
196impl Default for SharedCfg {
197 #[inline]
198 fn default() -> Self {
199 Self(DEFAULT_CFG.with(|cfg| *cfg))
200 }
201}
202
203impl SharedCfgBuilder {
204 fn new(tag: String) -> SharedCfgBuilder {
205 let storage = Box::into_raw(Box::new(Storage::new(tag, true)));
206 unsafe {
207 SharedCfgBuilder {
208 ctx: CfgContext(storage.as_ref().unwrap()),
209 storage: Some(Box::from_raw(storage)),
210 }
211 }
212 }
213
214 pub fn add<T: Configuration>(mut self, mut val: T) -> Self {
219 val.set_ctx(self.ctx);
220 self.storage
221 .as_mut()
222 .unwrap()
223 .data
224 .insert(TypeId::of::<T>(), Box::new(val));
225 self
226 }
227}
228
229impl Drop for SharedCfgBuilder {
230 fn drop(&mut self) {
231 if let Some(mut st) = self.storage.take() {
232 st.building = false;
233 let _ = Box::leak(st);
234 }
235 }
236}
237
238impl From<SharedCfgBuilder> for SharedCfg {
239 fn from(mut cfg: SharedCfgBuilder) -> SharedCfg {
240 let mut st = cfg.storage.take().unwrap();
241 st.building = false;
242 SharedCfg(Box::leak(st))
243 }
244}
245
246#[cfg(test)]
247mod tests {
248 use super::*;
249
250 #[test]
251 #[should_panic]
252 fn access_cfg_in_building_state() {
253 #[derive(Debug)]
254 struct TestCfg {
255 config: CfgContext,
256 }
257 impl TestCfg {
258 fn new() -> Self {
259 Self {
260 config: CfgContext::default(),
261 }
262 }
263 }
264 impl Default for TestCfg {
265 fn default() -> Self {
266 panic!()
267 }
268 }
269 impl Configuration for TestCfg {
270 const NAME: &str = "TEST";
271 fn ctx(&self) -> &CfgContext {
272 &self.config
273 }
274 fn set_ctx(&mut self, ctx: CfgContext) {
275 self.config = ctx;
276 let _ = ctx.shared().get::<TestCfg>();
277 }
278 }
279 SharedCfg::new("TEST").add(TestCfg::new());
280 }
281
282 #[test]
283 fn shared_cfg() {
284 #[derive(Default, Debug)]
285 struct TestCfg {
286 config: CfgContext,
287 }
288 impl Configuration for TestCfg {
289 const NAME: &str = "TEST";
290 fn ctx(&self) -> &CfgContext {
291 &self.config
292 }
293 fn set_ctx(&mut self, ctx: CfgContext) {
294 self.config = ctx;
295 }
296 }
297
298 let cfg: SharedCfg = SharedCfg::new("TEST").add(TestCfg::default()).into();
299
300 assert_eq!(cfg.tag(), "TEST");
301 let t = cfg.get::<TestCfg>();
302 assert_eq!(t.tag(), "TEST");
303 assert_eq!(t.shared(), cfg);
304 let t: Cfg<TestCfg> = Default::default();
305 assert_eq!(t.tag(), "--");
306 assert_eq!(t.ctx().id(), t.id());
307 assert_eq!(t.ctx().id(), t.ctx().clone().id());
308
309 let cfg: SharedCfg = SharedCfg::new("TEST2").into();
310 let t = cfg.get::<TestCfg>();
311 assert_eq!(t.tag(), "TEST2");
312 assert_eq!(t.id(), cfg.id());
313 }
314}