Refactor job queue: add JobState enum, improve pathfinding fallback
- Add JobState enum (Unclaimed, Claimed, Complete) replacing boolean claimed flag - Increase provisional node limit from 256 to 4096 - Add iter_unclaimed() and claim_job_at() methods to JobQueue - Update job_assignment to filter idle dorfs, match by distance - Add unclaim_jobs_for_entity() for handling dorf death/failure - Add JobId collision detection in debug builds - Fix demo system to check Pending state for chopping dorfs - Throttle queue_debug to every 120 ticks - Add surface position deduplication for terrain chunks
This commit is contained in:
+1
-1
@@ -6,7 +6,7 @@ pub const SEED: u32 = 420;
|
||||
|
||||
pub const PATHFINDER_SHORT_PATH_MAX_TILES: i32 = 64;
|
||||
pub const PATHFINDER_MAX_NODES: usize = 15000;
|
||||
pub const PATHFINDER_PROVISIONAL_NODE_LIMIT: usize = 256;
|
||||
pub const PATHFINDER_PROVISIONAL_NODE_LIMIT: usize = 4096;
|
||||
// Tier 1: Same/adjacent chunk -> sync A* (fast, ~87µs)
|
||||
// Tier 2: 2-4 chunks away -> Provisional + full path via queue
|
||||
// Tier 3: >4 chunks away -> Hierarchical chunk-path + async segmented A*
|
||||
|
||||
@@ -366,9 +366,14 @@ pub fn prepare_paths(
|
||||
request_id: 0,
|
||||
});
|
||||
} else {
|
||||
info!("[PATH] Hierarchical provisional failed for {:?}: start={:?} goal={:?} chunks={}", entity, start, goal, chunk_distance);
|
||||
let path = calculate_path_benchmarked(&tilemap, start, goal);
|
||||
if path.len() <= 1 {
|
||||
// Path failed — clear target so entity picks a new reachable one
|
||||
info!(
|
||||
"[PATH] Short path failed for {:?}, clearing target (path_len={})",
|
||||
entity,
|
||||
path.len()
|
||||
);
|
||||
ambulatory.target = None;
|
||||
ambulatory.current_path = None;
|
||||
} else {
|
||||
@@ -619,7 +624,7 @@ pub fn movement(
|
||||
query
|
||||
.par_iter_mut()
|
||||
.for_each(|(mut ambulatory, mut transform)| {
|
||||
info!(
|
||||
debug!(
|
||||
"[PATH] Moving: target={:?} current={:?}",
|
||||
ambulatory.target, transform.translation
|
||||
);
|
||||
@@ -1309,9 +1314,10 @@ pub fn calculate_provisional_path(
|
||||
}
|
||||
|
||||
if nodes_expanded >= node_limit {
|
||||
let goal_standable = is_standable_tile(tilemap, goal);
|
||||
info!(
|
||||
"[PATH] FAIL: node_limit hit limit={} start={:?} goal={:?} expanded={}",
|
||||
node_limit, start, goal, nodes_expanded
|
||||
"[PATH] FAIL: node_limit hit limit={} start={:?} goal={:?} goal_standable={} expanded={}",
|
||||
node_limit, start, goal, goal_standable, nodes_expanded
|
||||
);
|
||||
return (Vec::new(), nodes_expanded);
|
||||
}
|
||||
|
||||
@@ -16,7 +16,10 @@ pub fn demo_system(
|
||||
dorf_query: Query<(&TaskQueue, &TaskState), With<EntityType>>,
|
||||
) {
|
||||
let any_chopping = dorf_query.iter().any(|(queue, state)| {
|
||||
let is_active_or_completing = *state == TaskState::Active || *state == TaskState::Completed;
|
||||
let is_active_or_completing = matches!(
|
||||
*state,
|
||||
TaskState::Active | TaskState::Completed | TaskState::Pending
|
||||
);
|
||||
if is_active_or_completing {
|
||||
if let Some(current) = queue.current() {
|
||||
return matches!(current, Task::ChopTree { .. });
|
||||
@@ -31,10 +34,10 @@ pub fn demo_system(
|
||||
}
|
||||
}
|
||||
|
||||
if !job_queue.is_empty() {
|
||||
let (fell, haul) = job_queue.debug_counts();
|
||||
info!("[QUEUE] FellTree: {}, HaulCargo: {}", fell, haul);
|
||||
}
|
||||
// if !job_queue.is_empty() {
|
||||
// let (fell, haul) = job_queue.debug_counts();
|
||||
// info!("[QUEUE] FellTree: {}, HaulCargo: {}", fell, haul);
|
||||
// }
|
||||
}
|
||||
|
||||
fn find_tree_nearest_origin(
|
||||
|
||||
@@ -146,6 +146,10 @@ pub fn task_executor_system(
|
||||
} => match step {
|
||||
ChopStep::MovingToTree { ref mut approach } => {
|
||||
let job_id_val = *job_id;
|
||||
info!(
|
||||
"[EXECUTOR] ChopTree {:?}: trunk_pos={:?} approach={:?} target={:?}",
|
||||
entity, trunk_pos, approach, ambulatory.target
|
||||
);
|
||||
if !tilemap_mut.fixture_tiles.contains_key(trunk_pos) {
|
||||
info!(
|
||||
"TASK FAILED: {:?} for {:?} - {}",
|
||||
@@ -168,6 +172,10 @@ pub fn task_executor_system(
|
||||
// Neighbours at the same z as the trunk base — this IS the floor level.
|
||||
let trunk_z = trunk_pos.z;
|
||||
|
||||
info!(
|
||||
"[EXECUTOR] Searching for approach tile around trunk at z={}",
|
||||
trunk_z
|
||||
);
|
||||
// Search expanding outward from the trunk for a standable tile
|
||||
let approach_target = (1..=4).find_map(|radius: i32| {
|
||||
for dx in -radius..=radius {
|
||||
@@ -196,6 +204,7 @@ pub fn task_executor_system(
|
||||
|
||||
match approach_target {
|
||||
Some(target) => {
|
||||
info!("[EXECUTOR] ChopTree found approach tile: {:?}, setting target", target);
|
||||
ambulatory.target = Some(target);
|
||||
ambulatory.current_path = None;
|
||||
}
|
||||
|
||||
@@ -6,12 +6,14 @@ use crate::entities::tasks::job_queue::{JobId, JobKind, JobQueue};
|
||||
use crate::entities::tasks::jobs::{FellTreeJob, HaulCargoJob, IdleJob};
|
||||
use crate::world::tiles::TileMap;
|
||||
use bevy::prelude::*;
|
||||
use std::collections::HashSet;
|
||||
|
||||
pub fn job_assignment_system(
|
||||
mut job_queue: ResMut<JobQueue>,
|
||||
tilemap: Res<TileMap>,
|
||||
mut dorf_query: Query<
|
||||
(
|
||||
Entity,
|
||||
&mut TaskQueue,
|
||||
&mut TaskState,
|
||||
&Transform,
|
||||
@@ -21,59 +23,117 @@ pub fn job_assignment_system(
|
||||
With<EntityType>,
|
||||
>,
|
||||
) {
|
||||
for (mut queue, mut state, transform, haul_slot, mut ambulatory) in dorf_query.iter_mut() {
|
||||
let is_idle = queue.is_empty() || matches!(queue.current(), Some(Task::Idle { .. }));
|
||||
|
||||
if !is_idle {
|
||||
continue;
|
||||
}
|
||||
|
||||
if haul_slot.is_occupied() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let dorf_pos_ivec = transform.translation.as_ivec3();
|
||||
let dorf_pos_2d = Vec2::new(transform.translation.x, transform.translation.y);
|
||||
|
||||
if let Some((job_id, job)) =
|
||||
job_queue.pop_best_pathfinding(&tilemap, dorf_pos_ivec, dorf_pos_2d)
|
||||
{
|
||||
info!(
|
||||
"[ASSIGN] Job assigned to dorf at {:?}: {:?}",
|
||||
dorf_pos_ivec.xy(),
|
||||
job
|
||||
);
|
||||
|
||||
// Stop the dorf in its tracks so the new Task can take over movement.
|
||||
ambulatory.current_path = None;
|
||||
ambulatory.target = None;
|
||||
ambulatory.path_index = 0;
|
||||
|
||||
match job {
|
||||
JobKind::FellTree { trunk_pos } => {
|
||||
info!("[ASSIGN] Dorf assigned to FellTree at {:?}", trunk_pos);
|
||||
queue.clear();
|
||||
queue.push(FellTreeJob::start(job_id, trunk_pos));
|
||||
*state = TaskState::Pending;
|
||||
}
|
||||
JobKind::HaulCargo {
|
||||
cargo_entity,
|
||||
cargo_pos,
|
||||
dest,
|
||||
} => {
|
||||
info!("[ASSIGN] Dorf assigned to HaulCargo at {:?}", cargo_pos);
|
||||
queue.clear();
|
||||
queue.push(HaulCargoJob::start(job_id, cargo_entity, cargo_pos, dest));
|
||||
*state = TaskState::Pending;
|
||||
}
|
||||
let idle_dorfs: Vec<(Entity, IVec3)> = dorf_query
|
||||
.iter_mut()
|
||||
.filter(|(entity, queue, _state, transform, haul_slot, _)| {
|
||||
let pos = transform.translation.as_ivec3();
|
||||
let pos_standable = tilemap.is_standable(pos);
|
||||
let current_task = queue.current().map(|t| t.name()).unwrap_or("EMPTY");
|
||||
let is_idle = queue.is_empty() || matches!(queue.current(), Some(Task::Idle { .. }));
|
||||
let no_cargo = haul_slot.is_empty();
|
||||
let should_assign = is_idle && no_cargo;
|
||||
if !should_assign {
|
||||
info!("[ASSIGN] Filter out dorf {:?}: pos_standable={} current={} is_idle={} no_cargo={}",
|
||||
entity, pos_standable, current_task, is_idle, no_cargo);
|
||||
}
|
||||
} else if queue.is_empty() {
|
||||
let behaviour = EntityBehaviourRegistry::global_get("dorf");
|
||||
let origin = transform.translation.as_ivec3();
|
||||
if queue.is_empty() {
|
||||
queue.push(IdleJob::start(JobId::default(), origin, &behaviour.idle));
|
||||
*state = TaskState::Pending;
|
||||
|
||||
if !pos_standable {
|
||||
return false;
|
||||
}
|
||||
is_idle && no_cargo
|
||||
})
|
||||
.map(|(entity, _, _, transform, _, _)| (entity, transform.translation.as_ivec3()))
|
||||
.collect();
|
||||
|
||||
if idle_dorfs.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut sorted_jobs: Vec<(usize, JobKind)> = job_queue
|
||||
.iter_unclaimed()
|
||||
.map(|(idx, kind)| (idx, kind.clone()))
|
||||
.collect();
|
||||
sorted_jobs.sort_by(|a, b| b.1.priority().cmp(&a.1.priority()));
|
||||
|
||||
info!(
|
||||
"[ASSIGN] idle_dorfs={} unclaimed_jobs={}",
|
||||
idle_dorfs.len(),
|
||||
sorted_jobs.len()
|
||||
);
|
||||
|
||||
if sorted_jobs.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut assigned_dorfs: HashSet<Entity> = HashSet::new();
|
||||
|
||||
for (job_idx, job_kind) in sorted_jobs {
|
||||
let target_pos = job_kind.target();
|
||||
|
||||
let mut remaining: Vec<_> = idle_dorfs
|
||||
.iter()
|
||||
.filter(|(e, _)| !assigned_dorfs.contains(e))
|
||||
.collect();
|
||||
remaining.sort_by_key(|(_, pos)| {
|
||||
(pos.x - target_pos.x).abs()
|
||||
+ (pos.y - target_pos.y).abs()
|
||||
+ (pos.z - target_pos.z).abs()
|
||||
});
|
||||
|
||||
for (dorf_entity, dorf_pos) in remaining {
|
||||
if let Some((job_id, _)) =
|
||||
job_queue.claim_job_at(job_idx, *dorf_entity, &tilemap, *dorf_pos)
|
||||
{
|
||||
if let Ok((_, mut queue, mut state, _, _, mut ambulatory)) =
|
||||
dorf_query.get_mut(*dorf_entity)
|
||||
{
|
||||
let task = match &job_kind {
|
||||
JobKind::FellTree { trunk_pos } => Task::ChopTree {
|
||||
job_id,
|
||||
trunk_pos: *trunk_pos,
|
||||
chop_ticks: 120,
|
||||
step: crate::entities::tasks::components::ChopStep::MovingToTree {
|
||||
approach: None,
|
||||
},
|
||||
},
|
||||
JobKind::HaulCargo {
|
||||
cargo_entity,
|
||||
cargo_pos,
|
||||
dest,
|
||||
} => Task::HaulCargo {
|
||||
job_id,
|
||||
cargo_entity: *cargo_entity,
|
||||
cargo_pos: *cargo_pos,
|
||||
dest: *dest,
|
||||
step: crate::entities::tasks::components::HaulStep::MovingToCargo {
|
||||
approach: None,
|
||||
},
|
||||
},
|
||||
};
|
||||
let task_name = task.name();
|
||||
queue.clear();
|
||||
queue.push(task);
|
||||
*state = TaskState::Pending;
|
||||
info!(
|
||||
"[ASSIGN] Assigned {:?} to dorf {:?}: {} -> Pending",
|
||||
*dorf_entity, dorf_pos, task_name
|
||||
);
|
||||
ambulatory.current_path = None;
|
||||
ambulatory.target = None;
|
||||
ambulatory.path_index = 0;
|
||||
assigned_dorfs.insert(*dorf_entity);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (dorf_entity, mut queue, mut state, transform, _, _) in dorf_query.iter_mut() {
|
||||
if queue.is_empty() && !matches!(*state, TaskState::Active) {
|
||||
let behaviour = EntityBehaviourRegistry::global_get("dorf");
|
||||
let origin = transform.translation.as_ivec3();
|
||||
queue.push(IdleJob::start(JobId::default(), origin, &behaviour.idle));
|
||||
*state = TaskState::Pending;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+121
-23
@@ -5,6 +5,18 @@ use smallvec::SmallVec;
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::VecDeque;
|
||||
|
||||
/// Job state tracking - replaces simple boolean claimed flag.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum JobState {
|
||||
/// No dorf has taken this job.
|
||||
Unclaimed,
|
||||
/// A dorf has claimed this job and is executing it.
|
||||
/// Stores the entity so we can unclaim if the dorf dies/fails.
|
||||
Claimed(Entity),
|
||||
/// All tasks for this job are done. Pending removal.
|
||||
Complete,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum JobKind {
|
||||
FellTree {
|
||||
@@ -45,7 +57,7 @@ impl JobKind {
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct JobId(u16);
|
||||
pub struct JobId(u32);
|
||||
|
||||
impl JobId {
|
||||
#[inline]
|
||||
@@ -57,7 +69,7 @@ impl JobId {
|
||||
|
||||
struct Entry {
|
||||
kind: JobKind,
|
||||
claimed: bool,
|
||||
state: JobState,
|
||||
id: JobId,
|
||||
}
|
||||
|
||||
@@ -70,10 +82,18 @@ pub struct JobQueue {
|
||||
impl JobQueue {
|
||||
#[inline]
|
||||
pub fn push(&mut self, kind: JobKind) {
|
||||
let new_id = self.next_id.next();
|
||||
#[cfg(debug_assertions)]
|
||||
if self.jobs.iter().any(|e| e.id == new_id) {
|
||||
panic!(
|
||||
"JobId collision detected: {:?} already exists in queue",
|
||||
new_id
|
||||
);
|
||||
}
|
||||
self.jobs.push_back(Entry {
|
||||
kind,
|
||||
claimed: false,
|
||||
id: self.next_id.next(),
|
||||
state: JobState::Unclaimed,
|
||||
id: new_id,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -86,31 +106,38 @@ impl JobQueue {
|
||||
|
||||
pub fn unclaim_job(&mut self, id: JobId) {
|
||||
if let Some(entry) = self.jobs.iter_mut().find(|j| j.id == id) {
|
||||
entry.claimed = false;
|
||||
entry.state = JobState::Unclaimed;
|
||||
info!("[QUEUE] Job ID {:?} marked unclaimed for reassignment", id);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unclaim_jobs_for_entity(&mut self, entity: Entity) {
|
||||
for entry in self.jobs.iter_mut() {
|
||||
if let JobState::Claimed(e) = entry.state {
|
||||
if e == entity {
|
||||
entry.state = JobState::Unclaimed;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pop_best_pathfinding(
|
||||
&mut self,
|
||||
tilemap: &crate::world::tiles::TileMap,
|
||||
dorf_entity: Entity,
|
||||
dorf_pos: IVec3,
|
||||
_dorf_pos_2d: Vec2,
|
||||
) -> Option<(JobId, JobKind)> {
|
||||
if self.jobs.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// 1. Gather all unclaimed jobs and score them for sorting.
|
||||
// We store: (Queue Index, Priority, Rough Distance)
|
||||
let mut candidates: Vec<(usize, u8, i32)> = self
|
||||
.jobs
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, entry)| !entry.claimed)
|
||||
.filter(|(_, entry)| entry.state == JobState::Unclaimed)
|
||||
.map(|(i, entry)| {
|
||||
let target = entry.kind.target();
|
||||
// Manhattan distance is a cheap heuristic for sorting
|
||||
let dist = (target.x - dorf_pos.x).abs()
|
||||
+ (target.y - dorf_pos.y).abs()
|
||||
+ (target.z - dorf_pos.z).abs();
|
||||
@@ -118,15 +145,10 @@ impl JobQueue {
|
||||
})
|
||||
.collect();
|
||||
|
||||
// 2. Sort: Highest Priority first. If tied, Shortest Distance first.
|
||||
candidates.sort_by(|a, b| {
|
||||
b.1.cmp(&a.1) // Descending priority
|
||||
.then(a.2.cmp(&b.2)) // Ascending distance
|
||||
});
|
||||
candidates.sort_by(|a, b| b.1.cmp(&a.1).then(a.2.cmp(&b.2)));
|
||||
|
||||
let max_path_dist = 1024;
|
||||
let max_path_dist = 4096;
|
||||
|
||||
// 3. Evaluate the sorted jobs with actual pathfinding
|
||||
for (idx, _pri, _dist) in candidates {
|
||||
let entry = &self.jobs[idx];
|
||||
|
||||
@@ -137,11 +159,10 @@ impl JobQueue {
|
||||
let mut best_target = None;
|
||||
let mut shortest_path_len = usize::MAX;
|
||||
|
||||
// Pathfind to EVERY standable adjacent tile to find the absolute closest one
|
||||
for tile in standable_tiles {
|
||||
let path =
|
||||
calculate_provisional_path(tilemap, dorf_pos, tile, max_path_dist);
|
||||
if !path.is_empty() && path.len() < shortest_path_len {
|
||||
if path.len() > 1 && path.len() < shortest_path_len {
|
||||
shortest_path_len = path.len();
|
||||
best_target = Some(tile);
|
||||
}
|
||||
@@ -151,7 +172,7 @@ impl JobQueue {
|
||||
JobKind::HaulCargo { cargo_pos, .. } => {
|
||||
let path =
|
||||
calculate_provisional_path(tilemap, dorf_pos, *cargo_pos, max_path_dist);
|
||||
if !path.is_empty() {
|
||||
if path.len() > 1 {
|
||||
Some(*cargo_pos)
|
||||
} else {
|
||||
None
|
||||
@@ -159,15 +180,13 @@ impl JobQueue {
|
||||
}
|
||||
};
|
||||
|
||||
// 4. If we found a valid path to this job, claim it and return it
|
||||
if let Some(target_pos) = best_path_target {
|
||||
info!(
|
||||
"[QUEUE] Job Claimed: kind={:?} target={:?} dorf={:?}",
|
||||
entry.kind, target_pos, dorf_pos
|
||||
);
|
||||
|
||||
self.jobs[idx].claimed = true;
|
||||
// Note: JobKind needs `#[derive(Clone)]` if it doesn't have it already
|
||||
self.jobs[idx].state = JobState::Claimed(dorf_entity);
|
||||
return Some((self.jobs[idx].id, self.jobs[idx].kind.clone()));
|
||||
}
|
||||
}
|
||||
@@ -208,6 +227,85 @@ impl JobQueue {
|
||||
tiles
|
||||
}
|
||||
|
||||
pub fn iter_unclaimed(&self) -> impl Iterator<Item = (usize, &JobKind)> {
|
||||
self.jobs
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, entry)| entry.state == JobState::Unclaimed)
|
||||
.map(|(idx, entry)| (idx, &entry.kind))
|
||||
}
|
||||
|
||||
pub fn claim_job_at(
|
||||
&mut self,
|
||||
idx: usize,
|
||||
dorf_entity: Entity,
|
||||
tilemap: &crate::world::tiles::TileMap,
|
||||
dorf_pos: IVec3,
|
||||
) -> Option<(JobId, JobKind)> {
|
||||
const MAX_PATH_DIST: usize = 4096;
|
||||
|
||||
let entry = self.jobs.get(idx)?;
|
||||
if entry.state != JobState::Unclaimed {
|
||||
info!(
|
||||
"[CLAIM] idx={} state={:?} - not unclaimed",
|
||||
idx, entry.state
|
||||
);
|
||||
return None;
|
||||
}
|
||||
|
||||
let job_id = entry.id;
|
||||
let job_kind = entry.kind.clone();
|
||||
|
||||
info!(
|
||||
"[CLAIM] idx={} dorf={:?} dorf_pos={:?} kind={:?}",
|
||||
idx, dorf_entity, dorf_pos, job_kind
|
||||
);
|
||||
|
||||
let best_path_target = match &job_kind {
|
||||
JobKind::FellTree { trunk_pos } => {
|
||||
let mut standable_tiles = Self::find_all_standable_adjacent(trunk_pos, tilemap);
|
||||
standable_tiles.sort_by_key(|tile| {
|
||||
let dx = tile.x - dorf_pos.x;
|
||||
let dy = tile.y - dorf_pos.y;
|
||||
let dz = tile.z - dorf_pos.z;
|
||||
dx * dx + dy * dy + dz * dz
|
||||
});
|
||||
|
||||
standable_tiles.iter().find_map(|tile| {
|
||||
let path = calculate_provisional_path(tilemap, dorf_pos, *tile, MAX_PATH_DIST);
|
||||
if path.len() > 1 {
|
||||
Some(*tile)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
JobKind::HaulCargo { cargo_pos, .. } => {
|
||||
let path = calculate_provisional_path(tilemap, dorf_pos, *cargo_pos, MAX_PATH_DIST);
|
||||
if path.len() > 1 {
|
||||
Some(*cargo_pos)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(target_pos) = best_path_target {
|
||||
info!(
|
||||
"[QUEUE] Job Claimed: kind={:?} target={:?} dorf={:?}",
|
||||
job_kind, target_pos, dorf_pos
|
||||
);
|
||||
self.jobs[idx].state = JobState::Claimed(dorf_entity);
|
||||
Some((job_id, job_kind))
|
||||
} else {
|
||||
info!(
|
||||
"[QUEUE] Job NOT Claimed: kind={:?} dorf={:?} - no path found",
|
||||
job_kind, dorf_pos
|
||||
);
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.jobs.is_empty()
|
||||
|
||||
@@ -6,7 +6,13 @@ use bevy::prelude::*;
|
||||
pub fn queue_debug_system(
|
||||
job_queue: Res<JobQueue>,
|
||||
dorf_query: Query<(&TaskQueue, &TaskState, &Transform), With<EntityType>>,
|
||||
mut tick: Local<u32>,
|
||||
) {
|
||||
*tick = tick.wrapping_add(1);
|
||||
if *tick % 120 != 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
let (fell_count, haul_count) = job_queue.debug_counts();
|
||||
let total = job_queue.len();
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@ pub fn execute_idle(
|
||||
}
|
||||
|
||||
IdleState::Moving { target } => {
|
||||
info!(
|
||||
debug!(
|
||||
"[IDLE] Moving: target={:?} current={:?}",
|
||||
target, transform.translation
|
||||
);
|
||||
@@ -99,7 +99,7 @@ pub fn execute_idle(
|
||||
let arrived = dist_sq < arrive_threshold_sq;
|
||||
let no_nav = ambulatory.target.is_none() && ambulatory.current_path.is_none();
|
||||
|
||||
info!("[IDLE] Moving: arrived={} no_nav={}", arrived, no_nav);
|
||||
debug!("[IDLE] Moving: arrived={} no_nav={}", arrived, no_nav);
|
||||
if arrived || no_nav {
|
||||
ambulatory.target = None;
|
||||
ambulatory.current_path = None;
|
||||
@@ -225,7 +225,7 @@ fn pick_gaussian_target(
|
||||
}
|
||||
|
||||
if let Some(pos) = chosen {
|
||||
info!(
|
||||
debug!(
|
||||
"[IDLE] Target picked at {:?} (Weight Sum: {:.4})",
|
||||
pos, weight_sum
|
||||
);
|
||||
|
||||
@@ -329,8 +329,11 @@ pub fn apply_terrain_blobs(
|
||||
occlusion_event_writer.write(TileOcclusionEvent { tile_position: pos });
|
||||
}
|
||||
|
||||
for surface in blob.surface_positions.iter() {
|
||||
tilemap.surface_positions.push(surface.0.as_ivec3());
|
||||
if !tilemap.applied_surface_chunks.contains(&blob.chunk_pos) {
|
||||
tilemap.applied_surface_chunks.insert(blob.chunk_pos);
|
||||
for surface in blob.surface_positions.iter() {
|
||||
tilemap.surface_positions.push(surface.0.as_ivec3());
|
||||
}
|
||||
}
|
||||
|
||||
forrestry_event_writer.write(ChunkForrestryEvent {
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
//! - item_tiles: Entity references per tile position
|
||||
|
||||
use bevy::prelude::*;
|
||||
use rustc_hash::FxHashMap;
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
|
||||
use super::chunk_data::ChunkData;
|
||||
use crate::constants::ITILE_SIZE;
|
||||
@@ -214,6 +214,9 @@ pub struct TileMap {
|
||||
/// Surface tile positions for idle pathfinding and valid spawn targets.
|
||||
/// Populated from TerrainBlob during terrain processing.
|
||||
pub surface_positions: Vec<IVec3>,
|
||||
/// Chunks whose surface positions have been committed to surface_positions.
|
||||
/// Prevents duplicates when chunks are regenerated during world expansion.
|
||||
pub applied_surface_chunks: FxHashSet<IVec2>,
|
||||
}
|
||||
|
||||
impl TileMap {
|
||||
|
||||
Reference in New Issue
Block a user