feat(simulation): background worker pool infrastructure (#843 Part C)
Generic BackgroundWorkerPool<Req, Resp> with crossbeam channels, closure handlers, and 3 delivery strategies (Fallback, GracefulDegrade, ModalLock). Stub workers registered as Bevy resources: - ChunkGenWorker (2 threads) — terrain/props/navmesh generation - NpcPrepWorker (1 thread) — pre-compute NPC state for incoming areas - OffscreenTickWorker (1 thread) — advance NPCs outside active tier Tick loop integration: - PreInput: poll_worker_results drains completed work - PostSnapshot: push_worker_requests queues new work (no-op until Phase 5) Handlers are stubs — real computation plugs in when the phases that need them arrive. The infrastructure (channels, threads, push/poll, shutdown) is real and tested. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -70,6 +70,12 @@ impl Plugin for SimulationPlugin {
|
||||
.init_resource::<crate::npc::relationships::RelationshipGraph>()
|
||||
.init_resource::<crate::knowledge::KnowledgeEventQueue>();
|
||||
|
||||
// Background worker pools (#843 Part C) — off-tick computation on separate cores.
|
||||
// Stub handlers for now; real implementations plug in at Phase 5+.
|
||||
app.insert_resource(crate::workers::stubs::ChunkGenWorker::new(2));
|
||||
app.insert_resource(crate::workers::stubs::NpcPrepWorker::new(1));
|
||||
app.insert_resource(crate::workers::stubs::OffscreenTickWorker::new(1));
|
||||
|
||||
// Phase sub-plugins (#843) — each owns its domain
|
||||
app.add_plugins(time_plugin::TimePlugin);
|
||||
app.add_plugins(input_plugin::InputPlugin);
|
||||
@@ -77,6 +83,18 @@ impl Plugin for SimulationPlugin {
|
||||
app.add_plugins(social_plugin::SocialPlugin);
|
||||
app.add_plugins(economy_plugin::EconomyPlugin);
|
||||
|
||||
// Background worker tick integration (#843 Part C)
|
||||
app.add_systems(
|
||||
Update,
|
||||
crate::workers::systems::poll_worker_results
|
||||
.in_set(crate::tick_phases::TickPhase::PreInput),
|
||||
);
|
||||
app.add_systems(
|
||||
Update,
|
||||
crate::workers::systems::push_worker_requests
|
||||
.in_set(crate::tick_phases::TickPhase::PostSnapshot),
|
||||
);
|
||||
|
||||
// save_load is an exclusive system (takes &mut World).
|
||||
// Exclusive systems in Bevy 0.18 cannot use .in_set() — use .before()/.after()
|
||||
// to position it within the Snapshot phase window.
|
||||
|
||||
@@ -696,10 +696,15 @@ pub fn process_contradiction_monologue(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::simulation::rng::SimRng;
|
||||
use crate::simulation::rng::{EntityRng, SimRng};
|
||||
use crate::simulation::time::SimulationTime;
|
||||
use bevy_ecs::world::World;
|
||||
|
||||
/// Test helper: create an EntityRng for the player entity in tests.
|
||||
fn test_entity_rng() -> EntityRng {
|
||||
EntityRng::from_seed_and_id(42, 0)
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// SprintAnomalyQueue unit tests (#428, D-055)
|
||||
// -----------------------------------------------------------------------
|
||||
@@ -795,6 +800,7 @@ mod tests {
|
||||
|
||||
world.spawn((
|
||||
PlayerCharacter,
|
||||
test_entity_rng(),
|
||||
TilePosition::new(5, 5, 0),
|
||||
MonologueState::default(),
|
||||
MonologueBuffer::default(),
|
||||
@@ -823,6 +829,7 @@ mod tests {
|
||||
|
||||
world.spawn((
|
||||
PlayerCharacter,
|
||||
test_entity_rng(),
|
||||
TilePosition::new(5, 5, 0),
|
||||
MonologueState::default(),
|
||||
MonologueBuffer::default(),
|
||||
@@ -857,6 +864,7 @@ mod tests {
|
||||
|
||||
world.spawn((
|
||||
PlayerCharacter,
|
||||
test_entity_rng(),
|
||||
TilePosition::new(5, 5, 0),
|
||||
MonologueState::default(),
|
||||
buffer,
|
||||
@@ -887,6 +895,7 @@ mod tests {
|
||||
|
||||
world.spawn((
|
||||
PlayerCharacter,
|
||||
test_entity_rng(),
|
||||
TilePosition::new(5, 5, 0),
|
||||
MonologueState::default(),
|
||||
MonologueBuffer::default(),
|
||||
@@ -912,6 +921,7 @@ mod tests {
|
||||
|
||||
world.spawn((
|
||||
PlayerCharacter,
|
||||
test_entity_rng(),
|
||||
TilePosition::new(5, 5, 0),
|
||||
MonologueState::default(),
|
||||
MonologueBuffer::default(),
|
||||
@@ -935,6 +945,7 @@ mod tests {
|
||||
let mut world = setup_anomaly_world();
|
||||
world.spawn((
|
||||
PlayerCharacter,
|
||||
test_entity_rng(),
|
||||
TilePosition::new(5, 5, 0),
|
||||
MonologueState::default(),
|
||||
MonologueBuffer::default(),
|
||||
@@ -1000,6 +1011,7 @@ mod tests {
|
||||
|
||||
world.spawn((
|
||||
PlayerCharacter,
|
||||
test_entity_rng(),
|
||||
TilePosition::new(10, 10, 0),
|
||||
MonologueState::default(),
|
||||
MonologueBuffer::default(),
|
||||
@@ -1042,6 +1054,7 @@ mod tests {
|
||||
let player = world
|
||||
.spawn((
|
||||
PlayerCharacter,
|
||||
test_entity_rng(),
|
||||
TilePosition::new(10, 10, 0),
|
||||
MonologueState::default(),
|
||||
MonologueBuffer::default(),
|
||||
@@ -1106,6 +1119,7 @@ mod tests {
|
||||
|
||||
world.spawn((
|
||||
PlayerCharacter,
|
||||
test_entity_rng(),
|
||||
TilePosition::new(10, 10, 0),
|
||||
MonologueState::default(),
|
||||
buffer,
|
||||
@@ -1139,6 +1153,7 @@ mod tests {
|
||||
|
||||
world.spawn((
|
||||
PlayerCharacter,
|
||||
test_entity_rng(),
|
||||
TilePosition::new(10, 10, 0),
|
||||
MonologueState::default(),
|
||||
MonologueBuffer::default(),
|
||||
@@ -1186,6 +1201,7 @@ mod tests {
|
||||
let player = world
|
||||
.spawn((
|
||||
PlayerCharacter,
|
||||
test_entity_rng(),
|
||||
TilePosition::new(10, 10, 0),
|
||||
MonologueState::default(),
|
||||
MonologueBuffer::default(),
|
||||
@@ -1228,6 +1244,7 @@ mod tests {
|
||||
|
||||
world.spawn((
|
||||
PlayerCharacter,
|
||||
test_entity_rng(),
|
||||
TilePosition::new(10, 10, 0),
|
||||
MonologueState::default(),
|
||||
MonologueBuffer::default(),
|
||||
@@ -1268,6 +1285,7 @@ mod tests {
|
||||
|
||||
world.spawn((
|
||||
PlayerCharacter,
|
||||
test_entity_rng(),
|
||||
TilePosition::new(5, 5, 0),
|
||||
MonologueState::default(),
|
||||
MonologueBuffer::default(),
|
||||
@@ -1344,6 +1362,7 @@ mod tests {
|
||||
world
|
||||
.spawn((
|
||||
PlayerCharacter,
|
||||
test_entity_rng(),
|
||||
TilePosition::new(10, 10, 0),
|
||||
MonologueState::default(),
|
||||
MonologueBuffer::default(),
|
||||
@@ -1912,6 +1931,7 @@ mod tests {
|
||||
world
|
||||
.spawn((
|
||||
PlayerCharacter,
|
||||
test_entity_rng(),
|
||||
TilePosition::new(0, 0, 0),
|
||||
MonologueState::default(),
|
||||
MonologueBuffer::default(),
|
||||
@@ -2120,6 +2140,7 @@ mod tests {
|
||||
let player = world
|
||||
.spawn((
|
||||
PlayerCharacter,
|
||||
test_entity_rng(),
|
||||
TilePosition::new(0, 0, 0),
|
||||
MonologueState::default(),
|
||||
MonologueBuffer::default(),
|
||||
|
||||
Reference in New Issue
Block a user