1use std::any::{Any, TypeId};
2use std::cell::RefCell;
3use std::sync::atomic::{AtomicUsize, Ordering};
4use std::sync::{LazyLock, Mutex};
5
6use crate::{HashSet, PluginContext};
7
8static DATA_PLUGINS: LazyLock<Mutex<RefCell<HashSet<TypeId>>>> =
10 LazyLock::new(|| Mutex::new(RefCell::new(HashSet::default())));
11
12pub fn add_data_plugin_to_registry<T: DataPlugin>() {
13 DATA_PLUGINS
14 .lock()
15 .unwrap()
16 .borrow_mut()
17 .insert(TypeId::of::<T>());
18}
19
20#[must_use]
21pub fn get_data_plugin_ids() -> Vec<TypeId> {
22 DATA_PLUGINS
23 .lock()
24 .unwrap()
25 .borrow()
26 .iter()
27 .copied()
28 .collect()
29}
30
31#[must_use]
32pub fn get_data_plugin_count() -> usize {
33 DATA_PLUGINS.lock().unwrap().borrow().len()
34}
35
36static NEXT_DATA_PLUGIN_INDEX: Mutex<usize> = Mutex::new(0);
44
45#[must_use]
48pub fn initialize_data_plugin_index(plugin_index: &AtomicUsize) -> usize {
49 let mut guard = NEXT_DATA_PLUGIN_INDEX.lock().unwrap();
51 let candidate = *guard;
52
53 match plugin_index.compare_exchange(usize::MAX, candidate, Ordering::AcqRel, Ordering::Acquire)
60 {
61 Ok(_) => {
62 *guard += 1;
64 candidate
65 }
66 Err(existing) => {
67 existing
70 }
71 }
72}
73
74pub trait DataPlugin: Any {
76 type DataContainer;
77
78 fn init<C: PluginContext>(context: &C) -> Self::DataContainer;
79
80 #[must_use]
83 fn index_within_context() -> usize;
84}
85
86#[cfg(test)]
87mod tests {
88 use std::sync::{Arc, Barrier};
89 use std::thread;
90
91 use super::*;
92 use crate::{define_data_plugin, Context};
93
94 #[test]
96 #[should_panic(
97 expected = "No data plugin found with index = 1000. You must use the `define_data_plugin!` macro to create a data plugin."
98 )]
99 fn test_wrong_data_plugin_impl_index_oob() {
100 struct MyDataPlugin;
104 impl DataPlugin for MyDataPlugin {
105 type DataContainer = Vec<u32>;
106
107 fn init<C: PluginContext>(_context: &C) -> Self::DataContainer {
108 vec![]
109 }
110
111 fn index_within_context() -> usize {
112 1000 }
114 }
115
116 let context = Context::new();
117 let _container = context.get_data(MyDataPlugin);
118 }
119
120 define_data_plugin!(LegitDataPlugin, Vec<u32>, vec![]);
122 #[should_panic(
123 expected = "TypeID does not match data plugin type. You must use the `define_data_plugin!` macro to create a data plugin."
124 )]
125 #[test]
126 fn test_wrong_data_plugin_impl_wrong_type() {
127 struct MyOtherDataPlugin;
131 impl DataPlugin for MyOtherDataPlugin {
132 type DataContainer = Vec<u8>;
133
134 fn init<C: PluginContext>(_context: &C) -> Self::DataContainer {
135 vec![]
136 }
137
138 fn index_within_context() -> usize {
139 LegitDataPlugin::index_within_context()
142 }
143 }
144
145 let context = Context::new();
146 let _ = context.get_data(LegitDataPlugin);
148
149 let _container = context.get_data(MyOtherDataPlugin);
151 }
153
154 #[test]
156 fn test_multithreaded_plugin_init() {
157 struct DataPluginContainerA;
158 define_data_plugin!(DataPluginA, DataPluginContainerA, DataPluginContainerA);
159 struct DataPluginContainerB;
160 define_data_plugin!(DataPluginB, DataPluginContainerB, DataPluginContainerB);
161 struct DataPluginContainerC;
162 define_data_plugin!(DataPluginC, DataPluginContainerC, DataPluginContainerC);
163 struct DataPluginContainerD;
164 define_data_plugin!(DataPluginD, DataPluginContainerD, DataPluginContainerD);
165
166 let accessors: Vec<&(dyn Fn(&Context) + Send + Sync)> = vec![
168 &|ctx: &Context| {
169 let _ = ctx.get_data(DataPluginA);
170 },
171 &|ctx: &Context| {
172 let _ = ctx.get_data(DataPluginB);
173 },
174 &|ctx: &Context| {
175 let _ = ctx.get_data(DataPluginC);
176 },
177 &|ctx: &Context| {
178 let _ = ctx.get_data(DataPluginD);
179 },
180 ];
181
182 let num_threads = 20;
183 let barrier = Arc::new(Barrier::new(num_threads));
184 let mut handles = Vec::with_capacity(num_threads);
185
186 for i in 0..num_threads {
187 let barrier = Arc::clone(&barrier);
188 let accessor = accessors[i % accessors.len()];
189
190 let handle = thread::spawn(move || {
191 let context = Context::new();
192 barrier.wait();
193 accessor(&context);
194 });
195
196 handles.push(handle);
197 }
198
199 for handle in handles {
200 handle.join().expect("Thread panicked");
201 }
202 }
203}