1use crate::component::ComponentStorage;
2use crate::simple_world::{EntityId, SimpleWorld};
3use alloc::vec::Vec;
4use soroban_sdk::{Bytes, Symbol};
5
6pub struct ChangeTracker {
30 added: Vec<(EntityId, Symbol)>,
31 removed: Vec<(EntityId, Symbol)>,
32 modified: Vec<(EntityId, Symbol)>,
33 tick: u64,
34}
35
36impl ChangeTracker {
37 pub fn new() -> Self {
39 Self {
40 added: Vec::new(),
41 removed: Vec::new(),
42 modified: Vec::new(),
43 tick: 0,
44 }
45 }
46
47 pub fn record_add(&mut self, entity_id: EntityId, component_type: Symbol) {
49 self.added.push((entity_id, component_type));
50 }
51
52 pub fn record_remove(&mut self, entity_id: EntityId, component_type: Symbol) {
54 self.removed.push((entity_id, component_type));
55 }
56
57 pub fn record_modify(&mut self, entity_id: EntityId, component_type: Symbol) {
59 self.modified.push((entity_id, component_type));
60 }
61
62 pub fn was_added(&self, entity_id: EntityId, component_type: &Symbol) -> bool {
64 self.added
65 .iter()
66 .any(|(eid, ct)| *eid == entity_id && ct == component_type)
67 }
68
69 pub fn was_removed(&self, entity_id: EntityId, component_type: &Symbol) -> bool {
71 self.removed
72 .iter()
73 .any(|(eid, ct)| *eid == entity_id && ct == component_type)
74 }
75
76 pub fn was_modified(&self, entity_id: EntityId, component_type: &Symbol) -> bool {
78 self.modified
79 .iter()
80 .any(|(eid, ct)| *eid == entity_id && ct == component_type)
81 }
82
83 pub fn added_entities_with(&self, component_type: &Symbol) -> Vec<EntityId> {
85 self.added
86 .iter()
87 .filter(|(_, ct)| ct == component_type)
88 .map(|(eid, _)| *eid)
89 .collect()
90 }
91
92 pub fn modified_entities_with(&self, component_type: &Symbol) -> Vec<EntityId> {
94 self.modified
95 .iter()
96 .filter(|(_, ct)| ct == component_type)
97 .map(|(eid, _)| *eid)
98 .collect()
99 }
100
101 pub fn removed_entities_with(&self, component_type: &Symbol) -> Vec<EntityId> {
103 self.removed
104 .iter()
105 .filter(|(_, ct)| ct == component_type)
106 .map(|(eid, _)| *eid)
107 .collect()
108 }
109
110 pub fn clear(&mut self) {
112 self.added.clear();
113 self.removed.clear();
114 self.modified.clear();
115 }
116
117 pub fn tick(&self) -> u64 {
119 self.tick
120 }
121
122 pub fn advance_tick(&mut self) {
124 self.tick += 1;
125 }
126
127 pub fn change_count(&self) -> usize {
129 self.added.len() + self.removed.len() + self.modified.len()
130 }
131}
132
133impl Default for ChangeTracker {
134 fn default() -> Self {
135 Self::new()
136 }
137}
138
139pub struct TrackedWorld {
159 world: SimpleWorld,
160 tracker: ChangeTracker,
161}
162
163impl TrackedWorld {
164 pub fn new(world: SimpleWorld) -> Self {
166 Self {
167 world,
168 tracker: ChangeTracker::new(),
169 }
170 }
171
172 pub fn world(&self) -> &SimpleWorld {
174 &self.world
175 }
176
177 pub fn world_mut(&mut self) -> &mut SimpleWorld {
179 &mut self.world
180 }
181
182 pub fn tracker(&self) -> &ChangeTracker {
184 &self.tracker
185 }
186
187 pub fn tracker_mut(&mut self) -> &mut ChangeTracker {
189 &mut self.tracker
190 }
191
192 pub fn into_inner(self) -> SimpleWorld {
194 self.world
195 }
196
197 pub fn spawn_entity(&mut self) -> EntityId {
199 self.world.spawn_entity()
200 }
201
202 pub fn add_component(&mut self, entity_id: EntityId, component_type: Symbol, data: Bytes) {
204 let existed = self.world.has_component(entity_id, &component_type);
205 self.world
206 .add_component(entity_id, component_type.clone(), data);
207 if existed {
208 self.tracker.record_modify(entity_id, component_type);
209 } else {
210 self.tracker.record_add(entity_id, component_type);
211 }
212 }
213
214 pub fn add_component_with_storage(
216 &mut self,
217 entity_id: EntityId,
218 component_type: Symbol,
219 data: Bytes,
220 storage: ComponentStorage,
221 ) {
222 let existed = self.world.has_component(entity_id, &component_type);
223 self.world
224 .add_component_with_storage(entity_id, component_type.clone(), data, storage);
225 if existed {
226 self.tracker.record_modify(entity_id, component_type);
227 } else {
228 self.tracker.record_add(entity_id, component_type);
229 }
230 }
231
232 pub fn remove_component(&mut self, entity_id: EntityId, component_type: &Symbol) -> bool {
234 let removed = self.world.remove_component(entity_id, component_type);
235 if removed {
236 self.tracker
237 .record_remove(entity_id, component_type.clone());
238 }
239 removed
240 }
241
242 pub fn get_component(&self, entity_id: EntityId, component_type: &Symbol) -> Option<Bytes> {
244 self.world.get_component(entity_id, component_type)
245 }
246
247 pub fn has_component(&self, entity_id: EntityId, component_type: &Symbol) -> bool {
249 self.world.has_component(entity_id, component_type)
250 }
251
252 pub fn despawn_entity(&mut self, entity_id: EntityId) {
254 if let Some(types) = self.world.entity_components.get(entity_id) {
255 for i in 0..types.len() {
256 if let Some(t) = types.get(i) {
257 self.tracker.record_remove(entity_id, t);
258 }
259 }
260 }
261 self.world.despawn_entity(entity_id);
262 }
263}
264
265#[cfg(test)]
266mod tests {
267 use super::*;
268 use soroban_sdk::{symbol_short, Env};
269
270 #[test]
271 fn test_tracker_new() {
272 let tracker = ChangeTracker::new();
273 assert_eq!(tracker.tick(), 0);
274 assert_eq!(tracker.change_count(), 0);
275 }
276
277 #[test]
278 fn test_record_and_query_add() {
279 let _env = Env::default();
280 let mut tracker = ChangeTracker::new();
281 tracker.record_add(1, symbol_short!("pos"));
282
283 assert!(tracker.was_added(1, &symbol_short!("pos")));
284 assert!(!tracker.was_added(1, &symbol_short!("vel")));
285 assert!(!tracker.was_added(2, &symbol_short!("pos")));
286 }
287
288 #[test]
289 fn test_record_and_query_remove() {
290 let _env = Env::default();
291 let mut tracker = ChangeTracker::new();
292 tracker.record_remove(1, symbol_short!("pos"));
293
294 assert!(tracker.was_removed(1, &symbol_short!("pos")));
295 assert!(!tracker.was_removed(1, &symbol_short!("vel")));
296 }
297
298 #[test]
299 fn test_record_and_query_modify() {
300 let _env = Env::default();
301 let mut tracker = ChangeTracker::new();
302 tracker.record_modify(1, symbol_short!("pos"));
303
304 assert!(tracker.was_modified(1, &symbol_short!("pos")));
305 assert!(!tracker.was_modified(2, &symbol_short!("pos")));
306 }
307
308 #[test]
309 fn test_entities_with_queries() {
310 let _env = Env::default();
311 let mut tracker = ChangeTracker::new();
312 tracker.record_add(1, symbol_short!("pos"));
313 tracker.record_add(2, symbol_short!("pos"));
314 tracker.record_add(3, symbol_short!("vel"));
315
316 let added = tracker.added_entities_with(&symbol_short!("pos"));
317 assert_eq!(added.len(), 2);
318 assert!(added.contains(&1));
319 assert!(added.contains(&2));
320 }
321
322 #[test]
323 fn test_clear_resets() {
324 let _env = Env::default();
325 let mut tracker = ChangeTracker::new();
326 tracker.record_add(1, symbol_short!("pos"));
327 tracker.record_remove(2, symbol_short!("vel"));
328 tracker.record_modify(3, symbol_short!("hp"));
329 assert_eq!(tracker.change_count(), 3);
330
331 tracker.clear();
332 assert_eq!(tracker.change_count(), 0);
333 assert!(!tracker.was_added(1, &symbol_short!("pos")));
334 }
335
336 #[test]
337 fn test_advance_tick() {
338 let mut tracker = ChangeTracker::new();
339 assert_eq!(tracker.tick(), 0);
340 tracker.advance_tick();
341 assert_eq!(tracker.tick(), 1);
342 tracker.advance_tick();
343 assert_eq!(tracker.tick(), 2);
344 }
345
346 #[test]
347 fn test_tracked_world_add() {
348 let env = Env::default();
349 let world = SimpleWorld::new(&env);
350 let mut tracked = TrackedWorld::new(world);
351
352 let e1 = tracked.spawn_entity();
353 let data = Bytes::from_array(&env, &[1, 2, 3]);
354 tracked.add_component(e1, symbol_short!("pos"), data.clone());
355
356 assert!(tracked.has_component(e1, &symbol_short!("pos")));
357 assert!(tracked.tracker().was_added(e1, &symbol_short!("pos")));
358 assert!(!tracked.tracker().was_modified(e1, &symbol_short!("pos")));
359 }
360
361 #[test]
362 fn test_tracked_world_modify() {
363 let env = Env::default();
364 let world = SimpleWorld::new(&env);
365 let mut tracked = TrackedWorld::new(world);
366
367 let e1 = tracked.spawn_entity();
368 let data1 = Bytes::from_array(&env, &[1]);
369 tracked.add_component(e1, symbol_short!("pos"), data1);
370
371 let data2 = Bytes::from_array(&env, &[2]);
373 tracked.add_component(e1, symbol_short!("pos"), data2.clone());
374
375 assert!(tracked.tracker().was_added(e1, &symbol_short!("pos")));
376 assert!(tracked.tracker().was_modified(e1, &symbol_short!("pos")));
377 assert_eq!(
378 tracked.get_component(e1, &symbol_short!("pos")),
379 Some(data2)
380 );
381 }
382
383 #[test]
384 fn test_tracked_world_remove() {
385 let env = Env::default();
386 let world = SimpleWorld::new(&env);
387 let mut tracked = TrackedWorld::new(world);
388
389 let e1 = tracked.spawn_entity();
390 let data = Bytes::from_array(&env, &[1]);
391 tracked.add_component(e1, symbol_short!("pos"), data);
392
393 assert!(tracked.remove_component(e1, &symbol_short!("pos")));
394 assert!(tracked.tracker().was_removed(e1, &symbol_short!("pos")));
395 assert!(!tracked.has_component(e1, &symbol_short!("pos")));
396 }
397
398 #[test]
399 fn test_tracked_world_despawn_records_removals() {
400 let env = Env::default();
401 let world = SimpleWorld::new(&env);
402 let mut tracked = TrackedWorld::new(world);
403
404 let e1 = tracked.spawn_entity();
405 let data = Bytes::from_array(&env, &[1]);
406 tracked.add_component(e1, symbol_short!("a"), data.clone());
407 tracked.add_component(e1, symbol_short!("b"), data);
408
409 tracked.tracker_mut().clear(); tracked.despawn_entity(e1);
411
412 assert!(tracked.tracker().was_removed(e1, &symbol_short!("a")));
413 assert!(tracked.tracker().was_removed(e1, &symbol_short!("b")));
414 }
415
416 #[test]
417 fn test_tracked_world_into_inner() {
418 let env = Env::default();
419 let world = SimpleWorld::new(&env);
420 let mut tracked = TrackedWorld::new(world);
421
422 let e1 = tracked.spawn_entity();
423 let data = Bytes::from_array(&env, &[1]);
424 tracked.add_component(e1, symbol_short!("test"), data);
425
426 let inner = tracked.into_inner();
427 assert!(inner.has_component(e1, &symbol_short!("test")));
428 }
429}