1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
// Port of the name cache from box3d-cpp-reference/src/name_cache.h/.c.
// Remaining load/replay helpers land with the recording slice.
//
// SPDX-FileCopyrightText: 2025 Erin Catto
// SPDX-License-Identifier: MIT
use crate::constants::NULL_NAME;
use std::collections::HashMap;
/// A single name entry. (b3NameEntry)
#[derive(Debug, Clone, Default)]
pub struct NameEntry {
pub hash: u32,
pub length: i32,
pub name: String,
}
/// Name cache for shape and body names. Works with recording.
/// (b3NameCache)
///
/// C stores a verstable map as `void* map`; Rust uses a std HashMap keyed by
/// name id for the same id→entry association. Observable name↔id behavior will
/// match once the logic slice lands.
#[derive(Debug, Clone, Default)]
pub struct NameCache {
pub entries: Vec<NameEntry>,
/// Maps name id → entry index. Id 0 is [`NULL_NAME`] and is never inserted.
pub map: HashMap<u32, i32>,
}
impl NameCache {
/// (b3CreateNameCache)
pub fn new() -> NameCache {
NameCache {
entries: Vec::new(),
map: HashMap::new(),
}
}
/// Sentinel unused by real names. (B3_NULL_NAME)
pub const NULL: u32 = NULL_NAME;
/// FNV-1a + Murmur3 finalizer. Changing this breaks recordings. (b3Hash32)
pub fn hash32(data: &[u8]) -> u32 {
const FNV32_OFFSET_BASIS: u32 = 0x811c_9dc5;
const FNV32_PRIME: u32 = 0x0100_0193;
let mut h = FNV32_OFFSET_BASIS;
for &b in data {
h ^= b as u32;
h = h.wrapping_mul(FNV32_PRIME);
}
h ^= h >> 16;
h = h.wrapping_mul(0x85eb_ca6b);
h ^= h >> 13;
h = h.wrapping_mul(0xc2b2_ae35);
h ^= h >> 16;
h
}
/// Insert or look up a name, returning its id. Empty names yield NULL.
/// (b3AddName)
pub fn add_name(&mut self, name: &str) -> u32 {
if name.is_empty() {
return NULL_NAME;
}
let mut id = Self::hash32(name.as_bytes());
if id == 0 {
id = 1;
}
if let Some(&index) = self.map.get(&id) {
if self.entries[index as usize].name != name {
// Different name, same hash — C logs; keep returning the id.
}
return id;
}
let index = self.entries.len() as i32;
self.entries.push(NameEntry {
hash: id,
length: name.len() as i32,
name: name.to_string(),
});
self.map.insert(id, index);
id
}
/// (b3FindName)
pub fn find_name(&self, id: u32) -> Option<&str> {
let index = *self.map.get(&id)?;
Some(&self.entries[index as usize].name)
}
}