Skip to main content

oxihuman_core/
name_table.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Name table: bidirectional map between string names and u32 IDs.
6
7use std::collections::HashMap;
8
9/// Bidirectional name↔ID table.
10#[derive(Debug, Clone)]
11#[allow(dead_code)]
12pub struct NameTable {
13    name_to_id: HashMap<String, u32>,
14    id_to_name: HashMap<u32, String>,
15    next_id: u32,
16}
17
18/// Create a new NameTable.
19#[allow(dead_code)]
20pub fn new_name_table() -> NameTable {
21    NameTable {
22        name_to_id: HashMap::new(),
23        id_to_name: HashMap::new(),
24        next_id: 1,
25    }
26}
27
28/// Register a name; returns existing ID if already registered.
29#[allow(dead_code)]
30pub fn nt_register(nt: &mut NameTable, name: &str) -> u32 {
31    if let Some(&id) = nt.name_to_id.get(name) {
32        return id;
33    }
34    let id = nt.next_id;
35    nt.next_id += 1;
36    nt.name_to_id.insert(name.to_string(), id);
37    nt.id_to_name.insert(id, name.to_string());
38    id
39}
40
41/// Look up ID by name.
42#[allow(dead_code)]
43pub fn nt_id(nt: &NameTable, name: &str) -> Option<u32> {
44    nt.name_to_id.get(name).copied()
45}
46
47/// Look up name by ID.
48#[allow(dead_code)]
49pub fn nt_name(nt: &NameTable, id: u32) -> Option<&str> {
50    nt.id_to_name.get(&id).map(|s| s.as_str())
51}
52
53/// Whether a name is registered.
54#[allow(dead_code)]
55pub fn nt_has_name(nt: &NameTable, name: &str) -> bool {
56    nt.name_to_id.contains_key(name)
57}
58
59/// Whether an ID is registered.
60#[allow(dead_code)]
61pub fn nt_has_id(nt: &NameTable, id: u32) -> bool {
62    nt.id_to_name.contains_key(&id)
63}
64
65/// Unregister a name; returns the ID if it existed.
66#[allow(dead_code)]
67pub fn nt_unregister(nt: &mut NameTable, name: &str) -> Option<u32> {
68    if let Some(id) = nt.name_to_id.remove(name) {
69        nt.id_to_name.remove(&id);
70        Some(id)
71    } else {
72        None
73    }
74}
75
76/// Number of registered names.
77#[allow(dead_code)]
78pub fn nt_count(nt: &NameTable) -> usize {
79    nt.name_to_id.len()
80}
81
82/// All registered names.
83#[allow(dead_code)]
84pub fn nt_names(nt: &NameTable) -> Vec<&str> {
85    nt.name_to_id.keys().map(|s| s.as_str()).collect()
86}
87
88/// Clear all entries.
89#[allow(dead_code)]
90pub fn nt_clear(nt: &mut NameTable) {
91    nt.name_to_id.clear();
92    nt.id_to_name.clear();
93}
94
95/// Rename: update name for an existing ID.
96#[allow(dead_code)]
97pub fn nt_rename(nt: &mut NameTable, id: u32, new_name: &str) -> bool {
98    if let Some(old_name) = nt.id_to_name.get(&id).cloned() {
99        if nt.name_to_id.contains_key(new_name) {
100            return false; // name conflict
101        }
102        nt.name_to_id.remove(&old_name);
103        nt.name_to_id.insert(new_name.to_string(), id);
104        nt.id_to_name.insert(id, new_name.to_string());
105        true
106    } else {
107        false
108    }
109}
110
111#[cfg(test)]
112mod tests {
113    use super::*;
114
115    #[test]
116    fn test_register_and_lookup() {
117        let mut nt = new_name_table();
118        let id = nt_register(&mut nt, "bone_arm");
119        assert_eq!(nt_id(&nt, "bone_arm"), Some(id));
120        assert_eq!(nt_name(&nt, id), Some("bone_arm"));
121    }
122
123    #[test]
124    fn test_idempotent_register() {
125        let mut nt = new_name_table();
126        let a = nt_register(&mut nt, "x");
127        let b = nt_register(&mut nt, "x");
128        assert_eq!(a, b);
129        assert_eq!(nt_count(&nt), 1);
130    }
131
132    #[test]
133    fn test_has_name_and_id() {
134        let mut nt = new_name_table();
135        let id = nt_register(&mut nt, "mesh");
136        assert!(nt_has_name(&nt, "mesh"));
137        assert!(nt_has_id(&nt, id));
138    }
139
140    #[test]
141    fn test_unregister() {
142        let mut nt = new_name_table();
143        let id = nt_register(&mut nt, "tmp");
144        let removed = nt_unregister(&mut nt, "tmp");
145        assert_eq!(removed, Some(id));
146        assert!(!nt_has_name(&nt, "tmp"));
147    }
148
149    #[test]
150    fn test_count() {
151        let mut nt = new_name_table();
152        nt_register(&mut nt, "a");
153        nt_register(&mut nt, "b");
154        assert_eq!(nt_count(&nt), 2);
155    }
156
157    #[test]
158    fn test_clear() {
159        let mut nt = new_name_table();
160        nt_register(&mut nt, "x");
161        nt_clear(&mut nt);
162        assert_eq!(nt_count(&nt), 0);
163    }
164
165    #[test]
166    fn test_rename() {
167        let mut nt = new_name_table();
168        let id = nt_register(&mut nt, "old");
169        assert!(nt_rename(&mut nt, id, "new"));
170        assert_eq!(nt_name(&nt, id), Some("new"));
171        assert!(!nt_has_name(&nt, "old"));
172    }
173
174    #[test]
175    fn test_rename_conflict() {
176        let mut nt = new_name_table();
177        let id = nt_register(&mut nt, "a");
178        nt_register(&mut nt, "b");
179        assert!(!nt_rename(&mut nt, id, "b"));
180    }
181
182    #[test]
183    fn test_names_list() {
184        let mut nt = new_name_table();
185        nt_register(&mut nt, "alpha");
186        nt_register(&mut nt, "beta");
187        assert_eq!(nt_names(&nt).len(), 2);
188    }
189}