fix
This commit is contained in:
@@ -9,7 +9,7 @@ use crate::world::VisibleGameEntity;
|
|||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use bevy_rand::prelude::*;
|
use bevy_rand::prelude::*;
|
||||||
use rand::RngExt;
|
use rand::RngExt;
|
||||||
use smallvec::smallvec;
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
#[derive(Bundle)]
|
#[derive(Bundle)]
|
||||||
pub struct Dorf {
|
pub struct Dorf {
|
||||||
@@ -53,11 +53,11 @@ impl Dorf {
|
|||||||
haul_slot: HaulSlot::default(),
|
haul_slot: HaulSlot::default(),
|
||||||
carry_visual: CarryVisualState::new(normal_sprite, carry_sprite),
|
carry_visual: CarryVisualState::new(normal_sprite, carry_sprite),
|
||||||
task_queue: TaskQueue {
|
task_queue: TaskQueue {
|
||||||
tasks: smallvec![Task::Idle {
|
tasks: VecDeque::from([Task::Idle {
|
||||||
target: origin,
|
target: origin,
|
||||||
wander_radius: 10,
|
wander_radius: 10,
|
||||||
origin,
|
origin,
|
||||||
}],
|
}]),
|
||||||
},
|
},
|
||||||
task_state: TaskState::Pending,
|
task_state: TaskState::Pending,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,12 +7,12 @@
|
|||||||
//! - All task data is owned — no references to world state — for cache locality.
|
//! - All task data is owned — no references to world state — for cache locality.
|
||||||
//!
|
//!
|
||||||
//! # Performance
|
//! # Performance
|
||||||
//! - SmallVec<[Task; 4]> for queue — avoids heap alloc for typical short queues.
|
//! - VecDeque for queue — O(1) push/pop at both ends.
|
||||||
//! - Task enum is #[repr(u8)] for compact storage and fast matching.
|
//! - Task enum is #[repr(u8)] for compact storage and fast matching.
|
||||||
//! - No dynamic dispatch — match on Task variant directly in executor.
|
//! - No dynamic dispatch — match on Task variant directly in executor.
|
||||||
|
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use smallvec::SmallVec;
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
/// A single task an entity can execute.
|
/// A single task an entity can execute.
|
||||||
///
|
///
|
||||||
@@ -88,10 +88,10 @@ impl Task {
|
|||||||
/// Tasks are pushed to the back; executor pops from front when complete.
|
/// Tasks are pushed to the back; executor pops from front when complete.
|
||||||
/// Higher-priority tasks can be inserted at front via `push_front`.
|
/// Higher-priority tasks can be inserted at front via `push_front`.
|
||||||
///
|
///
|
||||||
/// Uses SmallVec to avoid heap allocation for typical queue lengths (<4 tasks).
|
/// Uses VecDeque for O(1) push/pop at both ends.
|
||||||
#[derive(Component, Debug, Default)]
|
#[derive(Component, Debug, Default)]
|
||||||
pub struct TaskQueue {
|
pub struct TaskQueue {
|
||||||
pub tasks: SmallVec<[Task; 4]>,
|
pub tasks: VecDeque<Task>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TaskQueue {
|
impl TaskQueue {
|
||||||
@@ -102,31 +102,27 @@ impl TaskQueue {
|
|||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn current(&self) -> Option<&Task> {
|
pub fn current(&self) -> Option<&Task> {
|
||||||
self.tasks.first()
|
self.tasks.front()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn current_mut(&mut self) -> Option<&mut Task> {
|
pub fn current_mut(&mut self) -> Option<&mut Task> {
|
||||||
self.tasks.first_mut()
|
self.tasks.front_mut()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn push(&mut self, task: Task) {
|
pub fn push(&mut self, task: Task) {
|
||||||
self.tasks.push(task);
|
self.tasks.push_back(task);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn push_front(&mut self, task: Task) {
|
pub fn push_front(&mut self, task: Task) {
|
||||||
self.tasks.insert(0, task);
|
self.tasks.push_front(task);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn pop(&mut self) -> Option<Task> {
|
pub fn pop(&mut self) -> Option<Task> {
|
||||||
if self.tasks.is_empty() {
|
self.tasks.pop_front()
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(self.tasks.remove(0))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|||||||
@@ -33,8 +33,11 @@ pub fn task_executor_system(
|
|||||||
mut completed_writer: MessageWriter<TaskCompleted>,
|
mut completed_writer: MessageWriter<TaskCompleted>,
|
||||||
mut failed_writer: MessageWriter<TaskFailed>,
|
mut failed_writer: MessageWriter<TaskFailed>,
|
||||||
) {
|
) {
|
||||||
let Ok(mut rng) = rng_q.single_mut() else {
|
// Get mutable access to the single GlobalRng entity
|
||||||
return;
|
// This must be done once per frame, not per entity
|
||||||
|
let mut rng = match rng_q.single_mut() {
|
||||||
|
Ok(r) => r,
|
||||||
|
Err(_) => return, // No GlobalRng entity exists yet
|
||||||
};
|
};
|
||||||
|
|
||||||
for (entity, mut queue, mut state, mut ambulatory, transform) in query.iter_mut() {
|
for (entity, mut queue, mut state, mut ambulatory, transform) in query.iter_mut() {
|
||||||
|
|||||||
+17
-10
@@ -31,14 +31,20 @@ pub(super) fn execute_idle(
|
|||||||
|
|
||||||
let entity_pos = (transform.translation / ITILE_SIZE as f32).as_ivec3();
|
let entity_pos = (transform.translation / ITILE_SIZE as f32).as_ivec3();
|
||||||
|
|
||||||
|
// Track if target actually changed this tick
|
||||||
|
let mut target_changed = false;
|
||||||
|
|
||||||
// If reached target or target is no longer standable, pick new target
|
// If reached target or target is no longer standable, pick new target
|
||||||
if entity_pos == *target || !tilemap.is_standable(*target) {
|
if entity_pos == *target || !tilemap.is_standable(*target) {
|
||||||
if let Some(new_target) = pick_wander_target(origin, *wander_radius, tilemap, rng) {
|
if let Some(new_target) = pick_wander_target(origin, *wander_radius, tilemap, rng) {
|
||||||
*target = new_target;
|
*target = new_target;
|
||||||
|
target_changed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set ambulatory target - pathfinding system will compute path
|
// Only reset path if target changed or no path exists
|
||||||
|
// This avoids forcing pathfinding to recompute every frame
|
||||||
|
if target_changed || ambulatory.current_path.is_none() {
|
||||||
ambulatory.target = Some(Vec3::new(
|
ambulatory.target = Some(Vec3::new(
|
||||||
target.x as f32,
|
target.x as f32,
|
||||||
target.y as f32,
|
target.y as f32,
|
||||||
@@ -47,17 +53,20 @@ pub(super) fn execute_idle(
|
|||||||
ambulatory.current_path = None;
|
ambulatory.current_path = None;
|
||||||
ambulatory.path_index = 0;
|
ambulatory.path_index = 0;
|
||||||
}
|
}
|
||||||
|
// Otherwise: keep existing path, let movement system continue along it
|
||||||
|
}
|
||||||
|
|
||||||
/// Pick a random standable tile within wander radius of origin.
|
/// Pick a random standable tile within wander radius of origin.
|
||||||
|
/// Uses reservoir sampling to avoid heap allocation.
|
||||||
fn pick_wander_target(
|
fn pick_wander_target(
|
||||||
origin: &IVec3,
|
origin: &IVec3,
|
||||||
radius: i32,
|
radius: i32,
|
||||||
tilemap: &TileMap,
|
tilemap: &TileMap,
|
||||||
rng: &mut WyRand,
|
rng: &mut WyRand,
|
||||||
) -> Option<IVec3> {
|
) -> Option<IVec3> {
|
||||||
let mut candidates = smallvec::SmallVec::<[IVec3; 16]>::new();
|
let mut chosen = None;
|
||||||
|
let mut count = 0u32;
|
||||||
|
|
||||||
// Collect valid tiles within radius
|
|
||||||
for dx in -radius..=radius {
|
for dx in -radius..=radius {
|
||||||
for dy in -radius..=radius {
|
for dy in -radius..=radius {
|
||||||
let candidate = IVec3::new(
|
let candidate = IVec3::new(
|
||||||
@@ -66,15 +75,13 @@ fn pick_wander_target(
|
|||||||
origin.z,
|
origin.z,
|
||||||
);
|
);
|
||||||
if tilemap.is_standable(candidate) {
|
if tilemap.is_standable(candidate) {
|
||||||
candidates.push(candidate);
|
count += 1;
|
||||||
|
// Reservoir sampling: 1/count chance to replace chosen
|
||||||
|
if rng.random_range(0..count) == 0 {
|
||||||
|
chosen = Some(candidate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if candidates.is_empty() {
|
|
||||||
return None;
|
|
||||||
}
|
}
|
||||||
|
chosen
|
||||||
let idx = rng.random_range(0..candidates.len());
|
|
||||||
Some(candidates[idx])
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user