Skip to main content

oxihuman_core/
ref_counted.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Manual reference-counted resource table.
6
7use std::collections::HashMap;
8
9/// Entry in the ref-counted table.
10#[derive(Debug, Clone)]
11pub struct RefEntry<V> {
12    pub value: V,
13    pub ref_count: u32,
14}
15
16/// Table that tracks reference counts for shared resources.
17pub struct RefCounted<V> {
18    entries: HashMap<String, RefEntry<V>>,
19}
20
21#[allow(dead_code)]
22impl<V: Clone> RefCounted<V> {
23    pub fn new() -> Self {
24        RefCounted {
25            entries: HashMap::new(),
26        }
27    }
28
29    /// Insert with initial ref-count of 1. Returns false if already exists.
30    pub fn insert(&mut self, key: &str, value: V) -> bool {
31        if self.entries.contains_key(key) {
32            return false;
33        }
34        self.entries.insert(
35            key.to_string(),
36            RefEntry {
37                value,
38                ref_count: 1,
39            },
40        );
41        true
42    }
43
44    /// Increment ref-count. Returns new count, or None if not found.
45    pub fn acquire(&mut self, key: &str) -> Option<u32> {
46        let e = self.entries.get_mut(key)?;
47        e.ref_count += 1;
48        Some(e.ref_count)
49    }
50
51    /// Decrement ref-count. Removes entry when count reaches 0.
52    /// Returns remaining count, or None if not found.
53    pub fn release(&mut self, key: &str) -> Option<u32> {
54        let count = {
55            let e = self.entries.get_mut(key)?;
56            e.ref_count = e.ref_count.saturating_sub(1);
57            e.ref_count
58        };
59        if count == 0 {
60            self.entries.remove(key);
61        }
62        Some(count)
63    }
64
65    pub fn get(&self, key: &str) -> Option<&V> {
66        self.entries.get(key).map(|e| &e.value)
67    }
68
69    pub fn ref_count(&self, key: &str) -> Option<u32> {
70        self.entries.get(key).map(|e| e.ref_count)
71    }
72
73    pub fn contains(&self, key: &str) -> bool {
74        self.entries.contains_key(key)
75    }
76
77    pub fn len(&self) -> usize {
78        self.entries.len()
79    }
80
81    pub fn is_empty(&self) -> bool {
82        self.entries.is_empty()
83    }
84
85    pub fn total_refs(&self) -> u64 {
86        self.entries.values().map(|e| e.ref_count as u64).sum()
87    }
88
89    pub fn keys(&self) -> Vec<&str> {
90        self.entries.keys().map(|k| k.as_str()).collect()
91    }
92
93    pub fn clear(&mut self) {
94        self.entries.clear();
95    }
96}
97
98impl<V: Clone> Default for RefCounted<V> {
99    fn default() -> Self {
100        Self::new()
101    }
102}
103
104pub fn new_ref_counted<V: Clone>() -> RefCounted<V> {
105    RefCounted::new()
106}
107
108#[cfg(test)]
109mod tests {
110    use super::*;
111
112    #[test]
113    fn insert_and_get() {
114        let mut rc: RefCounted<i32> = new_ref_counted();
115        assert!(rc.insert("res", 42));
116        assert_eq!(*rc.get("res").expect("should succeed"), 42);
117    }
118
119    #[test]
120    fn duplicate_insert_fails() {
121        let mut rc: RefCounted<i32> = new_ref_counted();
122        rc.insert("k", 1);
123        assert!(!rc.insert("k", 2));
124    }
125
126    #[test]
127    fn acquire_increments() {
128        let mut rc: RefCounted<i32> = new_ref_counted();
129        rc.insert("k", 1);
130        assert_eq!(rc.acquire("k"), Some(2));
131        assert_eq!(rc.ref_count("k"), Some(2));
132    }
133
134    #[test]
135    fn release_decrements() {
136        let mut rc: RefCounted<i32> = new_ref_counted();
137        rc.insert("k", 1);
138        rc.acquire("k");
139        assert_eq!(rc.release("k"), Some(1));
140    }
141
142    #[test]
143    fn release_to_zero_removes() {
144        let mut rc: RefCounted<i32> = new_ref_counted();
145        rc.insert("k", 1);
146        rc.release("k");
147        assert!(!rc.contains("k"));
148    }
149
150    #[test]
151    fn total_refs() {
152        let mut rc: RefCounted<i32> = new_ref_counted();
153        rc.insert("a", 1);
154        rc.insert("b", 2);
155        rc.acquire("a");
156        assert_eq!(rc.total_refs(), 3);
157    }
158
159    #[test]
160    fn contains_check() {
161        let mut rc: RefCounted<i32> = new_ref_counted();
162        rc.insert("x", 0);
163        assert!(rc.contains("x"));
164        assert!(!rc.contains("y"));
165    }
166
167    #[test]
168    fn clear_empties() {
169        let mut rc: RefCounted<i32> = new_ref_counted();
170        rc.insert("a", 1);
171        rc.clear();
172        assert!(rc.is_empty());
173    }
174
175    #[test]
176    fn release_missing_is_none() {
177        let mut rc: RefCounted<i32> = new_ref_counted();
178        assert!(rc.release("ghost").is_none());
179    }
180}