Code: include/managers/BackgroundSimulationManager.hpp, src/managers/BackgroundSimulationManager.cpp
Singleton Access: Use BackgroundSimulationManager::Instance() to access the manager.
BackgroundSimulationManager processes entities that are outside the active camera area but still need basic simulation to maintain world consistency. It handles Background tier entities with simplified updates at a reduced rate (10Hz by default).
| Tier | Manager | Update Rate | Features |
|---|---|---|---|
| Active | AIManager, CollisionManager | 60 Hz | Full AI, collision, rendering |
| Background | BackgroundSimulationManager | 10 Hz | Position-only updates, no collision |
| Hibernated | None | 0 Hz | Data stored only, no processing |
Entities in the Active tier receive full simulation:
- AI Processing: Full behavior execution via AIManager
- Collision Detection: Participates in CollisionManager narrowphase
- Rendering: Visible on screen
- Update Rate: Every frame (60 Hz)
When to use: Entities near the player/camera that need full gameplay interaction.
Entities in the Background tier receive reduced simulation:
- Position Updates: Simplified movement (continue current velocity)
- No Collision: Skipped to reduce CPU cost
- No Rendering: Off-screen
- Update Rate: 10 Hz (configurable)
- State Preservation: Behavior state maintained in EDM
When to use: Entities outside the visible area but still in the "active world" zone. Maintains world consistency (NPCs continue patrolling, animals continue grazing) without full simulation cost.
What Background Entities Do:
// Simplified Background tier update
void BackgroundSimulationManager::processBackgroundEntity(size_t edmIndex, float dt) {
auto& edm = EntityDataManager::Instance();
auto& transform = edm.getTransformByIndex(edmIndex);
// Simple position integration (no AI, no collision)
transform.position += transform.velocity * dt;
// Optional: Apply simple world bounds clamping
}Entities in the Hibernated tier receive no updates:
- No Processing: Completely dormant
- Data Stored: All EDM data preserved (position, state, inventory)
- No CPU Cost: Zero frame-to-frame overhead
- Reactivation: Instantly resume when transitioning to Background/Active
When to use: Entities far from the player that don't need any simulation. Their state is preserved for when the player returns.
Entities automatically transition between tiers based on distance from the reference point (usually the player):
Distance from player:
├─ 0 to activeRadius → Active (full simulation)
├─ activeRadius to backgroundRadius → Background (10Hz position)
└─ > backgroundRadius → Hibernated (no updates)
Transition Events:
- Active → Background: Entity leaves visible area, continues with simplified updates
- Background → Hibernated: Entity far enough to stop all processing
- Hibernated → Background: Entity approaching active zone, begin position updates
- Background → Active: Entity visible, resume full AI/collision
auto& bsm = BackgroundSimulationManager::Instance();
// Configure tier radii based on screen size
bsm.configureForScreenSize(1920, 1080);
// Results in:
// activeRadius ≈ 1650 (1.5x half-diagonal)
// backgroundRadius ≈ 2200 (2x half-diagonal)
// Or set manually
bsm.setActiveRadius(1500.0f);
bsm.setBackgroundRadius(5000.0f);
// Adjust Background tier update rate
bsm.setUpdateRate(10.0f); // 10 Hz (default)
bsm.setUpdateRate(5.0f); // 5 Hz (power saving)
bsm.setUpdateRate(30.0f); // 30 Hz (more responsive)Entity tier is stored in EntityHotData:
struct EntityHotData {
// ...
SimulationTier tier; // Active, Background, or Hibernated
// ...
};
// Check entity tier
const auto& hot = edm.getHotDataByIndex(edmIndex);
if (hot.tier == SimulationTier::Active) {
// Full processing
} else if (hot.tier == SimulationTier::Background) {
// Simplified processing
} else {
// Hibernated - skip entirely
}- Power-Efficient: Zero CPU when paused or no background entities
- Accumulator-Based Timing: Fixed 10Hz updates regardless of frame rate
- Tier Management: Updates simulation tiers every 60 frames (~1 second)
- WorkerBudget Integration: Adaptive batch sizing for parallel processing
- Screen-Size Aware: Configurable radii based on display dimensions
GameEngine::update()
│
├─► AIManager::update() // Active tier (60 Hz)
├─► CollisionManager::update() // Active tier (60 Hz)
│
└─► BackgroundSimulationManager::update()
│
├─► Phase 1: Tier updates (every 60 frames)
│ └─► EntityDataManager::updateSimulationTiers()
│
└─► Phase 2: Background entity processing (10 Hz)
└─► Simplified position updates
static BackgroundSimulationManager& Instance();
bool init();
void clean();
void prepareForStateTransition();
[[nodiscard]] bool isInitialized() const noexcept;
[[nodiscard]] bool isShutdown() const noexcept;/**
* @brief Main update - handles tier recalc AND background entity processing
* @param referencePoint Player/camera position for tier distance calculation
* @param deltaTime Frame delta time (for accumulator)
*
* Power-efficient single entry point:
* - Phase 1: Tier updates every 60 frames (~1 sec at 60Hz)
* - Phase 2: Background entity processing at 10Hz (only if entities exist)
*/
void update(const Vector2D& referencePoint, float deltaTime);void setGlobalPause(bool paused);
[[nodiscard]] bool isGloballyPaused() const noexcept;void setReferencePoint(const Vector2D& position);
[[nodiscard]] Vector2D getReferencePoint() const;
void updateTiers();
void invalidateTiers(); // Force tier update on next frame
[[nodiscard]] bool hasWork() const noexcept;// Tier radii
void setActiveRadius(float radius);
void setBackgroundRadius(float radius);
[[nodiscard]] float getActiveRadius() const;
[[nodiscard]] float getBackgroundRadius() const;
// Screen-size aware configuration
void configureForScreenSize(int screenWidth, int screenHeight);
// Update rate
void setUpdateRate(float hz); // Default: 10 Hz
[[nodiscard]] float getUpdateRate() const;void waitForAsyncCompletion(); // Call before state transitionsstruct PerfStats {
double lastUpdateMs;
double avgUpdateMs;
size_t lastEntitiesProcessed;
size_t lastBatchCount;
size_t lastTierChanges;
uint64_t totalUpdates;
bool lastWasThreaded;
};
[[nodiscard]] const PerfStats& getPerfStats() const;
void resetPerfStats();// In GameEngine::update()
void GameEngine::update(float dt) {
// Process Active tier entities
AIManager::Instance().update(dt);
CollisionManager::Instance().update(dt);
// Process Background tier entities (power-efficient, 10Hz)
BackgroundSimulationManager::Instance().update(playerPosition, dt);
}// In GameEngine::init() after window creation
BackgroundSimulationManager::Instance().configureForScreenSize(1920, 1080);
// Results in:
// - Active radius: ~1650 pixels (1.5x half-diagonal)
// - Background radius: ~2200 pixels (2x half-diagonal)void GameState::exit() {
// Wait for any async background processing
BackgroundSimulationManager::Instance().waitForAsyncCompletion();
// Now safe to clean up
// ...
}// For a more responsive background simulation
BackgroundSimulationManager::Instance().setUpdateRate(30.0f); // 30 Hz
// For power-saving mode
BackgroundSimulationManager::Instance().setUpdateRate(5.0f); // 5 HzBackgroundSimulationManager follows the AIManager threading pattern:
- Direct processing on main thread
- No threading overhead
- Uses WorkerBudget for optimal batch sizing
- Submits batches to ThreadSystem
- Per-batch output buffers (zero contention)
| Constant | Value | Purpose |
|---|---|---|
MIN_ENTITIES_FOR_THREADING |
500 | Threshold for multi-threaded processing |
MIN_BATCH_SIZE |
64 | Minimum entities per batch |
TIER_UPDATE_INTERVAL |
120 | Frames between tier recalculation |
float m_activeRadius{1650.0f}; // ~1.5x window half-diagonal
float m_backgroundRadius{2200.0f}; // ~2x window half-diagonalfloat halfDiagonal = sqrt((width/2)^2 + (height/2)^2);
activeRadius = halfDiagonal * 1.5f; // Visible + buffer
backgroundRadius = halfDiagonal * 2.0f; // Pre-loading zoneDistance from reference point:
├─ 0 to activeRadius → Active tier (full processing)
├─ activeRadius to backgroundRadius → Background tier (10Hz)
└─ > backgroundRadius → Hibernated tier (no processing)
BackgroundSimulationManager is designed for minimal CPU usage:
- Zero CPU when paused: Immediate return from
update() - No work detection: Skips processing if no background entities
- Accumulator-based timing: Only processes at target rate (10Hz)
- Tier caching: Only recalculates tiers every 60 frames
| Scenario | CPU Usage |
|---|---|
| No background entities | ~0% |
| 100 background entities | < 0.1% |
| 1000 background entities | < 0.5% |
| 10K background entities | ~1-2% |
auto& bsm = BackgroundSimulationManager::Instance();
const auto& stats = bsm.getPerfStats();
std::cout << "Last update: " << stats.lastUpdateMs << "ms\n";
std::cout << "Avg update: " << stats.avgUpdateMs << "ms\n";
std::cout << "Entities: " << stats.lastEntitiesProcessed << "\n";
std::cout << "Tier changes: " << stats.lastTierChanges << "\n";- EntityDataManager - Central entity data authority
- AIManager - Active tier AI processing
- CollisionManager - Active tier collision
- ThreadSystem - WorkerBudget integration