#ifndef DC_WORKER_CPU_H
#define DC_WORKER_CPU_H
// DCWorkerCPU: Simple CPU-only terrain worker.
// Replaces DCWorker's GPU pipeline with pure CPU dual contouring.
// Single code path: density grid → octree → mesh → clip → smooth.
#include "dc_spsc_queue.h"
#include "dc_octree_clipmap.h"
#include "dc_contour.h"
#include "dc_density_grid.h"
#include "dc_density.h"
#include "dc_generator.h"
#include "dc_seam.h"
#include "dc_edit.h"
#include <atomic>
#include <thread>
#include <vector>
#include <unordered_map>
#include <list>
// Request: main thread -> worker
struct CellRequest {
OctreeClipmapNode *node = nullptr;
float cell_origin[3] = {};
float voxel_size = 0.25f;
int grid_dims = 33;
int voxels_per_cell = 32;
int overlap = 0;
float nominal_origin[3] = {};
float nominal_size = 0.0f;
Vector3i node_position;
int lod_level = 0;
const DCDensity *density = nullptr;
DCGenerator *generator = nullptr;
bool gpu_eligible = true; // ignored in CPU worker
bool use_adaptive_qef = true;
bool use_shortest_diagonal = true;
bool use_mesh_simplify = true;
std::shared_ptr<const std::vector<TerrainEdit>> edits; // box/sphere edits for sharp features
uint32_t generation = 0;
uint32_t node_generation = 0;
uint64_t pre_mesh_morton = 0;
bool is_pre_mesh = false;
// Benchmarking: timestamp set at _submit_to_worker (steady_clock.now() in microseconds).
// Worker stamps queue_wait_us and compute_us in the result, main thread computes total.
int64_t submit_time_us = 0;
};
// Result: worker -> main thread
struct CellResult {
OctreeClipmapNode *node = nullptr;
DCMeshData mesh_data;
std::vector<SeamNodeInfo> seam_nodes;
uint32_t active_voxel_count = 0;
uint32_t triangle_count = 0;
float geometric_error = 0.0f;
bool success = false;
uint32_t generation = 0;
uint32_t node_generation = 0;
uint64_t pre_mesh_morton = 0;
bool is_pre_mesh = false;
// Benchmarking deltas (microseconds), filled by worker thread.
int64_t submit_time_us = 0; // copied from request
int64_t queue_wait_us = 0; // time spent in request_queue before worker picked it up
int64_t compute_us = 0; // time spent in _process_cell on worker thread
int64_t result_enqueue_us = 0; // steady_clock at result_queue push (for poll-wait calc)
};
class DCGpuThread; // forward declaration
class DCWorkerCPU {
public:
DCWorkerCPU() = default;
~DCWorkerCPU();
void start(int worker_id);
void stop();
void set_gpu_thread(DCGpuThread* gpu) { gpu_thread_ = gpu; }
bool submit(const CellRequest &request);
// High-priority lane for user-initiated edits (1b). The worker drains this
// queue before request_queue_, so an edit skips ahead of any LOD/pre-mesh
// work already queued. Same single-producer (main thread) / single-consumer
// (worker) invariant as request_queue_.
bool submit_priority(const CellRequest &request);
size_t priority_queue_size() const { return priority_queue_.approx_size(); }
bool poll_result(CellResult &out);
bool is_running() const;
void set_generation(uint32_t gen) { current_generation_.store(gen, std::memory_order_release); }
bool is_busy() const { return busy_.load(std::memory_order_acquire); }
size_t request_queue_size() const { return request_queue_.approx_size(); }
private:
void _thread_func();
void _process_cell(const CellRequest &request, int64_t picked_up_us);
int worker_id_ = 0;
std::thread thread_;
std::atomic<bool> running_{ false };
std::atomic<bool> stop_requested_{ false };
std::atomic<bool> busy_{ false };
std::atomic<uint32_t> current_generation_{ 0 };
DCGpuThread* gpu_thread_ = nullptr; // optional GPU density fill
SPSCQueue<CellRequest, 64> request_queue_;
SPSCQueue<CellRequest, 64> priority_queue_; // edit lane, drained before request_queue_ (1b)
SPSCQueue<CellResult, 64> result_queue_;
// Per-worker LRU cache for base density grids (noise only, no edits).
// Avoids re-evaluating expensive noise when only edits changed.
// Key: packed (position_x, position_y, position_z, lod_level).
struct DensityCacheKey {
int px, py, pz, lod;
bool operator==(const DensityCacheKey& o) const {
return px == o.px && py == o.py && pz == o.pz && lod == o.lod;
}
};
struct DensityCacheKeyHash {
size_t operator()(const DensityCacheKey& k) const {
size_t h = std::hash<int>()(k.px);
h ^= std::hash<int>()(k.py) + 0x9e3779b9 + (h << 6) + (h >> 2);
h ^= std::hash<int>()(k.pz) + 0x9e3779b9 + (h << 6) + (h >> 2);
h ^= std::hash<int>()(k.lod) + 0x9e3779b9 + (h << 6) + (h >> 2);
return h;
}
};
static constexpr int DENSITY_CACHE_MAX = 64; // ~9 MB per worker (64 × 140 KB)
using CacheList = std::list<std::pair<DensityCacheKey, std::vector<float>>>;
CacheList cache_lru_;
std::unordered_map<DensityCacheKey, CacheList::iterator, DensityCacheKeyHash> cache_map_;
};
#endif // DC_WORKER_CPU_H