optimize path finding

This commit is contained in:
2024-10-12 12:48:14 -04:00
parent 06ffb2bd3e
commit 3531f9f68f
7 changed files with 117 additions and 37 deletions

View File

@@ -29,4 +29,10 @@ ron = "0.8.1"
image = "0.25.2" image = "0.25.2"
[features] [features]
tracing = ["bevy/trace_tracy", "world_generation/tracing", "buildings/tracing"] tracing = [
"bevy/trace_tracy",
"world_generation/tracing",
"buildings/tracing",
"units/tracing",
"shared/tracing",
]

View File

@@ -24,7 +24,12 @@ fn update_tile_under_cursor(
map: Res<Map>, map: Res<Map>,
mut tile_under_cursor: ResMut<TileUnderCursor>, mut tile_under_cursor: ResMut<TileUnderCursor>,
) { ) {
let win = window.single(); let win_r = window.get_single();
if win_r.is_err() {
return;
}
let win = win_r.unwrap();
let (cam_transform, camera) = cam_query.single(); let (cam_transform, camera) = cam_query.single();
let Some(cursor_pos) = win.cursor_position() else { let Some(cursor_pos) = win.cursor_position() else {
return; return;

View File

@@ -21,4 +21,4 @@ pathfinding = "4.11.0"
ordered-float = "4.3.0" ordered-float = "4.3.0"
[features] [features]
tracing = [] tracing = ["bevy/trace_tracy"]

View File

@@ -27,3 +27,5 @@ pub struct Path(pub Vec<Vec3>, pub usize);
#[derive(Component, Debug)] #[derive(Component, Debug)]
pub struct PathTask(pub Task<Option<CommandQueue>>); pub struct PathTask(pub Task<Option<CommandQueue>>);
#[derive(Component, Debug)]
pub struct PathTaskPending(pub usize);

View File

@@ -1,4 +1,4 @@
use bevy::prelude::Resource; use bevy::prelude::*;
use ordered_float::OrderedFloat; use ordered_float::OrderedFloat;
use world_generation::{hex_utils::HexCoord, prelude::Map}; use world_generation::{hex_utils::HexCoord, prelude::Map};
@@ -79,6 +79,11 @@ impl NavData {
} }
} }
} }
pub fn update_tile(&mut self, coord: &HexCoord, height: f32, move_cost: f32) {
let tile = &mut self.tiles[coord.to_index(self.map_width)];
tile.move_cost = move_cost;
tile.height = height;
}
} }
#[derive(Clone)] #[derive(Clone)]

View File

@@ -1 +1,4 @@
use bevy::prelude::*; use bevy::prelude::*;
#[derive(Resource, Debug, Default)]
pub struct PathBatchId(pub usize);

View File

@@ -2,29 +2,33 @@ use std::collections::HashMap;
use bevy::{ecs::world::CommandQueue, prelude::*, tasks::AsyncComputeTaskPool, utils::futures}; use bevy::{ecs::world::CommandQueue, prelude::*, tasks::AsyncComputeTaskPool, utils::futures};
use pathfinding::prelude::astar; use pathfinding::prelude::astar;
use shared::{resources::TileUnderCursor, sets::GameplaySet}; use shared::{events::TileModifiedEvent, resources::TileUnderCursor, sets::GameplaySet};
use world_generation::{hex_utils::HexCoord, prelude::Map}; use world_generation::{hex_utils::HexCoord, prelude::Map, states::GeneratorState};
#[cfg(debug_assertions)]
use crate::units_debug_plugin::UnitsDebugPlugin;
use crate::{ use crate::{
assets::unit_asset::UnitAssetPlugin, assets::unit_asset::UnitAssetPlugin,
components::{Path, PathTask, Target, Unit}, components::{Path, PathTask, PathTaskPending, Target, Unit},
nav_data::NavData, nav_data::NavData,
units_debug_plugin::UnitsDebugPlugin, resources::PathBatchId,
}; };
pub struct UnitsPlugin; pub struct UnitsPlugin;
impl Plugin for UnitsPlugin { impl Plugin for UnitsPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.init_resource::<PathBatchId>();
app.add_plugins(UnitAssetPlugin); app.add_plugins(UnitAssetPlugin);
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
app.add_plugins(UnitsDebugPlugin); app.add_plugins(UnitsDebugPlugin);
// app.configure_loading_state(LoadingStateConfig::new(AssetLoadState::Loading).load_collection::<UnitDatabase>()); // app.configure_loading_state(LoadingStateConfig::new(AssetLoadState::Loading).load_collection::<UnitDatabase>());
app.add_systems(PostUpdate, build_navdata.run_if(in_state(GeneratorState::SpawnMap)));
app.add_systems(Update, units_control.in_set(GameplaySet)); app.add_systems(Update, units_control.in_set(GameplaySet));
app.add_systems(Update, move_unit.in_set(GameplaySet)); app.add_systems(Update, (move_unit, update_navdata).in_set(GameplaySet));
app.add_systems( app.add_systems(
FixedPreUpdate, FixedPreUpdate,
(dispatch_path_requests, resolve_path_task).in_set(GameplaySet), (dispatch_path_requests, resolve_path_task).in_set(GameplaySet),
@@ -32,6 +36,22 @@ impl Plugin for UnitsPlugin {
} }
} }
fn build_navdata(mut commands: Commands, map: Res<Map>) {
let nav_data = NavData::build(&map);
commands.insert_resource(nav_data);
}
fn update_navdata(mut tile_updates: EventReader<TileModifiedEvent>, mut nav_data: ResMut<NavData>) {
for event in tile_updates.read() {
match event {
TileModifiedEvent::HeightChanged(coord, new_height) => {
nav_data.update_tile(coord, *new_height, 1.0);
}
_ => (),
}
}
}
fn units_control(tile_under_cursor: Res<TileUnderCursor>) {} fn units_control(tile_under_cursor: Res<TileUnderCursor>) {}
fn move_unit( fn move_unit(
@@ -61,8 +81,10 @@ fn move_unit(
} }
fn dispatch_path_requests( fn dispatch_path_requests(
units: Query<(&Transform, &Target, Entity), (With<Unit>, Without<PathTask>)>, units: Query<(&Transform, &Target, Entity), With<Unit>>,
map: Res<Map>, map: Res<Map>,
nav_data: Res<NavData>,
mut batch_id: ResMut<PathBatchId>,
mut commands: Commands, mut commands: Commands,
) { ) {
if units.is_empty() { if units.is_empty() {
@@ -70,6 +92,8 @@ fn dispatch_path_requests(
} }
let mut groups: HashMap<HexCoord, Vec<PathRequest>> = HashMap::new(); let mut groups: HashMap<HexCoord, Vec<PathRequest>> = HashMap::new();
#[cfg(feature = "tracing")]
let _group_span = info_span!("Grouping").entered();
for (transform, target, entity) in units.iter() { for (transform, target, entity) in units.iter() {
let req = PathRequest { let req = PathRequest {
entity, entity,
@@ -81,36 +105,57 @@ fn dispatch_path_requests(
groups.insert(target.0, vec![req]); groups.insert(target.0, vec![req]);
} }
} }
#[cfg(feature = "tracing")]
//todo: only generate when map is changed drop(_group_span);
let nav_data = NavData::build(&map);
let pool = AsyncComputeTaskPool::get(); let pool = AsyncComputeTaskPool::get();
for (target, units) in groups { for (target, units) in groups {
let destinations = get_end_points(&target, units.len(), &map); let id = batch_id.0;
let mut i = 0; batch_id.0 += 1;
for req in units {
let d = nav_data.clone(); for req in &units {
let dst = destinations[i];
i += 1;
let task = pool.spawn(async move {
#[cfg(feature = "tracing")]
let _path_span = info_span!("Path Finding").entered();
if let Some(path) = calculate_path(&req.from, &dst, d) {
let mut queue = CommandQueue::default();
queue.push(move |world: &mut World| {
world.entity_mut(req.entity).insert(path);
});
return Some(queue);
}
return None;
});
commands commands
.entity(req.entity) .entity(req.entity)
.insert(PathTask(task)) .insert(PathTaskPending(id))
.remove::<Target>() .remove::<Target>();
.remove::<Path>();
} }
let destinations = get_end_points(&target, units.len(), &map);
let req = BatchPathRequest::new(units, destinations);
#[cfg(feature = "tracing")]
let _clone_span = info_span!("Nav Data Clone").entered();
let local_nav_data = nav_data.clone();
#[cfg(feature = "tracing")]
drop(_clone_span);
let batch_task = pool.spawn(async move {
let mut i = 0;
let mut queue = CommandQueue::default();
for entitiy_req in req.entities {
let dst = req.destination[i];
i += 1;
#[cfg(feature = "tracing")]
let _path_span = info_span!("Path Finding").entered();
if let Some(path) = calculate_path(&entitiy_req.from, &dst, &local_nav_data) {
queue.push(move |world: &mut World| {
let mut unit_e = world.entity_mut(entitiy_req.entity);
if let Some(pending_task) = unit_e.get::<PathTaskPending>() {
if pending_task.0 == id {
unit_e.insert(path);
unit_e.remove::<PathTaskPending>();
}
}
});
}
}
if queue.is_empty() {
return None;
}
return Some(queue);
});
commands.spawn(PathTask(batch_task));
} }
} }
@@ -144,18 +189,18 @@ fn get_end_points(coord: &HexCoord, count: usize, map: &Map) -> Vec<HexCoord> {
return result; return result;
} }
fn resolve_path_task(mut tasks: Query<(&mut PathTask, Entity), With<Unit>>, mut commands: Commands) { fn resolve_path_task(mut tasks: Query<(&mut PathTask, Entity)>, mut commands: Commands) {
for (mut task, entity) in tasks.iter_mut() { for (mut task, entity) in tasks.iter_mut() {
if let Some(c) = futures::check_ready(&mut task.0) { if let Some(c) = futures::check_ready(&mut task.0) {
if let Some(mut queue) = c { if let Some(mut queue) = c {
commands.append(&mut queue); commands.append(&mut queue);
} }
commands.entity(entity).remove::<PathTask>(); commands.entity(entity).despawn();
} }
} }
} }
fn calculate_path(from: &HexCoord, to: &HexCoord, nav: NavData) -> Option<Path> { fn calculate_path(from: &HexCoord, to: &HexCoord, nav: &NavData) -> Option<Path> {
let path = astar( let path = astar(
from, from,
|n| nav.get_neighbors(n), |n| nav.get_neighbors(n),
@@ -173,3 +218,17 @@ struct PathRequest {
pub entity: Entity, pub entity: Entity,
pub from: HexCoord, pub from: HexCoord,
} }
struct BatchPathRequest {
pub entities: Vec<PathRequest>,
pub destination: Vec<HexCoord>,
}
impl BatchPathRequest {
pub fn new(entities: Vec<PathRequest>, dst: Vec<HexCoord>) -> Self {
return Self {
destination: dst,
entities,
};
}
}