use bevy::log::LogPlugin;
use bevy::prelude::*;
use bevy::utils::HashMap;
use ryot_core::prelude::Navigable;
use ryot_pathfinder::components::{Path, PathFindingQuery};
use ryot_pathfinder::pathable::Pathable;
use ryot_pathfinder::prelude::PathableApp;
use ryot_utils::cache::Cache;
use std::fmt::Debug;
use std::time::{Duration, Instant};
#[cfg(feature = "ryot_tiled")]
use ryot_tiled::tile_size;
#[cfg(not(feature = "ryot_tiled"))]
fn tile_size() -> UVec2 {
UVec2::new(32, 32)
}
#[derive(Component, Copy, Clone)]
pub(crate) struct Pathing<P: Pathable>(P);
#[derive(Component, Copy, Clone)]
pub(crate) struct Obstacle;
#[derive(Copy, Clone)]
pub struct ExampleBuilder<
P: Pathable + Component + Debug + Into<Vec2>,
N: Navigable + Copy + Default,
> {
pub grid_size: i32,
pub n_entities: usize,
pub n_obstacles: usize,
pub max_distance: i32,
pub sleep: u64,
pub navigable: N,
pub query_builder: fn(P) -> PathFindingQuery<P>,
pub _phantom: std::marker::PhantomData<P>,
}
impl<P: Pathable + Default + Component + Debug + Into<Vec2>, N: Navigable + Copy + Default> Default
for ExampleBuilder<P, N>
{
fn default() -> Self {
Self {
grid_size: 10,
n_entities: 1,
n_obstacles: 0,
max_distance: 10,
sleep: 100,
navigable: N::default(),
query_builder: |pos| PathFindingQuery::new(pos).with_success_distance(0.),
_phantom: std::marker::PhantomData,
}
}
}
#[allow(dead_code)]
impl<P: Pathable + Default + Component + Debug + Into<Vec2>, N: Navigable + Copy + Default>
ExampleBuilder<P, N>
{
pub fn with_grid_size(mut self, grid_size: i32) -> Self {
self.grid_size = grid_size;
self
}
pub fn with_n_entities(mut self, n_entities: usize) -> Self {
self.n_entities = n_entities;
self
}
pub fn with_n_obstacles(mut self, n_obstacles: usize) -> Self {
self.n_obstacles = n_obstacles;
self
}
pub fn with_max_distance(mut self, max_distance: i32) -> Self {
self.max_distance = max_distance;
self
}
pub fn with_sleep(mut self, sleep: u64) -> Self {
self.sleep = sleep;
self
}
pub fn with_navigable(mut self, navigable: N) -> Self {
self.navigable = navigable;
self
}
pub fn with_query_builder(mut self, query_builder: fn(P) -> PathFindingQuery<P>) -> Self {
self.query_builder = query_builder;
self
}
pub fn drawing_app(&self) -> App {
let mut app = App::new();
app.add_plugins(DefaultPlugins)
.add_systems(Startup, basic_setup)
.add_systems(First, self.draw_grid())
.add_systems(Last, draw_actors::<P>)
.add_systems(Update, draw_target::<P>)
.add_systems(Update, draw_obstacles::<P>);
app.add_pathable::<P, N>()
.add_systems(Startup, (self.spawn_many(), self.spawn_obstacles(true)))
.add_systems(Update, (self.start_path(), self.process_path()));
app
}
pub fn minimum_app(&self) -> App {
let mut app = App::new();
app.add_plugins(MinimalPlugins)
.add_plugins(LogPlugin::default());
app.add_pathable::<P, N>()
.add_systems(Startup, (self.spawn_many(), self.spawn_obstacles(false)))
.add_systems(Update, (self.start_path(), self.process_path()));
app
}
pub fn start_path(
&self,
) -> impl FnMut(
Commands,
Res<Cache<P, N>>,
Query<(Entity, &P), (Without<Pathing<P>>, Without<Obstacle>)>,
) {
let builder = *self;
move |mut commands: Commands,
cache: Res<Cache<P, N>>,
q_pos: Query<(Entity, &P), (Without<Pathing<P>>, Without<Obstacle>)>| {
for (entity, current_pos) in q_pos.iter() {
let mut pos = builder.random_from_pos(current_pos);
while !cache
.read()
.unwrap()
.get(&pos)
.copied()
.unwrap_or_default()
.is_walkable()
{
pos = builder.random_from_pos(current_pos);
}
debug!("Starting path from {:?} to {:?}", current_pos, pos);
commands
.entity(entity)
.insert(((builder.query_builder)(pos), Pathing(pos)));
}
}
}
pub fn spawn_many(&self) -> impl FnMut(Commands) {
let builder = *self;
move |mut commands: Commands| {
for _ in 0..builder.n_entities {
commands.spawn(builder.random_pos());
}
}
}
pub fn spawn_obstacles(&self, draw: bool) -> impl FnMut(Commands, ResMut<Cache<P, N>>) {
let builder = *self;
move |mut commands: Commands, cache: ResMut<Cache<P, N>>| {
let Ok(mut write_guard) = cache.write() else {
return;
};
for _ in 0..builder.n_obstacles {
let pos = builder.random_pos();
write_guard.insert(pos, builder.navigable);
if draw {
commands.spawn((pos, Obstacle));
}
}
}
}
pub fn process_path(
&self,
) -> impl FnMut(
Commands,
Query<(Entity, &mut P, &mut Path<P>, Option<&PathFindingQuery<P>>)>,
Local<HashMap<Entity, Instant>>,
) {
let builder = *self;
move |mut commands: Commands,
mut q_paths: Query<(Entity, &mut P, &mut Path<P>, Option<&PathFindingQuery<P>>)>,
mut last_executed: Local<HashMap<Entity, Instant>>| {
for (entity, mut pos, mut path, path_query) in q_paths.iter_mut() {
if last_executed
.entry(entity)
.or_insert(Instant::now())
.elapsed()
< Duration::from_millis(builder.sleep)
{
return;
}
last_executed.insert(entity, Instant::now());
if path.is_empty() && path_query.is_none() {
commands.entity(entity).remove::<Pathing<P>>();
continue;
}
let Some(next_pos) = path.first().copied() else {
return;
};
path.remove(0);
*pos = next_pos;
}
}
}
pub fn draw_grid(&self) -> impl FnMut(Gizmos) {
let builder = *self;
move |mut gizmos: Gizmos| {
for x in -builder.grid_size..=builder.grid_size {
for y in -builder.grid_size..=builder.grid_size {
gizmos.rect_2d(
P::generate(x, y, 0).into(),
0.,
tile_size().as_vec2(),
Color::WHITE,
);
}
}
}
}
pub fn random_pos(&self) -> P {
P::generate(
rand::random::<i32>() % self.grid_size,
rand::random::<i32>() % self.grid_size,
0,
)
}
pub fn random_from_pos(&self, pos: &P) -> P {
let (x, y, z) = pos.coordinates();
P::generate(
(x + rand::random::<i32>() % self.max_distance).clamp(-self.grid_size, self.grid_size),
(y + rand::random::<i32>() % self.max_distance).clamp(-self.grid_size, self.grid_size),
z,
)
}
}
pub fn basic_setup(mut commands: Commands) {
commands.spawn(Camera2dBundle::default());
}
pub fn draw_actors<P: Pathable + Component + Into<Vec2>>(
mut gizmos: Gizmos,
mut q_paths: Query<&P, Without<Obstacle>>,
) {
for pos in q_paths.iter_mut() {
gizmos.circle_2d((*pos).into(), (tile_size().x / 2) as f32, Color::BLUE);
}
}
pub fn draw_target<P: Pathable + Component + Into<Vec2>>(
mut gizmos: Gizmos,
mut q_targets: Query<&Pathing<P>>,
) {
for Pathing(pos) in q_targets.iter_mut() {
gizmos.circle_2d((*pos).into(), (tile_size().x / 2) as f32, Color::GREEN);
}
}
pub fn draw_obstacles<P: Pathable + Component + Into<Vec2>>(
mut gizmos: Gizmos,
mut q_obstacles: Query<&P, With<Obstacle>>,
) {
for pos in q_obstacles.iter_mut() {
gizmos.rect_2d((*pos).into(), 0., tile_size().as_vec2() / 2., Color::RED);
}
}