Skip to main content

box3d_rust/hull/
database.rs

1//! World hull database: content-keyed sharing with reference counts.
2//!
3//! Port of `b3AddHullToDatabase` / `b3RemoveHullFromDatabase` from
4//! `physics_world.c`. C uses a verstable map keyed by hull pointer with
5//! content hash/equality; Rust keeps canonical `Rc<HullData>` entries and
6//! deduplicates with [`compare_hull_data`].
7//!
8//! SPDX-FileCopyrightText: 2025 Erin Catto
9//! SPDX-License-Identifier: MIT
10
11use super::{clone_hull, compare_hull_data, destroy_hull, hash_hull_data, HullData};
12use std::rc::Rc;
13
14/// Shared hull store for a world. (b3HullMap + world->hullDatabase)
15#[derive(Debug, Default)]
16pub struct HullDatabase {
17    /// Canonical strong refs — one per unique hull content.
18    entries: Vec<Rc<HullData>>,
19}
20
21impl HullDatabase {
22    pub fn new() -> Self {
23        Self {
24            entries: Vec::new(),
25        }
26    }
27
28    /// Find a shared copy matching `src` by content, if any.
29    fn find(&self, src: &HullData) -> Option<Rc<HullData>> {
30        let h = hash_hull_data(src);
31        for entry in &self.entries {
32            if hash_hull_data(entry) == h && compare_hull_data(entry, src) {
33                return Some(Rc::clone(entry));
34            }
35        }
36        None
37    }
38
39    /// Clone `src` into the database on miss; share on hit.
40    /// (b3AddHullToDatabase)
41    pub fn add(&mut self, src: &HullData) -> Rc<HullData> {
42        if let Some(existing) = self.find(src) {
43            return existing;
44        }
45
46        let owned = clone_hull(src).expect("valid hull");
47        let rc = Rc::new(owned);
48        self.entries.push(Rc::clone(&rc));
49        rc
50    }
51
52    /// Take ownership of `owned` on miss; destroy the duplicate on hit.
53    /// (b3AddOwnedHullToDatabase)
54    pub fn add_owned(&mut self, owned: HullData) -> Rc<HullData> {
55        if let Some(existing) = self.find(&owned) {
56            destroy_hull(owned);
57            return existing;
58        }
59
60        let rc = Rc::new(owned);
61        self.entries.push(Rc::clone(&rc));
62        rc
63    }
64
65    /// Drop the database's strong ref once the last shape releases its clone.
66    /// Call while the shape still holds its `Rc` (strong_count >= 2 if DB also holds).
67    /// (b3RemoveHullFromDatabase)
68    pub fn release(&mut self, hull: &Rc<HullData>) {
69        // Shape + DB = 2. After this release, only the shape's Rc remains (or none).
70        if Rc::strong_count(hull) == 2 {
71            self.entries.retain(|e| !Rc::ptr_eq(e, hull));
72        }
73    }
74
75    pub fn is_empty(&self) -> bool {
76        self.entries.is_empty()
77    }
78
79    pub fn len(&self) -> usize {
80        self.entries.len()
81    }
82}