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}