1use core::any::type_name;
26use std::borrow::Cow;
27use std::collections::HashMap;
28use std::fmt::{Display, Formatter, Result};
29use std::ops::Range;
30
31use crate::vecmap::VecMap;
32use index_vec::{Idx, IndexVec};
33
34use crate::assembly::{ReactorInitializer, TriggerId};
35use crate::{GlobalReactionId, ReactorId};
36
37pub(crate) struct DebugInfoRegistry {
43 reactor_infos: IndexVec<ReactorId, ReactorDebugInfo>,
45
46 reactor_bound: IndexVec<ReactorId, TriggerId>,
49
50 trigger_infos: IndexVec<TriggerId, Cow<'static, str>>,
53
54 reactor_container: VecMap<ReactorId, ReactorId>,
57
58 main_reactor: Option<ReactorId>,
59
60 reaction_labels: HashMap<GlobalReactionId, Cow<'static, str>>,
63}
64
65type RawId = (ReactorId, usize);
70
71impl DebugInfoRegistry {
72 pub fn new() -> Self {
73 let mut ich = Self {
74 reactor_infos: Default::default(),
75 reactor_bound: Default::default(),
76 trigger_infos: Default::default(),
77 reaction_labels: Default::default(),
78 reactor_container: Default::default(),
79 main_reactor: None,
80 };
81
82 assert_eq!(ich.trigger_infos.push(Cow::Borrowed("startup")), TriggerId::STARTUP);
83 assert_eq!(ich.trigger_infos.push(Cow::Borrowed("shutdown")), TriggerId::SHUTDOWN);
84
85 ich
86 }
87}
88
89impl DebugInfoRegistry {
90 pub fn get_debug_info(&self, id: ReactorId) -> &ReactorDebugInfo {
91 &self.reactor_infos[id]
92 }
93
94 fn fmt_component_path<'a>(
96 &'a self,
97 id: RawId,
98 label: Option<&'a Cow<'static, str>>,
99 always_display_idx: bool,
100 ) -> impl Display + 'a {
101 struct PathFmt<'a> {
102 debug: &'a DebugInfoRegistry,
103 id: RawId,
104 label: Option<&'a Cow<'static, str>>,
105 always_display_idx: bool,
108 }
109 use std::fmt::*;
110 impl Display for PathFmt<'_> {
111 #[inline]
112 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
113 write!(f, "{}", self.debug.get_debug_info(self.id.0))?;
114 if let Some(label) = &self.label {
115 if self.always_display_idx {
116 write!(f, "{}@{}", self.id.1, label)
117 } else {
118 write!(f, "{}", label)
119 }
120 } else {
121 write!(f, "{}", self.id.1)
122 }
123 }
124 }
125
126 PathFmt { debug: self, id, label, always_display_idx }
127 }
128
129 #[inline]
130 pub fn fmt_reaction(&self, id: GlobalReactionId) -> impl Display + '_ {
131 let raw = (id.0.container(), id.0.local().index());
132 self.fmt_component_path(raw, self.reaction_labels.get(&id), true)
133 }
134
135 #[inline]
136 pub fn fmt_component(&self, id: TriggerId) -> impl Display + '_ {
137 self.fmt_component_path(self.raw_id_of_trigger(id), Some(&self.trigger_infos[id]), false)
138 }
139
140 #[inline]
141 pub fn get_container(&self, id: ReactorId) -> Option<ReactorId> {
142 let container = self.reactor_container.get(&id);
143 debug_assert!(container.is_some() || self.is_main(id));
144 container.cloned()
145 }
146 #[inline]
147 pub fn get_trigger_container(&self, id: TriggerId) -> Option<ReactorId> {
148 match id {
149 TriggerId::SHUTDOWN | TriggerId::STARTUP => None,
150 _ => Some(self.raw_id_of_trigger(id).0),
151 }
152 }
153
154 #[inline]
155 pub fn is_main(&self, id: ReactorId) -> bool {
156 self.main_reactor.unwrap() == id
157 }
158
159 fn raw_id_of_trigger(&self, id: TriggerId) -> RawId {
160 match id {
161 TriggerId::STARTUP | TriggerId::SHUTDOWN => {
164 let last_reactor = self.reactor_infos.last_idx();
165 let max_idx = *self.reactor_bound.last().unwrap();
166 let max_local_idx = max_idx.index() - self.get_reactor_lower_bound(last_reactor).index();
167 (last_reactor, max_local_idx + id.index())
168 }
169
170 id => {
171 match self.reactor_bound.binary_search(&id) {
172 Ok(rid) => (rid.plus(1), 0usize),
175 Err(rid) => (rid, id.index() - self.get_reactor_lower_bound(rid).index()),
182 }
183 }
184 }
185 }
186
187 fn get_reactor_lower_bound(&self, rid: ReactorId) -> TriggerId {
188 rid.index()
189 .checked_sub(1)
190 .map(|ix| self.reactor_bound[ix])
191 .unwrap_or(TriggerId::FIRST_REGULAR)
192 }
193
194 pub(crate) fn record_trigger(&mut self, id: TriggerId, name: Cow<'static, str>) {
195 let ix = self.trigger_infos.push(name);
196 debug_assert_eq!(ix, id);
197 }
198
199 pub(crate) fn record_reaction(&mut self, id: GlobalReactionId, name: Cow<'static, str>) {
200 let existing = self.reaction_labels.insert(id, name);
201 debug_assert!(existing.is_none())
202 }
203
204 pub(crate) fn record_reactor(&mut self, id: ReactorId, debug: ReactorDebugInfo) {
205 let ix = self.reactor_infos.push(debug);
206 debug_assert_eq!(ix, id);
207 }
208
209 pub(crate) fn record_main_reactor(&mut self, id: ReactorId) {
210 let prev = self.main_reactor.replace(id);
211 assert!(prev.is_none(), "cannot call record_main twice");
212 }
213
214 pub(crate) fn record_reactor_container(&mut self, parent: ReactorId, child: ReactorId) {
215 let ix = self.reactor_container.insert(child, parent);
216 debug_assert!(ix.is_none(), "overwrote reactor");
217 }
218
219 pub(crate) fn set_id_range(&mut self, id: ReactorId, range: Range<TriggerId>) {
220 assert!(range.start <= range.end, "Malformed range {:?}", range);
221 assert!(range.start >= TriggerId::FIRST_REGULAR, "Trigger IDs 0-1 are reserved");
222
223 let ix = self.reactor_bound.push(range.end);
224 assert_eq!(ix, id);
225 }
226}
227
228pub(crate) struct ReactorDebugInfo {
230 #[allow(unused)]
232 pub type_name: &'static str,
233 #[allow(unused)]
235 pub inst_name: &'static str,
236 inst_path: String,
238}
239
240impl ReactorDebugInfo {
241 #[cfg(test)]
242 pub(crate) fn test() -> Self {
243 Self::root::<()>()
244 }
245
246 #[cfg(test)]
247 pub(crate) fn test_named(inst_path: impl Into<String>) -> Self {
248 let mut inst_path = inst_path.into();
249 inst_path.push('/');
250 Self {
251 type_name: "unknown",
252 inst_name: "unknown",
253 inst_path,
254 }
255 }
256
257 pub(crate) fn root<R>() -> Self {
258 Self {
259 type_name: type_name::<R>(),
260 inst_name: "/",
261 inst_path: "/".into(),
262 }
263 }
264
265 pub(crate) fn derive<R: ReactorInitializer>(&self, inst_name: &'static str) -> Self {
266 Self {
267 type_name: type_name::<R::Wrapped>(),
268 inst_name,
269 inst_path: format!("{}{}/", self.inst_path, inst_name),
270 }
271 }
272
273 pub(crate) fn derive_bank_item<R: ReactorInitializer>(&self, inst_name: &'static str, bank_idx: usize) -> Self {
274 Self {
275 type_name: type_name::<R::Wrapped>(),
276 inst_name,
277 inst_path: format!("{}{}[{}]/", self.inst_path, inst_name, bank_idx),
278 }
279 }
280}
281
282impl Display for ReactorDebugInfo {
283 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
284 write!(f, "{}", self.inst_path)
285 }
286}
287
288#[cfg(test)]
289pub mod test {
290 use crate::assembly::TriggerId;
291 use crate::{DebugInfoRegistry, ReactorDebugInfo, ReactorId};
292
293 #[test]
294 fn test_raw_id_from_trigger() -> Result<(), ()> {
295 let mut debug = DebugInfoRegistry::new();
296 let mut trigger_id = TriggerId::FIRST_REGULAR;
297 let reactor_0 = ReactorId::new(0);
298 let first_trigger = trigger_id;
299 debug.record_reactor(reactor_0, ReactorDebugInfo::test_named("foo"));
300 debug.record_trigger(trigger_id.get_and_incr()?, "t0".into());
301 debug.record_trigger(trigger_id.get_and_incr()?, "t1".into());
302 debug.set_id_range(reactor_0, first_trigger..trigger_id);
303
304 let reactor_1 = ReactorId::new(1);
305 let first_trigger = trigger_id;
306 debug.record_reactor(reactor_1, ReactorDebugInfo::test_named("foo1"));
307 debug.record_trigger(trigger_id.get_and_incr()?, "t0".into());
308 debug.record_trigger(trigger_id.get_and_incr()?, "t1".into());
309 debug.set_id_range(reactor_1, first_trigger..trigger_id);
310
311 let mut trigger_id = TriggerId::FIRST_REGULAR;
312 assert_eq!((reactor_0, 0), debug.raw_id_of_trigger(trigger_id.get_and_incr()?));
313 assert_eq!((reactor_0, 1), debug.raw_id_of_trigger(trigger_id.get_and_incr()?));
314 assert_eq!((reactor_1, 0), debug.raw_id_of_trigger(trigger_id.get_and_incr()?));
315 assert_eq!((reactor_1, 1), debug.raw_id_of_trigger(trigger_id.get_and_incr()?));
316
317 assert_eq!((reactor_1, 2), debug.raw_id_of_trigger(TriggerId::STARTUP));
318 assert_eq!((reactor_1, 3), debug.raw_id_of_trigger(TriggerId::SHUTDOWN));
319
320 Ok(())
321 }
322}