BREP_kernel 0.13.1

A boundary representation (BREP) geometry kernel for building CAD applications.
Documentation
//! Reuse deterministic fits within one imprint, keyed by complete input nets.
//! A cache hit replays the original report into the normal diagnostic ledger.
use super::*;
use std::cell::RefCell;

#[derive(Default)]
struct FitCache {
    trace: Option<super::reuse_profile::Trace<Vec<u64>>>,
    rollover: bool,
    limit: usize,
    fits: HashMap<Vec<u64>, crate::PcurveFit>,
    requests: usize,
    hits: usize,
    evaluations: usize,
    key_time: Duration,
    lookup_time: Duration,
    fit_time: Duration,
    replay_time: Duration,
}

thread_local! {
    static CACHES: RefCell<Vec<Option<FitCache>>> = const { RefCell::new(Vec::new()) };
}

pub(super) fn enabled() -> bool {
    switch!(live "BREP_IMPRINT_PCURVE_CACHE", true)
}

/// Separate nested operations, including error returns, never retain a cache
/// on their caller. The only mutable state is local to this thread and scope.
pub(super) struct FitCacheScope {
    depth: usize,
}
impl FitCacheScope {
    pub(super) fn open(enabled: bool) -> Self {
        Self::open_limit(enabled, switch!("BREP_IMPRINT_PCURVE_CACHE_ROLLOVER", false),
            if crate::pcurve::larger_working_set() { 512 } else { 256 })
    }
    pub(super) fn open_config(enabled: bool, rollover: bool) -> Self {
        Self::open_limit(enabled, rollover, 256)
    }
    pub(super) fn open_limit(enabled: bool, rollover: bool, limit: usize) -> Self {
        let depth = CACHES.with(|stack| {
        let _wall = crate::wall_profile::Scope::enter(crate::wall_profile::LOOKUP);
            let mut stack = stack.borrow_mut();
            let depth = stack.len();
            stack.push(enabled.then(|| FitCache { rollover, limit, trace: super::reuse_profile::Trace::open("fit", || serde_json::json!({"lifetime":"imprint","limit":limit})), ..FitCache::default() }));
            depth
        });
        Self { depth }
    }
}
impl Drop for FitCacheScope {
    fn drop(&mut self) {
        CACHES.with(|stack| {
        let _wall = crate::wall_profile::Scope::enter(crate::wall_profile::LOOKUP);
            let mut stack = stack.borrow_mut();
            debug_assert_eq!(stack.len(), self.depth + 1);
            if let Some(Some(cache)) = stack.pop() {
                if profile_enabled() {
                    profile_line!("imprint.pcurve_cache requests={} hits={} evaluations={} retained={}",
                        cache.requests, cache.hits, cache.evaluations, cache.fits.len());
                    profile_line!("imprint.pcurve_cache_time key={:.3} lookup={:.3} fit={:.3} replay={:.3}",
                        cache.key_time.as_secs_f64() * 1000.0, cache.lookup_time.as_secs_f64() * 1000.0,
                        cache.fit_time.as_secs_f64() * 1000.0, cache.replay_time.as_secs_f64() * 1000.0);
                }
            }
        });
    }
}

/// Lengths delimit every field; equality includes every IEEE bit, including
/// signed zero. Recognition labels and pointer identity cannot alias a net.
fn key(surface: &NurbsSurface, curve: &NurbsCurve) -> Vec<u64> {
    let _wall = crate::wall_profile::Scope::enter(crate::wall_profile::LOOKUP);
    fn floats(key: &mut Vec<u64>, values: &[f64]) {
        key.push(values.len() as u64);
        key.extend(values.iter().map(|v| v.to_bits()));
    }
    fn controls(key: &mut Vec<u64>, values: &[Vec4]) {
        key.push(values.len() as u64);
        for p in values { key.extend([p.x.to_bits(), p.y.to_bits(), p.z.to_bits(), p.w.to_bits()]); }
    }
    // Imprint face charts are borrowed for the entire scope. Keeping their
    // identity as well as every net bit prevents reuse across distinct
    // objects carrying different lazy projection/recognition cache states.
    let mut key = vec![surface as *const NurbsSurface as usize as u64,
        surface.degree_u as u64, surface.degree_v as u64];
    floats(&mut key, &surface.knots_u);
    floats(&mut key, &surface.knots_v);
    key.push(surface.control_points.len() as u64);
    for row in &surface.control_points { controls(&mut key, row); }
    key.push(curve.degree as u64);
    floats(&mut key, &curve.knots);
    controls(&mut key, &curve.control_points);
    // Other fit-policy switches are process OnceLocks; this one is dynamic.
    key.push((env_read!("BREP_SECTION_PCURVE_MARCH").as_deref() == Some("0")) as u64);
    key
}

#[track_caller]
pub(super) fn cached_build_pcurve(surface: &NurbsSurface, curve: &NurbsCurve) -> Result<NurbsCurve, String> {
    let caller = std::panic::Location::caller();
    let active = CACHES.with(|stack| stack.borrow().last().is_some_and(Option::is_some));
    if !active {
        return crate::build_pcurve_on_surface_marched(surface, curve);
    }
    let profiling = profile_enabled();
    let mut clock = profiling.then(Instant::now);
    let mut lap = || {
        let now = profiling.then(Instant::now);
        let elapsed = clock.zip(now).map_or(Duration::ZERO, |(before, after)| after - before);
        clock = now;
        elapsed
    };
    let key = key(surface, curve);
    let key_time = lap();
    let hit = CACHES.with(|stack| {
        let _wall = crate::wall_profile::Scope::enter(crate::wall_profile::LOOKUP);
        let mut stack = stack.borrow_mut();
        let cache = stack.last_mut().unwrap().as_mut().unwrap();
        cache.requests += 1;
        let hit = cache.fits.get(&key).cloned();
        if hit.is_some() { if let Some(trace) = &mut cache.trace { trace.access(&key, caller, true, true); } cache.hits += 1; } else { cache.evaluations += 1; }
        hit
    });
    let lookup_time = lap();
    let fit = match hit {
        Some(fit) => fit,
        None => {
            let result = crate::fit_pcurve_on_surface_marched(surface, curve);
            let fit = match result {
                Ok(fit) => fit,
                Err(error) => {
                    CACHES.with(|stack| {
        let _wall = crate::wall_profile::Scope::enter(crate::wall_profile::LOOKUP); if let Some(trace) = &mut stack.borrow_mut().last_mut().unwrap().as_mut().unwrap().trace {
                        trace.access(&key, caller, false, false);
                    } });
                    return Err(error);
                }
            };
            CACHES.with(|stack| {
        let _wall = crate::wall_profile::Scope::enter(crate::wall_profile::LOOKUP);
                let mut stack = stack.borrow_mut();
                let cache = stack.last_mut().unwrap().as_mut().unwrap();
                if let Some(trace) = &mut cache.trace { trace.access(&key, caller, false, true); }
                // Keep the selected entry bound (256 normally, 512 in the
                // measured working-set experiment). The experimental replacement
                // policy admits recent fits after saturation instead of
                // recomputing every later miss, including its immediate reuse.
                // Eviction changes no fit or report: a miss recomputes both.
                if cache.rollover && cache.fits.len() >= cache.limit { cache.fits.clear(); }
                if cache.fits.len() < cache.limit { cache.fits.insert(key, fit.clone()); }
            });
            fit
        }
    };
    let fit_time = lap();
    crate::pcurve::record_fit(&fit.report);
    let replay_time = lap();
    if profiling {
        CACHES.with(|stack| {
        let _wall = crate::wall_profile::Scope::enter(crate::wall_profile::LOOKUP);
            let mut stack = stack.borrow_mut();
            let cache = stack.last_mut().unwrap().as_mut().unwrap();
            cache.key_time += key_time;
            cache.lookup_time += lookup_time;
            cache.fit_time += fit_time;
            cache.replay_time += replay_time;
        });
    }
    Ok(fit.curve)
}