1use std::collections::{BTreeMap, BTreeSet};
10
11use serde::{Deserialize, Serialize};
12
13use crate::abi::{EntityId, TypeCode};
14
15#[derive(Debug, Default, Clone, Serialize, Deserialize)]
16pub(crate) struct ResourceLedger {
17 entities: BTreeSet<EntityId>,
19 component_sizes: BTreeMap<(EntityId, TypeCode), u64>,
25 total_bytes: u64,
28}
29
30impl ResourceLedger {
31 pub(crate) fn new() -> Self {
32 Self::default()
33 }
34
35 #[inline]
38 pub(crate) fn total_bytes(&self) -> u64 {
39 self.total_bytes
40 }
41
42 #[cfg_attr(not(test), allow(dead_code))]
46 #[inline]
47 pub(crate) fn total_entities(&self) -> u32 {
48 u32::try_from(self.entities.len()).unwrap_or(u32::MAX)
49 }
50
51 pub(crate) fn component_size(&self, entity: EntityId, tc: TypeCode) -> Option<u64> {
58 self.component_sizes.get(&(entity, tc)).copied()
59 }
60
61 #[cfg_attr(not(test), allow(dead_code))]
62 pub(crate) fn entity_bytes(&self, id: EntityId) -> u64 {
63 self.component_sizes
64 .range((id, TypeCode(0))..=(id, TypeCode(u32::MAX)))
65 .fold(0u64, |acc, (_, size)| acc.saturating_add(*size))
66 }
67
68 #[cfg_attr(not(test), allow(dead_code))]
69 pub(crate) fn type_count(&self, tc: TypeCode) -> u32 {
70 let n = self
71 .component_sizes
72 .keys()
73 .filter(|(_, t)| *t == tc)
74 .count();
75 u32::try_from(n).unwrap_or(u32::MAX)
76 }
77
78 pub(crate) fn add_entity(&mut self, id: EntityId) -> bool {
81 self.entities.insert(id)
82 }
83
84 pub(crate) fn remove_entity(&mut self, id: EntityId) -> u64 {
88 if !self.entities.remove(&id) {
89 return 0;
90 }
91 let keys: Vec<(EntityId, TypeCode)> = self
92 .component_sizes
93 .range((id, TypeCode(0))..=(id, TypeCode(u32::MAX)))
94 .map(|(k, _)| *k)
95 .collect();
96 let mut freed = 0u64;
97 for k in keys {
98 if let Some(size) = self.component_sizes.remove(&k) {
99 freed = freed.saturating_add(size);
100 self.total_bytes = self.total_bytes.saturating_sub(size);
101 }
102 }
103 freed
104 }
105
106 pub(crate) fn add_component(&mut self, entity: EntityId, tc: TypeCode, size: u64) -> bool {
111 if !self.entities.contains(&entity) {
112 return false;
113 }
114 match self.component_sizes.insert((entity, tc), size) {
115 Some(old) if size >= old => {
116 self.total_bytes = self.total_bytes.saturating_add(size - old);
117 }
118 Some(old) => {
119 self.total_bytes = self.total_bytes.saturating_sub(old - size);
120 }
121 None => {
122 self.total_bytes = self.total_bytes.saturating_add(size);
123 }
124 }
125 true
126 }
127
128 pub(crate) fn remove_component(&mut self, entity: EntityId, tc: TypeCode, _size: u64) -> bool {
133 if !self.entities.contains(&entity) {
134 return false;
135 }
136 if let Some(old) = self.component_sizes.remove(&(entity, tc)) {
137 self.total_bytes = self.total_bytes.saturating_sub(old);
138 }
139 true
140 }
141}
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146
147 fn e(n: u64) -> EntityId {
148 EntityId::new(n).unwrap()
149 }
150 fn t(n: u32) -> TypeCode {
151 TypeCode(n)
152 }
153
154 #[test]
155 fn empty_ledger_zeroes() {
156 let l = ResourceLedger::new();
157 assert_eq!(l.total_entities(), 0);
158 assert_eq!(l.total_bytes(), 0);
159 assert_eq!(l.entity_bytes(e(1)), 0);
160 assert_eq!(l.type_count(t(1)), 0);
161 }
162
163 #[test]
164 fn add_entity_increments_total() {
165 let mut l = ResourceLedger::new();
166 assert!(l.add_entity(e(1)));
167 assert!(l.add_entity(e(2)));
168 assert_eq!(l.total_entities(), 2);
169 }
170
171 #[test]
172 fn add_entity_idempotent() {
173 let mut l = ResourceLedger::new();
174 assert!(l.add_entity(e(1)));
175 assert!(!l.add_entity(e(1)));
176 assert_eq!(l.total_entities(), 1);
177 }
178
179 #[test]
180 fn add_component_updates_bytes_and_count() {
181 let mut l = ResourceLedger::new();
182 l.add_entity(e(1));
183 assert!(l.add_component(e(1), t(10), 100));
184 assert_eq!(l.entity_bytes(e(1)), 100);
185 assert_eq!(l.total_bytes(), 100);
186 assert_eq!(l.type_count(t(10)), 1);
187 }
188
189 #[test]
190 fn add_component_to_unknown_entity_is_noop() {
191 let mut l = ResourceLedger::new();
192 assert!(!l.add_component(e(1), t(10), 100));
193 assert_eq!(l.total_bytes(), 0);
194 assert_eq!(l.type_count(t(10)), 0);
195 }
196
197 #[test]
198 fn remove_component_balances_add() {
199 let mut l = ResourceLedger::new();
200 l.add_entity(e(1));
201 l.add_component(e(1), t(10), 100);
202 l.add_component(e(1), t(20), 50);
203 assert_eq!(l.total_bytes(), 150);
204 l.remove_component(e(1), t(10), 100);
205 assert_eq!(l.total_bytes(), 50);
206 assert_eq!(l.entity_bytes(e(1)), 50);
207 assert_eq!(l.type_count(t(10)), 0);
208 assert_eq!(l.type_count(t(20)), 1);
209 }
210
211 #[test]
212 fn remove_entity_returns_bytes_and_drops_total() {
213 let mut l = ResourceLedger::new();
214 l.add_entity(e(1));
215 l.add_component(e(1), t(10), 100);
216 l.add_component(e(1), t(20), 50);
217 let bytes = l.remove_entity(e(1));
218 assert_eq!(bytes, 150);
219 assert_eq!(l.total_entities(), 0);
220 assert_eq!(l.total_bytes(), 0);
221 assert_eq!(l.entity_bytes(e(1)), 0);
222 }
223
224 #[test]
225 fn remove_unknown_entity_is_noop() {
226 let mut l = ResourceLedger::new();
227 assert_eq!(l.remove_entity(e(999)), 0);
228 }
229
230 #[test]
231 fn add_remove_balanced_yields_empty() {
232 let mut l = ResourceLedger::new();
233 l.add_entity(e(1));
234 l.add_component(e(1), t(10), 100);
235 l.remove_component(e(1), t(10), 100);
236 l.remove_entity(e(1));
237 assert_eq!(l.total_bytes(), 0);
238 assert_eq!(l.total_entities(), 0);
239 }
240
241 #[test]
242 fn type_count_aggregates_across_entities() {
243 let mut l = ResourceLedger::new();
244 l.add_entity(e(1));
245 l.add_entity(e(2));
246 l.add_component(e(1), t(7), 10);
247 l.add_component(e(2), t(7), 20);
248 assert_eq!(l.type_count(t(7)), 2);
249 assert_eq!(l.total_bytes(), 30);
250 }
251
252 #[test]
253 fn type_count_underflow_is_saturating() {
254 let mut l = ResourceLedger::new();
256 l.add_entity(e(1));
257 l.remove_component(e(1), t(99), 50); assert_eq!(l.type_count(t(99)), 0);
259 }
260
261 #[test]
262 fn set_component_replace_adjusts_by_delta_no_double_count() {
263 let mut l = ResourceLedger::new();
267 l.add_entity(e(1));
268 assert!(l.add_component(e(1), t(7), 100));
269 assert!(l.add_component(e(1), t(7), 400)); assert_eq!(l.total_bytes(), 400);
271 assert_eq!(l.entity_bytes(e(1)), 400);
272 assert_eq!(l.type_count(t(7)), 1);
273 assert!(l.add_component(e(1), t(7), 50)); assert_eq!(l.total_bytes(), 50);
275 assert_eq!(l.type_count(t(7)), 1);
276 }
277
278 #[test]
279 fn remove_entity_cascades_component_accounting() {
280 let mut l = ResourceLedger::new();
283 l.add_entity(e(1));
284 l.add_component(e(1), t(7), 100);
285 l.add_component(e(1), t(9), 50);
286 assert_eq!(l.total_bytes(), 150);
287 assert_eq!(l.type_count(t(7)), 1);
288 assert_eq!(l.type_count(t(9)), 1);
289 assert_eq!(l.remove_entity(e(1)), 150);
290 assert_eq!(l.total_bytes(), 0);
291 assert_eq!(l.total_entities(), 0);
292 assert_eq!(l.type_count(t(7)), 0);
293 assert_eq!(l.type_count(t(9)), 0);
294 }
295
296 #[test]
297 fn remove_component_uses_stored_size_not_caller_size() {
298 let mut l = ResourceLedger::new();
301 l.add_entity(e(1));
302 l.add_component(e(1), t(7), 100);
303 assert!(l.remove_component(e(1), t(7), 999_999)); assert_eq!(l.total_bytes(), 0);
305 assert_eq!(l.entity_bytes(e(1)), 0);
306 }
307}