use bevy::prelude::*;
use bevy_persistence_database::{commit_sync, persistence_plugin::PersistencePlugins, PersistentQuery, PersistenceSystemSet, Guid};
use bevy_persistence_database_derive::db_matrix_test;
use crate::common::*;
use std::sync::{Arc, atomic::{AtomicUsize, Ordering}};
use crate::common::CountingDbConnection;
use bevy::prelude::IntoScheduleConfigs;
#[db_matrix_test]
fn test_ensure_loaded_then_pass_through() {
let (real_db, _container) = setup();
let mut app_seed = App::new();
app_seed.add_plugins(PersistencePlugins(real_db.clone()));
app_seed.world_mut().spawn((Health { value: 150 }, Position { x: 10.0, y: 20.0 }));
app_seed.update();
commit_sync(&mut app_seed).expect("seed commit failed");
let counter = Arc::new(AtomicUsize::new(0));
let db = Arc::new(CountingDbConnection::new(real_db.clone(), counter.clone()));
let mut app = App::new();
app.add_plugins(PersistencePlugins(db.clone()));
fn sys_load(mut pq: PersistentQuery<(&Health, &Position), (With<Health>, With<Position>)>) {
let _ = pq.ensure_loaded().ensure_loaded();
}
app.add_systems(Update, sys_load);
app.update();
fn sys_verify(pq: PersistentQuery<(&Health, &Position), (With<Health>, With<Position>)>) {
let all: Vec<_> = pq.iter().collect();
assert_eq!(all.len(), 1);
let (_e, (h, p)) = pq.single().unwrap();
assert_eq!(h.value, 150);
assert_eq!(p.x, 10.0);
assert_eq!(p.y, 20.0);
let combos = pq.iter_combinations::<1>().count();
assert_eq!(combos, 1);
}
app.add_systems(Update, sys_verify);
app.update();
assert_eq!(counter.load(Ordering::SeqCst), 1, "expected exactly one execute_documents call");
}
#[db_matrix_test]
fn test_cache_prevents_duplicate_loads_in_same_frame() {
let (real_db, _container) = setup();
let mut app_seed = App::new();
app_seed.add_plugins(PersistencePlugins(real_db.clone()));
app_seed.world_mut().spawn(Health { value: 42 });
app_seed.update();
commit_sync(&mut app_seed).unwrap();
let counter = Arc::new(AtomicUsize::new(0));
let db = Arc::new(CountingDbConnection::new(real_db.clone(), counter.clone()));
let mut app = App::new();
app.add_plugins(PersistencePlugins(db.clone()));
fn sys_twice(mut pq: PersistentQuery<&Health, With<Health>>) {
pq.ensure_loaded();
pq.ensure_loaded(); }
app.add_systems(Update, sys_twice);
app.update();
app.update();
assert_eq!(counter.load(Ordering::SeqCst), 1, "cache should coalesce identical loads");
}
#[db_matrix_test]
fn test_deref_forwards_bevy_query_methods() {
let (db_real, _container) = setup();
let mut app_seed = App::new();
app_seed.add_plugins(PersistencePlugins(db_real.clone()));
let e = app_seed.world_mut().spawn((Health { value: 5 }, Position { x: 1.0, y: 2.0 })).id();
app_seed.update();
commit_sync(&mut app_seed).unwrap();
let mut app = App::new();
app.add_plugins(PersistencePlugins(db_real.clone()));
fn load(mut pq: PersistentQuery<(&Health, &Position)>) { let _ = pq.ensure_loaded(); }
app.add_systems(Update, load);
app.update();
#[derive(Resource)]
struct Ent(Entity);
app.insert_resource(Ent(e));
fn verify(
pq: PersistentQuery<(&Health, &Position)>,
ent: Res<Ent>,
) {
assert!(pq.contains(ent.0));
let (_e, (h, p)) = pq.get(ent.0).unwrap();
assert_eq!(h.value, 5);
assert_eq!(p.x, 1.0);
let v: Vec<_> = pq.iter().collect();
assert_eq!(v.len(), 1);
assert_eq!(pq.iter_combinations::<1>().count(), 1);
let _ = pq.get_many([ent.0]).unwrap();
}
app.add_systems(Update, verify);
app.update();
}
#[db_matrix_test]
fn test_immediate_pass_through() {
let (db, _container) = setup();
let mut app_seed = App::new();
app_seed.add_plugins(PersistencePlugins(db.clone()));
app_seed.world_mut().spawn(Health { value: 10 });
app_seed.world_mut().spawn(Health { value: 20 });
app_seed.update();
commit_sync(&mut app_seed).expect("seed commit failed");
let mut app = App::new();
app.add_plugins(PersistencePlugins(db.clone()));
#[derive(Resource, Default)]
struct TestResults {
immediate_iter_count: usize,
immediate_get_success: bool,
immediate_contains: bool,
first_entity: Option<Entity>,
}
app.insert_resource(TestResults::default());
fn test_immediate_system(mut pq: PersistentQuery<&Health>, mut results: ResMut<TestResults>) {
pq.ensure_loaded();
let entities: Vec<Entity> = pq.iter().map(|(e, _)| e).collect();
results.immediate_iter_count = entities.len();
if let Some(&first) = entities.first() {
results.first_entity = Some(first);
results.immediate_get_success = pq.get(first).is_ok();
results.immediate_contains = pq.contains(first);
}
}
app.add_systems(PostUpdate, test_immediate_system.after(PersistenceSystemSet::PreCommit));
app.update();
let result = app.world().resource::<TestResults>();
assert_eq!(result.immediate_iter_count, 2, "iter should return all loaded entities immediately");
assert!(result.immediate_get_success, "get should work immediately after load");
assert!(result.immediate_contains, "contains should work immediately after load");
if let Some(entity) = result.first_entity {
let health = app.world().get::<Health>(entity);
assert!(health.is_some(), "Entity should exist in the world");
} else {
panic!("Failed to capture entity during immediate apply");
}
}
#[db_matrix_test]
fn test_query_contains_method() {
let (db, _container) = setup();
let mut app_seed = App::new();
app_seed.add_plugins(PersistencePlugins(db.clone()));
let entity = app_seed.world_mut().spawn(Health { value: 42 }).id();
app_seed.update();
commit_sync(&mut app_seed).expect("seed commit failed");
let _guid = app_seed.world().get::<Guid>(entity).unwrap().id().to_string();
let mut app = App::new();
app.add_plugins(PersistencePlugins(db.clone()));
#[derive(Resource, Default)]
struct LoadedEntity(Option<Entity>);
app.insert_resource(LoadedEntity::default());
fn load_health(
mut pq: PersistentQuery<&Health>,
mut res: ResMut<LoadedEntity>
) {
pq.ensure_loaded();
if let Some((e, _)) = pq.iter().next() {
res.0 = Some(e);
}
}
#[derive(Resource, Default)]
struct TestResult(bool);
app.insert_resource(TestResult::default());
fn check_contains(
pq: PersistentQuery<&Health>,
entity: Res<LoadedEntity>,
mut result: ResMut<TestResult>
) {
if let Some(e) = entity.0 {
result.0 = pq.contains(e);
}
}
app.add_systems(Update, load_health.after(PersistenceSystemSet::PreCommit));
app.update();
app.add_systems(Update, check_contains);
app.update();
let contains_result = app.world().resource::<TestResult>().0;
assert!(contains_result, "contains() should return true for a loaded entity");
}
#[db_matrix_test]
fn test_query_get_method() {
let (db, _container) = setup();
let mut app_seed = App::new();
app_seed.add_plugins(PersistencePlugins(db.clone()));
let _entity = app_seed.world_mut().spawn(Health { value: 42 }).id();
app_seed.update();
commit_sync(&mut app_seed).expect("seed commit failed");
let mut app = App::new();
app.add_plugins(PersistencePlugins(db.clone()));
#[derive(Resource, Default)]
struct LoadedEntity(Option<Entity>);
app.insert_resource(LoadedEntity::default());
fn load_health(
mut pq: PersistentQuery<&Health>,
mut res: ResMut<LoadedEntity>
) {
pq.ensure_loaded();
if let Some((e, _)) = pq.iter().next() {
res.0 = Some(e);
}
}
#[derive(Resource, Default)]
struct TestResult(bool);
app.insert_resource(TestResult::default());
fn check_get(
pq: PersistentQuery<&Health>,
entity: Res<LoadedEntity>,
mut result: ResMut<TestResult>
) {
if let Some(e) = entity.0 {
result.0 = pq.get(e).is_ok();
}
}
app.add_systems(Update, load_health.after(PersistenceSystemSet::PreCommit));
app.update();
app.add_systems(Update, check_get);
app.update();
let get_result = app.world().resource::<TestResult>().0;
assert!(get_result, "get() should succeed for a loaded entity");
}
#[db_matrix_test]
fn test_query_get_mut_method() {
let (db, _container) = setup();
let mut app_seed = App::new();
app_seed.add_plugins(PersistencePlugins(db.clone()));
let _entity = app_seed.world_mut().spawn(Health { value: 42 }).id();
app_seed.update();
commit_sync(&mut app_seed).expect("seed commit failed");
let mut app = App::new();
app.add_plugins(PersistencePlugins(db.clone()));
#[derive(Resource, Default)]
struct LoadedEntity(Option<Entity>);
app.insert_resource(LoadedEntity::default());
fn load_health(
mut pq: PersistentQuery<&Health>,
mut res: ResMut<LoadedEntity>
) {
pq.ensure_loaded();
if let Some((e, _)) = pq.iter().next() {
res.0 = Some(e);
}
}
#[derive(Resource, Default)]
struct TestResult(bool);
app.insert_resource(TestResult::default());
fn check_get_mut(
mut pq: PersistentQuery<&mut Health>,
entity: Res<LoadedEntity>,
mut result: ResMut<TestResult>
) {
if let Some(e) = entity.0 {
result.0 = pq.get_mut(e).is_ok();
}
}
app.add_systems(Update, load_health.after(PersistenceSystemSet::PreCommit));
app.update();
app.add_systems(Update, check_get_mut);
app.update();
let get_mut_result = app.world().resource::<TestResult>().0;
assert!(get_mut_result, "get_mut() should succeed for a loaded entity");
}
#[db_matrix_test]
fn test_query_get_many_method() {
let (db, _container) = setup();
let mut app_seed = App::new();
app_seed.add_plugins(PersistencePlugins(db.clone()));
app_seed.world_mut().spawn(Health { value: 10 });
app_seed.world_mut().spawn(Health { value: 20 });
app_seed.update();
commit_sync(&mut app_seed).expect("seed commit failed");
let mut app = App::new();
app.add_plugins(PersistencePlugins(db.clone()));
#[derive(Resource, Default)]
struct LoadedEntities(Vec<Entity>);
app.insert_resource(LoadedEntities::default());
fn load_health(
mut pq: PersistentQuery<&Health>,
mut res: ResMut<LoadedEntities>
) {
pq.ensure_loaded();
for (e, _) in pq.iter() {
res.0.push(e);
}
}
#[derive(Resource, Default)]
struct TestResult(bool);
app.insert_resource(TestResult::default());
fn check_get_many(
pq: PersistentQuery<&Health>,
entities: Res<LoadedEntities>,
mut result: ResMut<TestResult>
) {
if entities.0.len() < 2 {
return;
}
let get_result = pq.get_many([entities.0[0], entities.0[1]]);
result.0 = get_result.is_ok();
}
app.add_systems(Update, load_health.after(PersistenceSystemSet::PreCommit));
app.update();
app.add_systems(Update, check_get_many);
app.update();
let get_many_result = app.world().resource::<TestResult>().0;
assert!(get_many_result, "get_many() should succeed for loaded entities");
}
#[db_matrix_test]
fn test_query_single_method() {
let (db, _container) = setup();
let mut app_seed = App::new();
app_seed.add_plugins(PersistencePlugins(db.clone()));
app_seed.world_mut().spawn((Health { value: 50 }, PlayerName { name: "player".into() }));
app_seed.update();
commit_sync(&mut app_seed).expect("seed commit failed");
let mut app = App::new();
app.add_plugins(PersistencePlugins(db.clone()));
#[derive(Resource, Default)]
struct SingleResult {
health_value: Option<i32>,
success: bool
}
app.insert_resource(SingleResult::default());
fn test_single(
mut pq: PersistentQuery<&Health, With<PlayerName>>,
mut result: ResMut<SingleResult>
) {
pq.ensure_loaded();
match pq.single() {
Ok((_e, health)) => {
result.health_value = Some(health.value);
result.success = true;
}
Err(_) => {
result.success = false;
}
}
}
app.add_systems(Update, test_single.after(PersistenceSystemSet::PreCommit));
app.update();
let result = app.world().resource::<SingleResult>();
assert!(result.success, "single() should succeed for a unique entity");
assert_eq!(result.health_value, Some(50), "single() should return the correct health value");
}
#[db_matrix_test]
fn test_query_iter_combinations_method() {
let (db, _container) = setup();
let mut app_seed = App::new();
app_seed.add_plugins(PersistencePlugins(db.clone()));
app_seed.world_mut().spawn(Health { value: 10 });
app_seed.world_mut().spawn(Health { value: 20 });
app_seed.world_mut().spawn(Health { value: 30 });
app_seed.update();
commit_sync(&mut app_seed).expect("seed commit failed");
let mut app = App::new();
app.add_plugins(PersistencePlugins(db.clone()));
#[derive(Resource, Default)]
struct CombinationsResult {
count: usize
}
app.insert_resource(CombinationsResult::default());
fn test_combinations(
mut pq: PersistentQuery<&Health>,
mut result: ResMut<CombinationsResult>
) {
pq.ensure_loaded();
result.count = pq.iter_combinations::<2>().count();
bevy::log::info!("Found {} combinations of 2 entities", result.count);
}
app.add_systems(Update, test_combinations.after(PersistenceSystemSet::PreCommit));
app.update();
let result = app.world().resource::<CombinationsResult>();
assert_eq!(result.count, 3, "iter_combinations should return the correct number of combinations");
}