Skip to main content

ferroui_base/utilities/
weak_hash_list.rs

1use std::any::{Any, TypeId};
2use std::cell::{Cell, RefCell};
3use std::collections::HashMap;
4use std::hash::{Hash, Hasher};
5use std::rc::{Rc, Weak};
6
7/// A list of weakly referenced items, compared by identity.
8///
9/// Up to [`DEFAULT_ARRAY_SIZE`](Self::DEFAULT_ARRAY_SIZE) items are kept in
10/// an array; beyond that the list switches to a dictionary that counts how
11/// many times each item was added.
12pub struct WeakHashList<T: ?Sized + 'static> {
13    dic: Option<OrderedDic<T>>,
14    arr: Option<Vec<Option<Weak<T>>>>,
15    arr_count: usize,
16    need_compact: Cell<bool>,
17}
18
19/// An entry of the dictionary storage: a weak reference for stored entries,
20/// a strong one for lookups.
21struct Key<T: ?Sized> {
22    weak: Option<Weak<T>>,
23    strong: Option<Rc<T>>,
24    hash_code: usize,
25}
26
27impl<T: ?Sized> Key<T> {
28    fn make_strong(r: &Rc<T>) -> Self {
29        Self { hash_code: address(Rc::as_ptr(r)), weak: None, strong: Some(r.clone()) }
30    }
31
32    fn make_weak(r: &Rc<T>) -> Self {
33        Self { hash_code: address(Rc::as_ptr(r)), weak: Some(Rc::downgrade(r)), strong: None }
34    }
35}
36
37impl<T: ?Sized> Clone for Key<T> {
38    fn clone(&self) -> Self {
39        Self { weak: self.weak.clone(), strong: self.strong.clone(), hash_code: self.hash_code }
40    }
41}
42
43/// The dictionary storage: a hash index over entry slots, so that
44/// enumeration follows insertion order, and a removed slot is reused by the
45/// next addition (most recently freed first), as the managed dictionary does.
46struct OrderedDic<T: ?Sized> {
47    entries: Vec<Option<(Key<T>, i32)>>,
48    free: Vec<usize>,
49    map: HashMap<Key<T>, usize>,
50}
51
52impl<T: ?Sized> OrderedDic<T> {
53    fn new() -> Self {
54        Self { entries: Vec::new(), free: Vec::new(), map: HashMap::new() }
55    }
56
57    fn is_empty(&self) -> bool {
58        self.map.is_empty()
59    }
60
61    fn get(&self, key: &Key<T>) -> Option<i32> {
62        self.map.get(key).and_then(|&slot| self.entries[slot].as_ref()).map(|(_, count)| *count)
63    }
64
65    /// Sets the count of `key`, adding the entry if it is not present. An
66    /// existing entry keeps its key.
67    fn set(&mut self, key: Key<T>, count: i32) {
68        if let Some(&slot) = self.map.get(&key) {
69            if let Some(entry) = &mut self.entries[slot] {
70                entry.1 = count;
71            }
72            return;
73        }
74        let slot = match self.free.pop() {
75            Some(slot) => {
76                self.entries[slot] = Some((key.clone(), count));
77                slot
78            }
79            None => {
80                self.entries.push(Some((key.clone(), count)));
81                self.entries.len() - 1
82            }
83        };
84        self.map.insert(key, slot);
85    }
86
87    fn remove(&mut self, key: &Key<T>) {
88        if let Some(slot) = self.map.remove(key) {
89            self.entries[slot] = None;
90            self.free.push(slot);
91        }
92    }
93
94    /// The keys, in enumeration order.
95    fn keys(&self) -> impl Iterator<Item = &Key<T>> {
96        self.entries.iter().flatten().map(|(key, _)| key)
97    }
98}
99
100impl<T: ?Sized> Hash for Key<T> {
101    fn hash<H: Hasher>(&self, state: &mut H) {
102        self.hash_code.hash(state);
103    }
104}
105
106/// The key comparer of the dictionary storage.
107impl<T: ?Sized> PartialEq for Key<T> {
108    fn eq(&self, y: &Self) -> bool {
109        let x = self;
110        if x.hash_code != y.hash_code {
111            return false;
112        }
113        if let Some(x_strong) = &x.strong {
114            if let Some(y_strong) = &y.strong {
115                return Rc::ptr_eq(x_strong, y_strong);
116            }
117            let Some(y_weak) = &y.weak else { return false };
118            return y_weak.upgrade().is_some_and(|weak_target| Rc::ptr_eq(&weak_target, x_strong));
119        } else if let Some(y_strong) = &y.strong {
120            let Some(x_weak) = &x.weak else { return false };
121            return x_weak.upgrade().is_some_and(|weak_target| Rc::ptr_eq(&weak_target, y_strong));
122        }
123        match x.weak.as_ref().and_then(Weak::upgrade) {
124            None => y.weak.as_ref().and_then(Weak::upgrade).is_none(),
125            Some(x_target) => y.weak.as_ref().and_then(Weak::upgrade).is_some_and(|y_target| Rc::ptr_eq(&x_target, &y_target)),
126        }
127    }
128}
129
130impl<T: ?Sized> Eq for Key<T> {}
131
132fn address<T: ?Sized>(ptr: *const T) -> usize {
133    ptr as *const () as usize
134}
135
136crate::ferro_thread_local! {
137    /// The shared pools of lists returned by `get_alive`, one per item type.
138    static LIST_POOLS: RefCell<HashMap<TypeId, Box<dyn Any>>> = RefCell::new(HashMap::new());
139}
140
141impl<T: ?Sized + 'static> Default for WeakHashList<T> {
142    fn default() -> Self {
143        Self::new()
144    }
145}
146
147impl<T: ?Sized + 'static> WeakHashList<T> {
148    pub const DEFAULT_ARRAY_SIZE: usize = 8;
149
150    pub fn new() -> Self {
151        Self { dic: None, arr: None, arr_count: 0, need_compact: Cell::new(false) }
152    }
153
154    pub fn is_empty(&self) -> bool {
155        match &self.dic {
156            Some(dic) => dic.is_empty(),
157            None => self.arr_count == 0,
158        }
159    }
160
161    pub fn need_compact(&self) -> bool {
162        self.need_compact.get()
163    }
164
165    pub fn add(&mut self, item: &Rc<T>) {
166        if let Some(dic) = &mut self.dic {
167            let strong_key = Key::make_strong(item);
168            if let Some(cnt) = dic.get(&strong_key) {
169                dic.set(strong_key, cnt + 1);
170            } else {
171                dic.set(Key::make_weak(item), 1);
172            }
173            return;
174        }
175
176        let arr = self.arr.get_or_insert_with(|| vec![None; Self::DEFAULT_ARRAY_SIZE]);
177
178        if self.arr_count < arr.len() {
179            arr[self.arr_count] = Some(Rc::downgrade(item));
180            self.arr_count += 1;
181            return;
182        }
183
184        // Check if something is dead
185        for c in 0..self.arr_count {
186            if arr[c].as_ref().and_then(Weak::upgrade).is_none() {
187                arr[c] = Some(Rc::downgrade(item));
188                return;
189            }
190        }
191
192        let existing: Vec<Rc<T>> = arr.iter().filter_map(|r| r.as_ref().and_then(Weak::upgrade)).collect();
193        self.dic = Some(OrderedDic::new());
194        for target in &existing {
195            self.add(target);
196        }
197
198        self.add(item);
199
200        self.arr = None;
201        self.arr_count = 0;
202    }
203
204    pub fn remove(&mut self, item: &Rc<T>) {
205        if let Some(arr) = &mut self.arr {
206            for c in 0..self.arr_count {
207                if arr[c].as_ref().and_then(Weak::upgrade).is_some_and(|target| Rc::ptr_eq(&target, item)) {
208                    arr[c] = None;
209                    self.arr_compact();
210                    return;
211                }
212            }
213        } else if let Some(dic) = &mut self.dic {
214            let strong_key = Key::make_strong(item);
215
216            if let Some(cnt) = dic.get(&strong_key) {
217                if cnt > 1 {
218                    dic.set(strong_key, cnt - 1);
219                    return;
220                }
221            }
222
223            dic.remove(&strong_key);
224        }
225    }
226
227    fn arr_compact(&mut self) {
228        if let Some(arr) = &mut self.arr {
229            let mut empty: Option<usize> = None;
230            for c in 0..self.arr_count {
231                let r = arr[c].clone();
232                // Mark current index as first empty
233                if r.is_none() && empty.is_none() {
234                    empty = Some(c);
235                }
236                // If current element isn't null and we have an empty one
237                if let (Some(_), Some(e)) = (&r, empty) {
238                    arr[c] = None;
239                    arr[e] = r;
240                    empty = Some(e + 1);
241                }
242            }
243
244            if let Some(empty) = empty {
245                self.arr_count = empty;
246            }
247        }
248    }
249
250    pub fn compact(&mut self) {
251        if let Some(dic) = &mut self.dic {
252            let to_remove: Vec<Key<T>> = dic
253                .keys()
254                .filter(|key| key.weak.as_ref().and_then(Weak::upgrade).is_none())
255                .map(|key| Key { weak: key.weak.clone(), strong: None, hash_code: key.hash_code })
256                .collect();
257
258            for k in &to_remove {
259                dic.remove(k);
260            }
261        }
262    }
263
264    /// Clears `list` and keeps it for a later [`get_alive`](Self::get_alive).
265    pub fn return_to_shared_pool(mut list: Vec<Rc<T>>) {
266        list.clear();
267        LIST_POOLS.with(|pools| {
268            pools
269                .borrow_mut()
270                .entry(TypeId::of::<T>())
271                .or_insert_with(|| Box::new(Vec::<Vec<Rc<T>>>::new()))
272                .downcast_mut::<Vec<Vec<Rc<T>>>>()
273                .expect("the pool of an item type holds lists of that type")
274                .push(list);
275        });
276    }
277
278    fn rent_from_shared_pool() -> Vec<Rc<T>> {
279        LIST_POOLS
280            .with(|pools| {
281                pools
282                    .borrow_mut()
283                    .get_mut(&TypeId::of::<T>())
284                    .and_then(|pool| pool.downcast_mut::<Vec<Vec<Rc<T>>>>())
285                    .and_then(Vec::pop)
286            })
287            .unwrap_or_default()
288    }
289
290    /// The items that are still alive, or `None` if there are none. The list
291    /// comes from `factory` when given, else from the shared pool.
292    pub fn get_alive(&mut self, factory: Option<&dyn Fn() -> Vec<Rc<T>>>) -> Option<Vec<Rc<T>>> {
293        let mut pooled: Option<Vec<Rc<T>>> = None;
294        let mut new_list = || match factory {
295            Some(factory) => factory(),
296            None => Self::rent_from_shared_pool(),
297        };
298        if let Some(arr) = &mut self.arr {
299            let mut need_compact = false;
300            for c in 0..self.arr_count {
301                match arr[c].as_ref().and_then(Weak::upgrade) {
302                    Some(target) => pooled.get_or_insert_with(&mut new_list).push(target),
303                    None => {
304                        arr[c] = None;
305                        need_compact = true;
306                    }
307                }
308            }
309            if need_compact {
310                self.arr_compact();
311            }
312            return pooled;
313        }
314        if let Some(dic) = &self.dic {
315            for key in dic.keys() {
316                match key.weak.as_ref().and_then(Weak::upgrade) {
317                    Some(target) => pooled.get_or_insert_with(&mut new_list).push(target),
318                    None => self.need_compact.set(true),
319                }
320            }
321        }
322
323        pooled
324    }
325}
326
327#[cfg(test)]
328mod tests {
329    use super::*;
330
331    // The managed tests add and remove strings by value; their small number
332    // strings are cached instances, so the same object is added and removed.
333    // The handles below play that part.
334    fn strings(count: usize) -> Vec<Rc<str>> {
335        (0..count).map(|i| Rc::from(i.to_string())).collect()
336    }
337
338    #[test]
339    fn is_empty_works() {
340        let mut target = WeakHashList::<str>::new();
341        let one: Rc<str> = Rc::from("1");
342
343        assert!(target.is_empty());
344
345        target.add(&one);
346
347        assert!(!target.is_empty());
348
349        target.remove(&one);
350
351        assert!(target.is_empty());
352
353        // Fill array storage.
354        let arr_max_size = WeakHashList::<str>::DEFAULT_ARRAY_SIZE;
355        let items = strings(arr_max_size + 1);
356
357        for item in items.iter().take(arr_max_size) {
358            target.add(item);
359        }
360
361        assert!(!target.is_empty());
362
363        // This goes above array storage and upgrades to a dictionary.
364        target.add(&items[arr_max_size]);
365
366        assert!(!target.is_empty());
367
368        // Remove everything, this should still keep an empty dictionary.
369        for item in items.iter().take(arr_max_size + 1) {
370            target.remove(item);
371        }
372
373        assert!(target.is_empty());
374    }
375
376    #[test]
377    fn array_compact_after_remove_works() {
378        let mut target = WeakHashList::<str>::new();
379
380        // Use all slots in array storage.
381        let arr_max_size = WeakHashList::<str>::DEFAULT_ARRAY_SIZE;
382        let items = strings(arr_max_size);
383
384        for item in &items {
385            target.add(item);
386        }
387
388        // This should compact the array.
389        target.remove(&items[3]);
390
391        // And new value should fill empty space.
392        let forty_two: Rc<str> = Rc::from("42");
393        target.add(&forty_two);
394    }
395
396    /// Not from upstream: the dictionary storage enumerates in insertion
397    /// order and reuses the most recently freed slot, as the managed
398    /// dictionary does.
399    #[test]
400    fn dictionary_storage_keeps_insertion_order() {
401        let mut target = WeakHashList::<str>::new();
402        let items = strings(WeakHashList::<str>::DEFAULT_ARRAY_SIZE + 4);
403        for item in &items {
404            target.add(item);
405        }
406
407        target.remove(&items[2]);
408        let added: Rc<str> = Rc::from("added");
409        target.add(&added);
410
411        let mut expected = items.clone();
412        expected[2] = added.clone();
413        let alive = target.get_alive(None).unwrap();
414        assert_eq!(alive.len(), expected.len());
415        assert!(alive.iter().zip(&expected).all(|(a, e)| Rc::ptr_eq(a, e)));
416    }
417}