use core::sync::atomic::{AtomicU32, Ordering};
use cougr_core::scheduler::SimpleScheduler;
use cougr_core::{
query::SimpleQueryCache,
runtime::{ComponentEvent, ObservedWorld, TrackedWorld},
CommandQueue, GameApp, Plugin, SimpleWorld,
};
use soroban_sdk::{symbol_short, Bytes, Env};
static OBSERVER_FIRE_COUNT: AtomicU32 = AtomicU32::new(0);
fn counting_observer(_event: &ComponentEvent, _world: &SimpleWorld, _env: &Env) {
OBSERVER_FIRE_COUNT.fetch_add(1, Ordering::Relaxed);
}
fn setup_physics_system(world: &mut SimpleWorld, env: &Env) {
let entities = world.get_entities_with_component(&symbol_short!("pos"), env);
for i in 0..entities.len() {
let eid = entities.get(i).unwrap();
if let (Some(pos_data), Some(vel_data)) = (
world.get_component(eid, &symbol_short!("pos")),
world.get_component(eid, &symbol_short!("vel")),
) {
let px = pos_data.get(0).unwrap_or(0);
let vx = vel_data.get(0).unwrap_or(0);
let new_pos = Bytes::from_array(env, &[px.wrapping_add(vx)]);
world.add_component(eid, symbol_short!("pos"), new_pos);
}
}
}
#[test]
fn test_tracked_world_records_command_queue_changes() {
let env = Env::default();
let world = SimpleWorld::new(&env);
let mut tracked = TrackedWorld::new(world);
let e1 = tracked.spawn_entity();
tracked.add_component(e1, symbol_short!("pos"), Bytes::from_array(&env, &[1]));
let mut queue = CommandQueue::new();
queue.spawn();
queue.add_component(e1, symbol_short!("hp"), Bytes::from_array(&env, &[100]));
let spawned = queue.apply(tracked.world_mut());
assert_eq!(spawned.len(), 1);
let _e2 = spawned[0];
assert!(tracked.tracker().was_added(e1, &symbol_short!("pos")));
assert!(!tracked.tracker().was_added(e1, &symbol_short!("hp")));
assert!(tracked.has_component(e1, &symbol_short!("pos")));
assert!(tracked.has_component(e1, &symbol_short!("hp")));
}
#[test]
fn test_query_cache_invalidated_by_tracked_mutations() {
let env = Env::default();
let world = SimpleWorld::new(&env);
let mut tracked = TrackedWorld::new(world);
let mut cache = SimpleQueryCache::new(symbol_short!("pos"), &env);
let results = cache.execute(tracked.world(), &env);
assert_eq!(results.len(), 0);
let e1 = tracked.spawn_entity();
tracked.add_component(e1, symbol_short!("pos"), Bytes::from_array(&env, &[1]));
assert!(!cache.is_valid(tracked.world().version()));
let results = cache.execute(tracked.world(), &env);
assert_eq!(results.len(), 1);
tracked.remove_component(e1, &symbol_short!("pos"));
let results = cache.execute(tracked.world(), &env);
assert_eq!(results.len(), 0);
}
#[test]
fn test_observed_world_into_plugin_app() {
let env = Env::default();
let before = OBSERVER_FIRE_COUNT.load(Ordering::Relaxed);
let world = SimpleWorld::new(&env);
let mut observed = ObservedWorld::new(world);
observed
.observers_mut()
.on_add(symbol_short!("pos"), counting_observer);
let e1 = observed.spawn_entity();
observed.add_component(
e1,
symbol_short!("pos"),
Bytes::from_array(&env, &[10]),
&env,
);
observed.add_component(
e1,
symbol_short!("vel"),
Bytes::from_array(&env, &[5]),
&env,
);
let after_add = OBSERVER_FIRE_COUNT.load(Ordering::Relaxed);
assert_eq!(after_add - before, 1);
let inner_world = observed.into_inner();
let mut app = GameApp::with_world(inner_world);
app.add_system("physics", setup_physics_system);
app.run(&env).unwrap();
let pos = app
.world()
.get_component(e1, &symbol_short!("pos"))
.unwrap();
assert_eq!(pos.get(0).unwrap(), 15);
}
#[test]
fn test_change_tracker_and_query_cache_cycle() {
let env = Env::default();
let world = SimpleWorld::new(&env);
let mut tracked = TrackedWorld::new(world);
let mut cache = SimpleQueryCache::new(symbol_short!("pos"), &env);
let e1 = tracked.spawn_entity();
tracked.add_component(e1, symbol_short!("pos"), Bytes::from_array(&env, &[10]));
let e2 = tracked.spawn_entity();
tracked.add_component(e2, symbol_short!("pos"), Bytes::from_array(&env, &[20]));
let results = cache.execute(tracked.world(), &env);
assert_eq!(results.len(), 2);
assert!(tracked.tracker().was_added(e1, &symbol_short!("pos")));
assert!(tracked.tracker().was_added(e2, &symbol_short!("pos")));
tracked.tracker_mut().clear();
tracked.tracker_mut().advance_tick();
assert_eq!(tracked.tracker().tick(), 1);
tracked.add_component(e1, symbol_short!("pos"), Bytes::from_array(&env, &[15]));
assert!(tracked.tracker().was_modified(e1, &symbol_short!("pos")));
assert!(!tracked.tracker().was_added(e1, &symbol_short!("pos")));
let results = cache.execute(tracked.world(), &env);
assert_eq!(results.len(), 2);
}
#[test]
fn test_multiple_plugins_sharing_world() {
struct PluginA;
impl Plugin for PluginA {
fn name(&self) -> &'static str {
"plugin_a"
}
fn build(&self, app: &mut GameApp) {
let e = app.world_mut().spawn_entity();
let env = app.world().env().clone();
app.world_mut().add_component(
e,
symbol_short!("a_comp"),
Bytes::from_array(&env, &[42]),
);
}
}
struct PluginB;
impl Plugin for PluginB {
fn name(&self) -> &'static str {
"plugin_b"
}
fn build(&self, app: &mut GameApp) {
app.add_system("b_system", |world: &mut SimpleWorld, env: &Env| {
let entities = world.get_entities_with_component(&symbol_short!("a_comp"), env);
for i in 0..entities.len() {
let eid = entities.get(i).unwrap();
let data = Bytes::from_array(env, &[99]);
world.add_component(eid, symbol_short!("b_comp"), data);
}
});
}
}
let env = Env::default();
let mut app = GameApp::new(&env);
app.add_plugin(PluginA);
app.add_plugin(PluginB);
assert!(app.world().has_component(1, &symbol_short!("a_comp")));
app.run(&env).unwrap();
assert!(app.world().has_component(1, &symbol_short!("b_comp")));
}
#[test]
fn test_tracked_world_despawn_with_query_cache() {
let env = Env::default();
let world = SimpleWorld::new(&env);
let mut tracked = TrackedWorld::new(world);
let mut cache = SimpleQueryCache::new(symbol_short!("pos"), &env);
let e1 = tracked.spawn_entity();
tracked.add_component(e1, symbol_short!("pos"), Bytes::from_array(&env, &[1]));
let e2 = tracked.spawn_entity();
tracked.add_component(e2, symbol_short!("pos"), Bytes::from_array(&env, &[2]));
let e3 = tracked.spawn_entity();
tracked.add_component(e3, symbol_short!("pos"), Bytes::from_array(&env, &[3]));
let results = cache.execute(tracked.world(), &env);
assert_eq!(results.len(), 3);
tracked.tracker_mut().clear();
tracked.despawn_entity(e2);
assert!(tracked.tracker().was_removed(e2, &symbol_short!("pos")));
let results = cache.execute(tracked.world(), &env);
assert_eq!(results.len(), 2);
}
#[test]
fn test_command_queue_sparse_storage_integration() {
let env = Env::default();
let mut world = SimpleWorld::new(&env);
let e1 = world.spawn_entity();
let mut queue = CommandQueue::new();
queue.add_component(e1, symbol_short!("pos"), Bytes::from_array(&env, &[1]));
queue.add_sparse_component(e1, symbol_short!("tag"), Bytes::from_array(&env, &[2]));
queue.apply(&mut world);
assert_eq!(world.table_component_count(&symbol_short!("pos")), 1);
assert_eq!(world.table_component_count(&symbol_short!("tag")), 0);
assert_eq!(world.component_count(&symbol_short!("tag")), 1);
assert!(world.has_component(e1, &symbol_short!("pos")));
assert!(world.has_component(e1, &symbol_short!("tag")));
}
#[test]
fn test_scheduler_with_command_queue_pattern() {
fn spawner_system(world: &mut SimpleWorld, env: &Env) {
let mut cmds = CommandQueue::new();
cmds.spawn();
let spawned = cmds.apply(world);
for id in &spawned {
world.add_component(*id, symbol_short!("spawned"), Bytes::from_array(env, &[1]));
}
}
let env = Env::default();
let mut world = SimpleWorld::new(&env);
let mut scheduler = SimpleScheduler::new();
scheduler.add_system("spawner", spawner_system);
scheduler.run_all(&mut world, &env).unwrap();
scheduler.run_all(&mut world, &env).unwrap();
scheduler.run_all(&mut world, &env).unwrap();
let spawned_entities = world.get_entities_with_component(&symbol_short!("spawned"), &env);
assert_eq!(spawned_entities.len(), 3);
}