Skip to main content

fast_mesh_bvh/
error.rs

1use std::fmt;
2
3use fast_mesh_store::StoreError;
4
5/// Errors returned by BVH operations. On any error the BVH is left exactly as it
6/// was before the call: every operation reserves the memory it needs before it
7/// changes the tree (best-effort steps that run afterwards are skipped silently
8/// instead of failing).
9#[derive(Debug, Clone, PartialEq, Eq)]
10pub enum BvhError {
11    /// The mesh is not the one this BVH was built from.
12    MeshMismatch,
13    /// An option is out of range; the message says which.
14    InvalidOptions(&'static str),
15    /// A memory allocation failed.
16    OutOfMemory,
17    /// The mesh store reported an error.
18    Store(StoreError),
19}
20
21impl fmt::Display for BvhError {
22    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
23        match self {
24            Self::MeshMismatch => write!(f, "mesh is not the one this BVH was built from"),
25            Self::InvalidOptions(msg) => write!(f, "invalid BVH options: {msg}"),
26            Self::OutOfMemory => write!(f, "out of memory"),
27            Self::Store(e) => write!(f, "mesh store error: {e}"),
28        }
29    }
30}
31
32impl std::error::Error for BvhError {}
33
34impl From<StoreError> for BvhError {
35    fn from(e: StoreError) -> Self {
36        match e {
37            StoreError::OutOfMemory => Self::OutOfMemory,
38            other => Self::Store(other),
39        }
40    }
41}
42
43pub type Result<T> = std::result::Result<T, BvhError>;
44
45#[cfg(test)]
46thread_local! {
47    /// Test-only: how many more allocations may succeed (`None` = unlimited).
48    static ALLOCATIONS_LEFT: std::cell::Cell<Option<usize>> = const { std::cell::Cell::new(None) };
49}
50
51/// Test-only: make every allocation after the next `n` fail with `OutOfMemory`
52/// on this thread (`None` turns it off). While armed, parallel work runs on
53/// this thread so the hook sees every allocation.
54#[cfg(test)]
55pub(crate) fn fail_allocations_after(n: Option<usize>) {
56    // Only on arming or disarming: the count must see one `true` per arm.
57    let was = ALLOCATIONS_LEFT.with(|c| c.replace(n)).is_some();
58    if was != n.is_some() {
59        fast_mesh_store::par::set_force_sequential(n.is_some());
60    }
61}
62
63/// Checks the test fault hook; counts as one allocation. Always succeeds
64/// outside tests.
65#[cfg(test)]
66pub(crate) fn allocation_allowed() -> Result<()> {
67    ALLOCATIONS_LEFT.with(|c| match c.get() {
68        Some(0) => Err(BvhError::OutOfMemory),
69        Some(n) => {
70            c.set(Some(n - 1));
71            Ok(())
72        }
73        None => Ok(()),
74    })
75}
76
77#[cfg(not(test))]
78pub(crate) fn allocation_allowed() -> Result<()> {
79    Ok(())
80}
81
82pub(crate) fn try_reserve<T>(v: &mut Vec<T>, additional: usize) -> Result<()> {
83    allocation_allowed()?;
84    v.try_reserve(additional).map_err(|_| BvhError::OutOfMemory)
85}
86
87pub(crate) fn try_reserve_exact<T>(v: &mut Vec<T>, additional: usize) -> Result<()> {
88    allocation_allowed()?;
89    v.try_reserve_exact(additional)
90        .map_err(|_| BvhError::OutOfMemory)
91}
92
93pub(crate) fn try_reserve_map<K: Eq + std::hash::Hash, V>(
94    m: &mut std::collections::HashMap<K, V>,
95    additional: usize,
96) -> Result<()> {
97    allocation_allowed()?;
98    m.try_reserve(additional).map_err(|_| BvhError::OutOfMemory)
99}
100
101pub(crate) fn try_vec<T: Clone>(len: usize, value: T) -> Result<Vec<T>> {
102    allocation_allowed()?;
103    let mut v = Vec::new();
104    v.try_reserve_exact(len)
105        .map_err(|_| BvhError::OutOfMemory)?;
106    v.resize(len, value);
107    Ok(v)
108}
109
110#[cfg(test)]
111mod tests {
112    use super::*;
113
114    #[test]
115    fn messages_name_the_problem() {
116        assert_eq!(
117            BvhError::InvalidOptions("max_leaf_size must be 1 to 16").to_string(),
118            "invalid BVH options: max_leaf_size must be 1 to 16"
119        );
120        assert_eq!(
121            BvhError::from(StoreError::OutOfMemory),
122            BvhError::OutOfMemory
123        );
124        assert_eq!(
125            BvhError::from(StoreError::InvalidVertex(3)).to_string(),
126            "mesh store error: vertex 3 does not exist"
127        );
128    }
129}