#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SourceLocation {
pub name: &'static str,
pub file: &'static str,
pub line: u32,
pub manifest_dir: &'static str,
#[cfg(all(feature = "inspection", debug_assertions))]
recompositions: Option<RecompositionCounter>,
}
impl SourceLocation {
pub fn recompositions(&self) -> Option<u64> {
#[cfg(all(feature = "inspection", debug_assertions))]
return self
.recompositions
.as_ref()
.map(|counter| counter.0.executions.get().saturating_sub(1));
#[cfg(not(all(feature = "inspection", debug_assertions)))]
None
}
}
#[cfg(all(feature = "inspection", debug_assertions))]
#[derive(Clone, Debug)]
pub(crate) struct RecompositionCounter(std::rc::Rc<Counter>);
#[cfg(all(feature = "inspection", debug_assertions))]
#[derive(Debug)]
struct Counter {
executions: std::cell::Cell<u64>,
instance_id: u64,
name: &'static str,
file: &'static str,
line: u32,
manifest_dir: &'static str,
}
#[derive(Debug, PartialEq, Eq)]
pub struct RecompositionInfo {
pub instance_id: u64,
pub name: &'static str,
pub file: &'static str,
pub line: u32,
pub manifest_dir: &'static str,
pub recompositions: u64,
}
pub fn recomposition_snapshot() -> Vec<RecompositionInfo> {
#[cfg(all(feature = "inspection", debug_assertions))]
{
if !crate::thread_state::cells(|cells| cells.track_recompositions.get()) {
return Vec::new();
}
crate::thread_state::runtimes(|state| {
let mut registry = state.recomposition_counters.borrow_mut();
let mut result = Vec::with_capacity(registry.len());
registry.retain(|weak| {
let Some(scope) = crate::RecomposeScope::upgrade(weak) else {
return false;
};
if scope.is_effectively_active()
&& let Some(counter) = scope.inner.recompositions.get()
{
result.push(RecompositionInfo {
instance_id: counter.0.instance_id,
name: counter.0.name,
file: counter.0.file,
line: counter.0.line,
manifest_dir: counter.0.manifest_dir,
recompositions: counter.0.executions.get().saturating_sub(1),
});
}
true
});
result
})
}
#[cfg(not(all(feature = "inspection", debug_assertions)))]
Vec::new()
}
#[cfg(all(feature = "inspection", debug_assertions))]
impl PartialEq for RecompositionCounter {
fn eq(&self, other: &Self) -> bool {
std::rc::Rc::ptr_eq(&self.0, &other.0)
}
}
#[cfg(all(feature = "inspection", debug_assertions))]
impl Eq for RecompositionCounter {}
pub fn set_recomposition_tracking(enabled: bool) {
#[cfg(all(feature = "inspection", debug_assertions))]
crate::thread_state::cells(|cells| cells.track_recompositions.set(enabled));
#[cfg(not(all(feature = "inspection", debug_assertions)))]
let _ = enabled;
}
#[cfg(all(feature = "inspection", debug_assertions))]
fn record_composition(
name: &'static str,
file: &'static str,
line: u32,
manifest_dir: &'static str,
) -> Option<RecompositionCounter> {
if !crate::thread_state::cells(|cells| cells.track_recompositions.get())
|| name.starts_with("__cranpose_call:")
{
return None;
}
crate::with_current_composer_opt(|composer| {
let scope = composer.current_recompose_scope()?;
let counter = scope.inner.recompositions.get_or_init(|| {
crate::thread_state::runtimes(|state| {
state
.recomposition_counters
.borrow_mut()
.push(scope.downgrade());
});
let instance_id = crate::thread_state::cells(|cells| {
let next = &cells.next_recomposition_instance;
let id = next.get().saturating_add(1);
next.set(id);
id
});
RecompositionCounter(std::rc::Rc::new(Counter {
executions: std::cell::Cell::new(0),
instance_id,
name,
file,
line,
manifest_dir,
}))
});
counter
.0
.executions
.set(counter.0.executions.get().saturating_add(1));
Some(counter.clone())
})
.flatten()
}
#[doc(hidden)]
pub struct SourceScope {
#[cfg(feature = "inspection")]
depth: usize,
marker: std::marker::PhantomData<std::rc::Rc<()>>,
}
impl Drop for SourceScope {
fn drop(&mut self) {
#[cfg(feature = "inspection")]
crate::thread_state::runtimes(|state| state.source_stack.borrow_mut().truncate(self.depth));
}
}
#[doc(hidden)]
#[inline]
pub fn __source_scope(
name: &'static str,
file: &'static str,
line: u32,
manifest_dir: &'static str,
) -> SourceScope {
#[cfg(feature = "inspection")]
let depth = crate::thread_state::runtimes(|state| {
let mut stack = state.source_stack.borrow_mut();
let depth = stack.len();
stack.push(SourceLocation {
name,
file,
line,
manifest_dir,
#[cfg(debug_assertions)]
recompositions: record_composition(name, file, line, manifest_dir),
});
depth
});
#[cfg(not(feature = "inspection"))]
let _ = (name, file, line, manifest_dir);
SourceScope {
#[cfg(feature = "inspection")]
depth,
marker: std::marker::PhantomData,
}
}
#[cfg(feature = "inspection")]
pub fn current_source_trace() -> std::rc::Rc<[SourceLocation]> {
crate::thread_state::runtimes(|state| std::rc::Rc::from(state.source_stack.borrow().as_slice()))
}
#[cfg(feature = "inspection")]
pub(crate) struct SourceContext(Vec<SourceLocation>);
#[cfg(feature = "inspection")]
impl Drop for SourceContext {
fn drop(&mut self) {
crate::thread_state::runtimes(|state| {
*state.source_stack.borrow_mut() = std::mem::take(&mut self.0);
});
}
}
#[cfg(feature = "inspection")]
pub(crate) fn restore_source_trace(trace: &[SourceLocation]) -> SourceContext {
SourceContext(crate::thread_state::runtimes(|state| {
state.source_stack.replace(trace.to_vec())
}))
}
#[cfg(all(test, feature = "inspection"))]
#[path = "tests/source_trace_tests.rs"]
mod tests;