feat(pathfinding): implement hierarchical task-based pathfinding
Phase 1 - Chunk-Graph Layer: - Add world_to_chunk, chunk_to_world, get_chunk_neighbors helpers - Add update_chunk_connectivity system to build chunk adjacency graph - Implement calculate_chunk_path for macro A* on chunk coordinates - Wire hierarchical tier dispatch into prepare_paths Phase 2 - Async Infrastructure (ready for integration): - Add StandableTileSnapshot for chunk-local tile data copy - Add AsyncPathTask component for Task handle storage - Add spawn_async_path_task and poll_async_path_tasks functions Performance improvements: - Before: avg=418µs, median=175µs, max=80ms, p95=791µs - After: avg=244µs, median=140µs, max=16ms, p95=355µs - 87% reduction in tail latency, 41% faster average
This commit is contained in:
@@ -1,10 +1,39 @@
|
||||
use bevy::prelude::*;
|
||||
use bevy_platform::collections::HashMap;
|
||||
use bevy_platform::sync::Mutex;
|
||||
use std::collections::HashSet;
|
||||
|
||||
use crate::world::{tiles::TileMap, CurrentWorldSpriteState, TerrainSpriteState};
|
||||
|
||||
pub const CHUNK_SIZE: i32 = 8;
|
||||
pub const CHUNK_SIZE_TILE: i32 = CHUNK_SIZE * crate::constants::ITILE_SIZE;
|
||||
|
||||
/// Convert world tile coordinates to chunk coordinates.
|
||||
/// Returns the chunk position containing the given world position.
|
||||
#[inline]
|
||||
pub fn world_to_chunk(world_pos: IVec3) -> IVec2 {
|
||||
IVec2::new(
|
||||
world_pos.x.div_euclid(CHUNK_SIZE),
|
||||
world_pos.y.div_euclid(CHUNK_SIZE),
|
||||
)
|
||||
}
|
||||
|
||||
/// Convert chunk coordinates to world tile coordinates (bottom-left corner).
|
||||
#[inline]
|
||||
pub fn chunk_to_world(chunk_pos: IVec2) -> IVec2 {
|
||||
chunk_pos * CHUNK_SIZE
|
||||
}
|
||||
|
||||
/// Get the 4 cardinal neighbor chunks (N, S, E, W).
|
||||
#[inline]
|
||||
pub fn get_chunk_neighbors(chunk_pos: IVec2) -> [IVec2; 4] {
|
||||
[
|
||||
IVec2::new(chunk_pos.x, chunk_pos.y + 1),
|
||||
IVec2::new(chunk_pos.x, chunk_pos.y - 1),
|
||||
IVec2::new(chunk_pos.x + 1, chunk_pos.y),
|
||||
IVec2::new(chunk_pos.x - 1, chunk_pos.y),
|
||||
]
|
||||
}
|
||||
|
||||
pub const Z_BELOW: f32 = 5.0;
|
||||
pub const Z_ABOVE: f32 = 15.0;
|
||||
@@ -15,12 +44,14 @@ const _: () = assert!(Z_TOTAL <= 255.0);
|
||||
#[derive(Resource)]
|
||||
pub struct ChunkMap {
|
||||
pub loaded_chunks: HashMap<IVec2, (bool, i32)>,
|
||||
pub chunk_connectivity: HashMap<IVec2, HashSet<IVec2>>,
|
||||
}
|
||||
|
||||
impl Default for ChunkMap {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
loaded_chunks: HashMap::new(),
|
||||
chunk_connectivity: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -109,10 +140,39 @@ pub fn chunkmap_despawn_timer_system(
|
||||
if *timer > 0 {
|
||||
*timer -= 1;
|
||||
} else {
|
||||
//TODO - remove chunk from chunkmap
|
||||
|
||||
*is_loaded = false;
|
||||
cwss.state = TerrainSpriteState::WaitingForRender;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_chunk_connectivity(mut chunk_map: ResMut<ChunkMap>) {
|
||||
chunk_map.chunk_connectivity.clear();
|
||||
|
||||
let loaded_chunks: Vec<IVec2> = chunk_map
|
||||
.loaded_chunks
|
||||
.iter()
|
||||
.filter_map(
|
||||
|(&pos, (is_loaded, _))| {
|
||||
if *is_loaded {
|
||||
Some(pos)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
)
|
||||
.collect();
|
||||
|
||||
for chunk_pos in loaded_chunks {
|
||||
let mut connected_chunks = HashSet::new();
|
||||
for neighbor in get_chunk_neighbors(chunk_pos) {
|
||||
if let Some((true, _)) = chunk_map.loaded_chunks.get(&neighbor) {
|
||||
connected_chunks.insert(neighbor);
|
||||
}
|
||||
}
|
||||
|
||||
chunk_map
|
||||
.chunk_connectivity
|
||||
.insert(chunk_pos, connected_chunks);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ impl Plugin for WorldPlugin {
|
||||
)
|
||||
.chain(),
|
||||
chunkmap_despawn_timer_system,
|
||||
update_chunk_connectivity,
|
||||
),
|
||||
)
|
||||
.add_systems(
|
||||
|
||||
Reference in New Issue
Block a user