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 #[cfg_attr(not(test), allow(dead_code))]
52 pub(crate) fn entity_bytes(&self, id: EntityId) -> u64 {
53 self.component_sizes
54 .range((id, TypeCode(0))..=(id, TypeCode(u32::MAX)))
55 .fold(0u64, |acc, (_, size)| acc.saturating_add(*size))
56 }
57
58 #[cfg_attr(not(test), allow(dead_code))]
59 pub(crate) fn type_count(&self, tc: TypeCode) -> u32 {
60 let n = self
61 .component_sizes
62 .keys()
63 .filter(|(_, t)| *t == tc)
64 .count();
65 u32::try_from(n).unwrap_or(u32::MAX)
66 }
67
68 pub(crate) fn add_entity(&mut self, id: EntityId) -> bool {
71 self.entities.insert(id)
72 }
73
74 pub(crate) fn remove_entity(&mut self, id: EntityId) -> u64 {
78 if !self.entities.remove(&id) {
79 return 0;
80 }
81 let keys: Vec<(EntityId, TypeCode)> = self
82 .component_sizes
83 .range((id, TypeCode(0))..=(id, TypeCode(u32::MAX)))
84 .map(|(k, _)| *k)
85 .collect();
86 let mut freed = 0u64;
87 for k in keys {
88 if let Some(size) = self.component_sizes.remove(&k) {
89 freed = freed.saturating_add(size);
90 self.total_bytes = self.total_bytes.saturating_sub(size);
91 }
92 }
93 freed
94 }
95
96 pub(crate) fn add_component(&mut self, entity: EntityId, tc: TypeCode, size: u64) -> bool {
101 if !self.entities.contains(&entity) {
102 return false;
103 }
104 match self.component_sizes.insert((entity, tc), size) {
105 Some(old) if size >= old => {
106 self.total_bytes = self.total_bytes.saturating_add(size - old);
107 }
108 Some(old) => {
109 self.total_bytes = self.total_bytes.saturating_sub(old - size);
110 }
111 None => {
112 self.total_bytes = self.total_bytes.saturating_add(size);
113 }
114 }
115 true
116 }
117
118 pub(crate) fn remove_component(&mut self, entity: EntityId, tc: TypeCode, _size: u64) -> bool {
123 if !self.entities.contains(&entity) {
124 return false;
125 }
126 if let Some(old) = self.component_sizes.remove(&(entity, tc)) {
127 self.total_bytes = self.total_bytes.saturating_sub(old);
128 }
129 true
130 }
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136
137 fn e(n: u64) -> EntityId {
138 EntityId::new(n).unwrap()
139 }
140 fn t(n: u32) -> TypeCode {
141 TypeCode(n)
142 }
143
144 #[test]
145 fn empty_ledger_zeroes() {
146 let l = ResourceLedger::new();
147 assert_eq!(l.total_entities(), 0);
148 assert_eq!(l.total_bytes(), 0);
149 assert_eq!(l.entity_bytes(e(1)), 0);
150 assert_eq!(l.type_count(t(1)), 0);
151 }
152
153 #[test]
154 fn add_entity_increments_total() {
155 let mut l = ResourceLedger::new();
156 assert!(l.add_entity(e(1)));
157 assert!(l.add_entity(e(2)));
158 assert_eq!(l.total_entities(), 2);
159 }
160
161 #[test]
162 fn add_entity_idempotent() {
163 let mut l = ResourceLedger::new();
164 assert!(l.add_entity(e(1)));
165 assert!(!l.add_entity(e(1)));
166 assert_eq!(l.total_entities(), 1);
167 }
168
169 #[test]
170 fn add_component_updates_bytes_and_count() {
171 let mut l = ResourceLedger::new();
172 l.add_entity(e(1));
173 assert!(l.add_component(e(1), t(10), 100));
174 assert_eq!(l.entity_bytes(e(1)), 100);
175 assert_eq!(l.total_bytes(), 100);
176 assert_eq!(l.type_count(t(10)), 1);
177 }
178
179 #[test]
180 fn add_component_to_unknown_entity_is_noop() {
181 let mut l = ResourceLedger::new();
182 assert!(!l.add_component(e(1), t(10), 100));
183 assert_eq!(l.total_bytes(), 0);
184 assert_eq!(l.type_count(t(10)), 0);
185 }
186
187 #[test]
188 fn remove_component_balances_add() {
189 let mut l = ResourceLedger::new();
190 l.add_entity(e(1));
191 l.add_component(e(1), t(10), 100);
192 l.add_component(e(1), t(20), 50);
193 assert_eq!(l.total_bytes(), 150);
194 l.remove_component(e(1), t(10), 100);
195 assert_eq!(l.total_bytes(), 50);
196 assert_eq!(l.entity_bytes(e(1)), 50);
197 assert_eq!(l.type_count(t(10)), 0);
198 assert_eq!(l.type_count(t(20)), 1);
199 }
200
201 #[test]
202 fn remove_entity_returns_bytes_and_drops_total() {
203 let mut l = ResourceLedger::new();
204 l.add_entity(e(1));
205 l.add_component(e(1), t(10), 100);
206 l.add_component(e(1), t(20), 50);
207 let bytes = l.remove_entity(e(1));
208 assert_eq!(bytes, 150);
209 assert_eq!(l.total_entities(), 0);
210 assert_eq!(l.total_bytes(), 0);
211 assert_eq!(l.entity_bytes(e(1)), 0);
212 }
213
214 #[test]
215 fn remove_unknown_entity_is_noop() {
216 let mut l = ResourceLedger::new();
217 assert_eq!(l.remove_entity(e(999)), 0);
218 }
219
220 #[test]
221 fn add_remove_balanced_yields_empty() {
222 let mut l = ResourceLedger::new();
223 l.add_entity(e(1));
224 l.add_component(e(1), t(10), 100);
225 l.remove_component(e(1), t(10), 100);
226 l.remove_entity(e(1));
227 assert_eq!(l.total_bytes(), 0);
228 assert_eq!(l.total_entities(), 0);
229 }
230
231 #[test]
232 fn type_count_aggregates_across_entities() {
233 let mut l = ResourceLedger::new();
234 l.add_entity(e(1));
235 l.add_entity(e(2));
236 l.add_component(e(1), t(7), 10);
237 l.add_component(e(2), t(7), 20);
238 assert_eq!(l.type_count(t(7)), 2);
239 assert_eq!(l.total_bytes(), 30);
240 }
241
242 #[test]
243 fn type_count_underflow_is_saturating() {
244 let mut l = ResourceLedger::new();
246 l.add_entity(e(1));
247 l.remove_component(e(1), t(99), 50); assert_eq!(l.type_count(t(99)), 0);
249 }
250
251 #[test]
252 fn set_component_replace_adjusts_by_delta_no_double_count() {
253 let mut l = ResourceLedger::new();
257 l.add_entity(e(1));
258 assert!(l.add_component(e(1), t(7), 100));
259 assert!(l.add_component(e(1), t(7), 400)); assert_eq!(l.total_bytes(), 400);
261 assert_eq!(l.entity_bytes(e(1)), 400);
262 assert_eq!(l.type_count(t(7)), 1);
263 assert!(l.add_component(e(1), t(7), 50)); assert_eq!(l.total_bytes(), 50);
265 assert_eq!(l.type_count(t(7)), 1);
266 }
267
268 #[test]
269 fn remove_entity_cascades_component_accounting() {
270 let mut l = ResourceLedger::new();
273 l.add_entity(e(1));
274 l.add_component(e(1), t(7), 100);
275 l.add_component(e(1), t(9), 50);
276 assert_eq!(l.total_bytes(), 150);
277 assert_eq!(l.type_count(t(7)), 1);
278 assert_eq!(l.type_count(t(9)), 1);
279 assert_eq!(l.remove_entity(e(1)), 150);
280 assert_eq!(l.total_bytes(), 0);
281 assert_eq!(l.total_entities(), 0);
282 assert_eq!(l.type_count(t(7)), 0);
283 assert_eq!(l.type_count(t(9)), 0);
284 }
285
286 #[test]
287 fn remove_component_uses_stored_size_not_caller_size() {
288 let mut l = ResourceLedger::new();
291 l.add_entity(e(1));
292 l.add_component(e(1), t(7), 100);
293 assert!(l.remove_component(e(1), t(7), 999_999)); assert_eq!(l.total_bytes(), 0);
295 assert_eq!(l.entity_bytes(e(1)), 0);
296 }
297}