Skip to main content

box2d_rust/
table.rs

1// Port of box2d-cpp-reference/src/table.h and src/table.c
2//
3// An open-addressing hash set of u64 keys (a key of 0 is the empty sentinel).
4// Linear probing for lookup, backward-shift deletion to keep probe chains tight.
5// The C `b2SetItem` is just a `uint64_t key`, so the backing store is a
6// `Vec<u64>` whose length is always the (power-of-two) capacity.
7//
8// b2CountSetBits, defined in this C file, lives in bitset.rs next to the type it
9// operates on.
10//
11// SPDX-FileCopyrightText: 2023 Erin Catto
12// SPDX-License-Identifier: MIT
13
14use crate::core::round_up_power_of2;
15
16/// Build a symmetric key from a pair of shape indices. (B2_SHAPE_PAIR_KEY)
17///
18/// The smaller index goes in the high 32 bits so `(a, b)` and `(b, a)` map to
19/// the same key.
20pub fn shape_pair_key(k1: i32, k2: i32) -> u64 {
21    if k1 < k2 {
22        (k1 as u64) << 32 | (k2 as u64)
23    } else {
24        (k2 as u64) << 32 | (k1 as u64)
25    }
26}
27
28/// An open-addressing hash set of non-zero u64 keys. (b2HashSet)
29#[derive(Debug, Clone, Default)]
30pub struct HashSet {
31    /// Backing slots; `items.len()` is the capacity. A slot of 0 is empty.
32    pub(crate) items: Vec<u64>,
33    pub(crate) count: u32,
34}
35
36// Murmur hash finalizer. A good mixer matters here because keys are built from
37// pairs of increasing integers, where a simple XOR hash collides heavily.
38fn key_hash(key: u64) -> u64 {
39    let mut h = key;
40    h ^= h >> 33;
41    h = h.wrapping_mul(0xff51afd7ed558ccd);
42    h ^= h >> 33;
43    h = h.wrapping_mul(0xc4ceb9fe1a85ec53);
44    h ^= h >> 33;
45    h
46}
47
48impl HashSet {
49    /// Create a set with at least `capacity` slots, rounded up to a power of two
50    /// (minimum 16). (b2CreateSet)
51    pub fn new(capacity: i32) -> HashSet {
52        // Capacity must be a power of 2
53        let capacity = if capacity > 16 {
54            round_up_power_of2(capacity)
55        } else {
56            16
57        };
58
59        HashSet {
60            items: vec![0; capacity as usize],
61            count: 0,
62        }
63    }
64
65    /// Release the storage. (b2DestroySet)
66    pub fn destroy(&mut self) {
67        self.items = Vec::new();
68        self.count = 0;
69    }
70
71    /// Remove all keys, keeping capacity. (b2ClearSet)
72    pub fn clear(&mut self) {
73        self.count = 0;
74        self.items.iter_mut().for_each(|slot| *slot = 0);
75    }
76
77    /// Number of keys in the set. (b2GetSetCount)
78    pub fn count(&self) -> i32 {
79        self.count as i32
80    }
81
82    /// Number of allocated slots. (b2GetSetCapacity)
83    pub fn capacity(&self) -> i32 {
84        self.items.len() as i32
85    }
86
87    /// Byte size of the backing storage. (b2GetHashSetBytes)
88    pub fn bytes(&self) -> i32 {
89        self.items.len() as i32 * core::mem::size_of::<u64>() as i32
90    }
91
92    fn cap(&self) -> u32 {
93        self.items.len() as u32
94    }
95
96    // Find the slot holding `key`, or the first empty slot on its probe chain.
97    fn find_slot(&self, key: u64, hash: u64) -> usize {
98        let capacity = self.cap();
99        let mut index = (hash as u32) & (capacity - 1);
100        while self.items[index as usize] != 0 && self.items[index as usize] != key {
101            index = (index + 1) & (capacity - 1);
102        }
103        index as usize
104    }
105
106    fn add_key_have_capacity(&mut self, key: u64, hash: u64) {
107        let index = self.find_slot(key, hash);
108        debug_assert!(self.items[index] == 0);
109        self.items[index] = key;
110        self.count += 1;
111    }
112
113    fn grow_table(&mut self) {
114        let old_items = core::mem::take(&mut self.items);
115
116        self.count = 0;
117        // Capacity must be a power of 2
118        self.items = vec![0; 2 * old_items.len()];
119
120        // Transfer items into new array
121        for &key in &old_items {
122            if key == 0 {
123                // this item was empty
124                continue;
125            }
126
127            let hash = key_hash(key);
128            self.add_key_have_capacity(key, hash);
129        }
130    }
131
132    /// True if `key` is present. (b2ContainsKey)
133    pub fn contains_key(&self, key: u64) -> bool {
134        // key of zero is a sentinel
135        debug_assert!(key != 0);
136        let hash = key_hash(key);
137        let index = self.find_slot(key, hash);
138        self.items[index] == key
139    }
140
141    /// Add `key`. Returns true if it was already present. (b2AddKey)
142    pub fn add_key(&mut self, key: u64) -> bool {
143        // key of zero is a sentinel
144        debug_assert!(key != 0);
145
146        let hash = key_hash(key);
147        debug_assert!(hash != 0);
148
149        let index = self.find_slot(key, hash);
150        if self.items[index] != 0 {
151            // Already in set
152            debug_assert!(self.items[index] == key);
153            return true;
154        }
155
156        if 2 * self.count >= self.cap() {
157            self.grow_table();
158        }
159
160        self.add_key_have_capacity(key, hash);
161        false
162    }
163
164    /// Remove `key`. Returns true if it was found. (b2RemoveKey)
165    // See https://en.wikipedia.org/wiki/Open_addressing
166    pub fn remove_key(&mut self, key: u64) -> bool {
167        let hash = key_hash(key);
168        let mut i = self.find_slot(key, hash);
169        if self.items[i] == 0 {
170            // Not in set
171            return false;
172        }
173
174        // Mark item i as unoccupied
175        self.items[i] = 0;
176
177        debug_assert!(self.count > 0);
178        self.count -= 1;
179
180        // Attempt to fill item i
181        let mask = self.items.len() - 1;
182        let mut j = i;
183        loop {
184            j = (j + 1) & mask;
185            if self.items[j] == 0 {
186                break;
187            }
188
189            // k is the first slot for the hash of j
190            let hash_j = key_hash(self.items[j]);
191            let k = (hash_j as usize) & mask;
192
193            // determine if k lies cyclically in (i,j]
194            // i <= j: | i..k..j |
195            // i > j: |.k..j  i....| or |....j     i..k.|
196            if i <= j {
197                if i < k && k <= j {
198                    continue;
199                }
200            } else if i < k || k <= j {
201                continue;
202            }
203
204            // Move j into i
205            self.items[i] = self.items[j];
206
207            // Mark item j as unoccupied
208            self.items[j] = 0;
209
210            i = j;
211        }
212
213        true
214    }
215}