use crate::{
chunks::Chunks,
content::sync_at_chunk_from_position,
loader::ChunkLoader,
occupancy::{
Occupancy, resync_occupied_positions, sync_occupancy_register, sync_occupancy_unregister,
},
terrain::{
Navigation, resync_navigation_positions, sync_navigation_register,
sync_navigation_unregister,
},
};
use bevy::prelude::*;
pub mod chunks;
pub mod content;
pub mod loader;
pub mod occupancy;
pub mod terrain;
pub const CHUNK_EXP: usize = 3;
pub const CHUNK_SIZE: usize = 1 << CHUNK_EXP;
pub const CHUNK_AREA: usize = CHUNK_SIZE * CHUNK_SIZE;
pub const CHUNK_MASK: usize = CHUNK_SIZE - 1;
pub struct ChunkPlugin;
impl Plugin for ChunkPlugin {
fn build(&self, app: &mut App) {
app.init_resource::<Chunks>().init_resource::<ChunkLoader>();
app.add_observer(sync_occupancy_register)
.add_observer(sync_occupancy_unregister)
.add_observer(sync_at_chunk_from_position)
.add_observer(resync_occupied_positions)
.add_observer(sync_navigation_register)
.add_observer(sync_navigation_unregister)
.add_observer(resync_navigation_positions);
}
}
#[derive(Component)]
#[component(immutable)]
pub struct Active;
#[derive(Component)]
#[component(immutable)]
pub struct Inactive;
#[derive(Component)]
#[component(immutable)]
#[require(Occupancy, Navigation)]
pub struct Chunk;
#[cfg(test)]
mod tests {
use super::*;
use crate::content::{AtChunk, Content};
use suon_position::position::Position;
#[test]
fn should_expose_consistent_chunk_constants() {
assert_eq!(
CHUNK_EXP, 3,
"Chunk exponent should remain the configured value"
);
assert_eq!(
CHUNK_SIZE,
1 << CHUNK_EXP,
"Chunk size should be derived from the exponent"
);
assert_eq!(
CHUNK_AREA,
CHUNK_SIZE * CHUNK_SIZE,
"Chunk area should match the square of the edge size"
);
assert_eq!(
CHUNK_MASK,
CHUNK_SIZE - 1,
"Chunk mask should cover local coordinates inside one chunk"
);
}
#[test]
fn should_initialize_chunk_resources_when_plugin_is_added() {
let mut app = App::new();
app.add_plugins(MinimalPlugins);
app.add_plugins(ChunkPlugin);
assert!(
app.world().contains_resource::<Chunks>(),
"ChunkPlugin should initialize the chunk registry resource"
);
assert!(
app.world().contains_resource::<ChunkLoader>(),
"ChunkPlugin should initialize the chunk loader resource"
);
}
#[test]
fn should_require_occupancy_when_spawning_chunk_component() {
let mut world = World::new();
let entity = world.spawn(Chunk).id();
assert!(
world.entity(entity).contains::<Occupancy>(),
"Spawning Chunk should automatically attach Occupancy"
);
}
#[test]
fn should_require_navigation_when_spawning_chunk_component() {
let mut world = World::new();
let entity = world.spawn(Chunk).id();
assert!(
world.entity(entity).contains::<Navigation>(),
"Spawning Chunk should automatically attach Navigation"
);
}
#[test]
fn should_spawn_active_and_inactive_marker_components() {
let mut world = World::new();
let active = world.spawn(Active).id();
let inactive = world.spawn(Inactive).id();
assert!(
world.entity(active).contains::<Active>(),
"Active should behave like a plain marker component when spawned"
);
assert!(
world.entity(inactive).contains::<Inactive>(),
"Inactive should behave like a plain marker component when spawned"
);
}
#[test]
fn should_link_content_back_to_chunk_through_relationship() {
let mut world = World::new();
let chunk = world.spawn_empty().id();
let entity = world.spawn(AtChunk::new(chunk)).id();
let at_chunk = world
.get::<AtChunk>(entity)
.expect("Entity should contain the AtChunk relationship");
let content = world
.get::<Content>(chunk)
.expect("Chunk should receive the Content relationship target");
assert_eq!(
at_chunk.entity(),
chunk,
"The relationship should preserve the target chunk entity"
);
assert!(
content.contains(&entity),
"The chunk content list should include linked entities"
);
}
#[test]
fn should_sync_at_chunk_from_position_automatically() {
let mut app = App::new();
app.add_plugins(MinimalPlugins);
app.add_plugins(ChunkPlugin);
let chunk = app.world_mut().spawn(Chunk).id();
app.insert_resource(Chunks::from_iter([(Position { x: 4, y: 4 }, chunk)]));
let entity = app.world_mut().spawn(Position { x: 4, y: 4 }).id();
app.update();
let at_chunk = app
.world()
.get::<AtChunk>(entity)
.expect("Position synchronization should assign AtChunk automatically");
assert_eq!(
at_chunk.entity(),
chunk,
"The derived chunk relationship should match the chunk registry"
);
}
}