#[cfg(test)]
#[cfg(all(feature = "gis", feature = "parallel"))]
use {
krabmaga::bevy_a5::prelude::GeoCell,
krabmaga::engine::fields::field::Field,
krabmaga::engine::fields::grid_option::GridOption,
krabmaga::engine::fields::sparse_a5_grid::SparseA5Grid,
std::sync::atomic::{AtomicUsize, Ordering},
};
#[cfg(all(feature = "gis", feature = "parallel"))]
fn london_root_grid() -> (SparseA5Grid<u32>, Vec<GeoCell>) {
let london = GeoCell::from_lon_lat(-0.1, 51.5, 1).expect("london resolves");
let grid: SparseA5Grid<u32> = SparseA5Grid::new_with_root(london, 4);
let cells = grid.all_cells().expect("london has res-4 descendants");
(grid, cells)
}
#[cfg(all(feature = "gis", feature = "parallel"))]
#[test]
fn sparse_a5_grid_bags_parallel() {
let (grid, cells) = london_root_grid();
let total = cells.len();
assert_eq!(grid.get_empty_bags().len(), total);
let pick = grid.get_random_empty_bag().expect("non-empty");
grid.set_object_location(0, &pick);
let mut grid = grid;
grid.update();
assert_eq!(grid.num_objects(), 1);
assert_eq!(grid.num_objects_at_location(&pick), 1);
assert_eq!(grid.get_empty_bags().len(), total - 1);
assert_eq!(grid.get_location(&0).as_ref(), Some(&pick));
}
#[cfg(all(feature = "gis", feature = "parallel"))]
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
struct Tagged {
id: u32,
flag: bool,
}
#[cfg(all(feature = "gis", feature = "parallel"))]
#[test]
fn sparse_a5_grid_apply_parallel() {
let london = GeoCell::from_lon_lat(-0.1, 51.5, 1).expect("london resolves");
let grid: SparseA5Grid<Tagged> = SparseA5Grid::new_with_root(london, 4);
let cells = grid.all_cells().expect("res-4 cells");
let n = cells.len().min(6) as u32;
for i in 0..n {
grid.set_object_location(Tagged { id: i, flag: false }, &cells[i as usize]);
}
let mut grid = grid;
grid.lazy_update();
for i in 0..n {
assert_eq!(
grid.get(&Tagged { id: i, flag: false }),
Some(Tagged { id: i, flag: false }),
"staged id={i} should be findable before apply"
);
}
grid.apply_to_all_values(
|_loc, t| {
Some(Tagged {
id: t.id,
flag: true,
})
},
GridOption::WRITE,
);
grid.lazy_update();
for i in 0..n {
assert_eq!(
grid.get(&Tagged { id: i, flag: true }),
Some(Tagged { id: i, flag: true }),
"apply WRITE should have rewritten id={i} key to flag=true"
);
assert!(
grid.get(&Tagged { id: i, flag: false }).is_none(),
"old flag=false key for id={i} should be gone from obj2loc"
);
}
grid.apply_to_all_values(
|_loc, t| {
Some(Tagged {
id: t.id,
flag: false,
})
},
GridOption::READ,
);
grid.lazy_update();
for i in 0..n {
assert_eq!(
grid.get(&Tagged { id: i, flag: false }),
Some(Tagged { id: i, flag: false }),
"apply READ should have rewritten id={i} back to flag=false"
);
assert!(grid.get(&Tagged { id: i, flag: true }).is_none());
}
grid.apply_to_all_values(
|_loc, t| {
Some(Tagged {
id: t.id,
flag: true,
})
},
GridOption::READWRITE,
);
grid.lazy_update();
for i in 0..n {
assert_eq!(
grid.get(&Tagged { id: i, flag: true }),
Some(Tagged { id: i, flag: true }),
"apply READWRITE should have rewritten id={i} to flag=true"
);
}
}
#[cfg(all(feature = "gis", feature = "parallel"))]
#[test]
fn sparse_a5_grid_gets_parallel() {
let (grid, cells) = london_root_grid();
let loc = cells[0];
assert!(grid.get_objects(&loc).is_none());
grid.set_object_location(42, &loc);
assert!(grid.get_objects_unbuffered(&loc).is_some());
assert_eq!(
grid.get_objects_unbuffered(&loc).unwrap().first().copied(),
Some(42)
);
assert!(grid.get_objects(&loc).is_none());
assert!(grid.get(&42).is_none());
let unbuf_loc = grid.get_location_unbuffered(&42);
assert_eq!(unbuf_loc.as_ref(), Some(&loc));
assert!(grid.get_location(&42).is_none());
let mut grid = grid;
grid.update();
assert_eq!(grid.get_objects(&loc).unwrap().first().copied(), Some(42));
assert_eq!(grid.get(&42), Some(42));
assert_eq!(grid.get_location(&42).as_ref(), Some(&loc));
grid.remove_object(&42);
grid.lazy_update();
assert!(grid.get_objects(&loc).is_none());
assert!(grid.get(&42).is_none());
assert!(grid.get_location(&42).is_none());
}
#[cfg(all(feature = "gis", feature = "parallel"))]
#[test]
fn sparse_a5_grid_iter_and_spatial_parallel() {
let (grid, cells) = london_root_grid();
let centre = cells[0];
let neigh = grid.cell_neighbors(¢re).expect("centre has neighbours");
for (i, c) in neigh.iter().enumerate() {
grid.set_object_location(100 + i as u32, c);
}
grid.set_object_location(0, ¢re);
let count = AtomicUsize::new(0);
grid.iter_objects_unbuffered(|_loc, _obj| {
count.fetch_add(1, Ordering::Relaxed);
});
assert_eq!(count.load(Ordering::Relaxed), neigh.len() + 1);
let mut grid = grid;
grid.update();
let count = AtomicUsize::new(0);
grid.iter_objects(|loc, obj| {
let bag = grid
.get_objects(loc)
.expect("iter handed us a loc that get can't find");
assert!(bag.contains(obj));
count.fetch_add(1, Ordering::Relaxed);
});
assert_eq!(count.load(Ordering::Relaxed), neigh.len() + 1);
assert_eq!(grid.get_neighbors(¢re).len(), neigh.len());
let disk = grid.get_objects_within_disk(¢re, 1);
assert!(disk.contains(&0));
assert_eq!(disk.len(), neigh.len() + 1);
assert!(grid
.get_neighbors_within_distance(¢re, 1_000_000.0)
.contains(&0));
assert!(grid.get_neighbors_within_distance(¢re, 0.0).is_empty());
}
#[cfg(all(feature = "gis", feature = "parallel"))]
#[test]
fn sparse_a5_grid_concurrent_inserts_dont_lose_objects() {
use std::sync::Arc;
use std::thread;
const N_THREADS: u32 = 8;
const PER_THREAD: u32 = 100;
let grid: SparseA5Grid<u32> = SparseA5Grid::new(2);
let cells = grid.all_cells().expect("res-2 children");
assert!(!cells.is_empty());
const SENTINEL: u32 = u32::MAX;
for cell in &cells {
grid.set_object_location(SENTINEL, cell);
}
let grid = Arc::new(grid);
let cells = Arc::new(cells);
let handles: Vec<_> = (0..N_THREADS)
.map(|tid| {
let grid = Arc::clone(&grid);
let cells = Arc::clone(&cells);
thread::spawn(move || {
for i in 0..PER_THREAD {
let id = tid * PER_THREAD + i;
let cell = cells[(id as usize) % cells.len()];
grid.set_object_location(id, &cell);
}
})
})
.collect();
for h in handles {
h.join().expect("worker panic");
}
for id in 0..N_THREADS * PER_THREAD {
assert!(
grid.get_location_unbuffered(&id).is_some(),
"id {id} missing from write buffer pre-update"
);
}
let mut grid = Arc::try_unwrap(grid).unwrap_or_else(|_| panic!("workers leaked an Arc handle"));
grid.lazy_update();
let inserted = (N_THREADS * PER_THREAD) as usize;
let expected_total = cells.len() + inserted;
assert_eq!(
grid.num_objects(),
expected_total,
"lost or duplicated entries during concurrent inserts"
);
for id in 0..N_THREADS * PER_THREAD {
assert_eq!(grid.get(&id), Some(id), "id {id} missing post-update");
let expected_cell = cells[(id as usize) % cells.len()];
assert_eq!(
grid.get_location(&id),
Some(expected_cell),
"id {id} ended up in the wrong cell"
);
}
}