refactor: simplify to time-sliced synchronous path queue
The async pathfinding with StandableBitGrid caused massive lag due to synchronous bounding box calculation in the main thread (millions of hashmap lookups for long paths). Additionally, the splicing logic had a catastrophic bug where it applied a single finished path to ALL entities unconditionally. Solution: - Revert to thread-local synchronous A* - Implement PathRequestQueue to process max 8 paths per frame - Keep provisional paths for immediate movement - Since entity walks provisional path, full path calculates from current position, eliminating the need for complex splicing logic.
This commit is contained in:
@@ -1,15 +1,12 @@
|
||||
use bevy::prelude::*;
|
||||
use bevy::tasks::AsyncComputeTaskPool;
|
||||
use rayon::prelude::*;
|
||||
use rustc_hash::FxHashMap;
|
||||
use rustc_hash::FxHashSet;
|
||||
use std::{cell::RefCell, collections::BinaryHeap, time::Instant};
|
||||
use std::{cell::RefCell, collections::BinaryHeap, collections::VecDeque, time::Instant};
|
||||
|
||||
use crate::constants::{
|
||||
ITILE_SIZE, PATHFINDER_MAX_NODES, PATHFINDER_PROVISIONAL_NODE_LIMIT, TILE_SIZE,
|
||||
};
|
||||
use crate::entities::shared_components::PendingAsyncPath;
|
||||
use crate::world::tiles::tilemap::StandableBitGrid;
|
||||
use crate::world::tiles::TileMap;
|
||||
use crate::world::{chunks::ChunkMap, chunks::CHUNK_SIZE};
|
||||
use crate::{constants::*, entities::shared_components::Ambulatory};
|
||||
@@ -134,6 +131,13 @@ impl PathfindingBenchmark {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Resource, Default)]
|
||||
pub struct PathRequestQueue {
|
||||
pub pending: VecDeque<(Entity, IVec3, IVec3)>,
|
||||
}
|
||||
|
||||
const MAX_PATHS_PER_FRAME: usize = 8;
|
||||
|
||||
pub struct PathfindingPlugin;
|
||||
|
||||
impl Plugin for PathfindingPlugin {
|
||||
@@ -141,9 +145,7 @@ impl Plugin for PathfindingPlugin {
|
||||
app.insert_resource(PathfindingBenchmark::new(100))
|
||||
.insert_resource(crate::entities::shared_components::CompletedPaths::default())
|
||||
.insert_resource(crate::entities::shared_components::PathRequestCounter::default())
|
||||
.insert_resource(
|
||||
crate::entities::shared_systems::async_pathfinding::AsyncPathCounter::default(),
|
||||
)
|
||||
.insert_resource(PathRequestQueue::default())
|
||||
.add_systems(
|
||||
FixedUpdate,
|
||||
(prepare_paths, update_wandering_targets, movement).chain(),
|
||||
@@ -153,8 +155,7 @@ impl Plugin for PathfindingPlugin {
|
||||
(
|
||||
merge_benchmark_stats,
|
||||
process_completed_paths,
|
||||
crate::entities::shared_systems::async_pathfinding::poll_async_paths,
|
||||
splice_completed_async_paths,
|
||||
process_path_queue,
|
||||
),
|
||||
)
|
||||
.add_systems(Update, bench_report_system);
|
||||
@@ -163,26 +164,19 @@ impl Plugin for PathfindingPlugin {
|
||||
|
||||
pub fn prepare_paths(
|
||||
mut commands: Commands,
|
||||
mut counter: ResMut<crate::entities::shared_systems::async_pathfinding::AsyncPathCounter>,
|
||||
mut queue: ResMut<PathRequestQueue>,
|
||||
mut query: Query<
|
||||
(
|
||||
Entity,
|
||||
&mut crate::entities::shared_components::Ambulatory,
|
||||
&Transform,
|
||||
Option<&crate::entities::shared_components::PendingAsyncPath>,
|
||||
),
|
||||
Without<crate::entities::shared_components::PendingPath>,
|
||||
>,
|
||||
tilemap: Res<TileMap>,
|
||||
) {
|
||||
for (entity, mut ambulatory, transform, pending_async) in query.iter_mut() {
|
||||
if ambulatory.current_path.is_some() {
|
||||
continue;
|
||||
}
|
||||
if pending_async.is_some() {
|
||||
continue;
|
||||
}
|
||||
if ambulatory.target.is_none() {
|
||||
for (entity, mut ambulatory, transform) in query.iter_mut() {
|
||||
if ambulatory.current_path.is_some() || ambulatory.target.is_none() {
|
||||
continue;
|
||||
}
|
||||
let Some(target) = ambulatory.target else {
|
||||
@@ -204,19 +198,18 @@ pub fn prepare_paths(
|
||||
PATHFINDER_PROVISIONAL_NODE_LIMIT,
|
||||
);
|
||||
if !provisional.is_empty() {
|
||||
ambulatory.current_path = Some(provisional.clone());
|
||||
ambulatory.current_path = Some(provisional);
|
||||
ambulatory.path_index = 0;
|
||||
|
||||
let request_id = counter.next();
|
||||
let pending =
|
||||
crate::entities::shared_systems::async_pathfinding::spawn_async_path_task(
|
||||
&tilemap,
|
||||
queue.pending.push_back((entity, start, goal));
|
||||
commands
|
||||
.entity(entity)
|
||||
.insert(crate::entities::shared_components::PendingPath {
|
||||
start,
|
||||
goal,
|
||||
provisional,
|
||||
request_id,
|
||||
);
|
||||
commands.entity(entity).insert(pending);
|
||||
waypoint_path: Vec::new(),
|
||||
request_id: 0,
|
||||
});
|
||||
} else {
|
||||
let path = calculate_path_benchmarked(&tilemap, start, goal);
|
||||
ambulatory.current_path = Some(path);
|
||||
@@ -226,38 +219,38 @@ pub fn prepare_paths(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn splice_completed_async_paths(
|
||||
mut completed: ResMut<crate::entities::shared_components::CompletedPaths>,
|
||||
mut query: Query<(
|
||||
Entity,
|
||||
&mut crate::entities::shared_components::Ambulatory,
|
||||
&Transform,
|
||||
)>,
|
||||
pub fn process_path_queue(
|
||||
mut commands: Commands,
|
||||
mut queue: ResMut<PathRequestQueue>,
|
||||
tilemap: Res<TileMap>,
|
||||
mut query: Query<
|
||||
(Entity, &mut Ambulatory, &Transform),
|
||||
With<crate::entities::shared_components::PendingPath>,
|
||||
>,
|
||||
) {
|
||||
if completed.paths.is_empty() {
|
||||
return;
|
||||
}
|
||||
let mut processed = 0;
|
||||
while processed < MAX_PATHS_PER_FRAME {
|
||||
if let Some((entity, _old_start, goal)) = queue.pending.pop_front() {
|
||||
processed += 1;
|
||||
|
||||
let mut to_process = Vec::new();
|
||||
for (request_id, path) in completed.paths.drain(..) {
|
||||
to_process.push((request_id, path));
|
||||
}
|
||||
if let Ok((_, mut ambulatory, transform)) = query.get_mut(entity) {
|
||||
let actual_start = transform.translation.as_ivec3();
|
||||
let full_path = calculate_path_benchmarked(&tilemap, actual_start, goal);
|
||||
|
||||
for (request_id, path) in to_process {
|
||||
for (entity, mut ambulatory, _transform) in query.iter_mut() {
|
||||
if ambulatory.current_path.as_ref().is_none_or(|p| p != &path) {
|
||||
let splice_index = find_splice_point(&path, ambulatory.path_index);
|
||||
ambulatory.current_path = Some(path.clone());
|
||||
ambulatory.path_index = splice_index;
|
||||
if !full_path.is_empty() {
|
||||
ambulatory.current_path = Some(full_path);
|
||||
ambulatory.path_index = 0;
|
||||
}
|
||||
commands
|
||||
.entity(entity)
|
||||
.remove::<crate::entities::shared_components::PendingPath>();
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn find_splice_point(path: &[Vec3], current_index: usize) -> usize {
|
||||
current_index.min(path.len().saturating_sub(1))
|
||||
}
|
||||
|
||||
pub fn process_completed_paths(
|
||||
mut completed: ResMut<crate::entities::shared_components::CompletedPaths>,
|
||||
mut query: Query<(
|
||||
|
||||
Reference in New Issue
Block a user