#pragma once #include #include #include #include #include #include #include #include #include #include "vf/ChunkCache.h" #include "vf/JobSystem.h" #include "vf/Lighting.h" #include "vf/LitMesh.h" #include "vf/Math.h" #include "vf/Mesh.h" #include "vf/World.h" #include "vf/WorldGen.h" namespace vf { enum class ChunkStage : std::uint8_t { Generate = 0, Light = 1, Mesh = 2, Ready = 3, }; constexpr int chunkStageCount = 4; const char* chunkStageName(ChunkStage stage); struct ChunkTask { Vec3i coord; ChunkStage stage = ChunkStage::Generate; JobPriority priority = JobPriority::Normal; long long distanceSq = 0; bool inRange = true; }; struct ChunkTaskResult { Vec3i coord; ChunkStage stage = ChunkStage::Generate; bool cancelled = false; Chunk chunk; LightField light; ChunkMesh mesh; std::uint64_t entryBytes = 0; std::uint64_t stageNanos[chunkStageCount] = {0, 0, 0, 0}; }; struct PipelineConfig { unsigned threadCount = 0; int viewRadius = 2; // chunk columns kept around the observer int verticalChunks = 2; // chunk layers stacked upwards from y = 0 std::uint64_t cacheBudgetBytes = 256ull * 1024ull * 1024ull; CachePolicy cachePolicy = CachePolicy::DistanceWeighted; WorldGenParams gen; }; struct PipelineStats { std::array stageCompleted{}; std::array stageNanos{}; std::uint64_t cancelled = 0; std::uint64_t chunksReady = 0; std::uint64_t cachedBytes = 0; double averageStageMs(ChunkStage stage) const; }; // Chunk production line running on a JobSystem. // // Every queued chunk becomes four graph nodes: Generate fills its blocks, Light // floods a box one voxel wider than the chunk on each side, Mesh bakes the lit // and ambient-occluded geometry, Ready snapshots the entry for the cache. Light // and Mesh both read across the chunk border, so their nodes depend on the // Generate nodes of every one of the 26 neighbours that is part of the same run. // // The output is order independent by construction: workers only ever write into // their own preallocated slot, and the slots are folded back in chunk // coordinate order once the graph has drained. The same configuration with one // worker and with eight therefore produces the same bytes, which digest() makes // directly comparable. class ChunkPipeline { public: explicit ChunkPipeline(const PipelineConfig& config = {}); ChunkPipeline(const ChunkPipeline&) = delete; ChunkPipeline& operator=(const ChunkPipeline&) = delete; const PipelineConfig& settings() const { return config; } JobSystem& jobSystem() { return jobs; } ChunkCache& cache() { return chunkStore; } const ChunkCache& cache() const { return chunkStore; } const World& worldView() const { return world; } const WorldGen& worldGenerator() const { return generator; } void setObserver(const Vec3i& chunkCoord); Vec3i observer() const; bool inViewVolume(const Vec3i& chunkCoord) const; void enqueue(const Vec3i& chunkCoord); std::size_t enqueueViewVolume(); std::size_t pendingCount() const; // Marks a chunk cancelled. Every stage tests the flag as it starts, so a // chunk that leaves the view radius stops at the next stage boundary and // never reaches the result list or the cache. void cancel(const Vec3i& chunkCoord); bool isCancelled(const Vec3i& chunkCoord) const; // Distance ordered, nearest first, each tagged with its service class. std::vector pendingTasks() const; // Drains the pending queue through all four stages and folds the results in. void run(); const std::vector& results() const { return completedResults; } const ChunkTaskResult* resultFor(const Vec3i& chunkCoord) const; PipelineStats stats() const; void resetStats(); // Deterministic byte image of the world, the merged results and the cache // footprint. Two runs that agree here agree on every produced byte. std::vector digest() const; private: long long distanceSqLocked(const Vec3i& chunkCoord) const; bool inViewVolumeLocked(const Vec3i& chunkCoord) const; JobPriority priorityFor(long long distanceSq) const; void stageGenerate(ChunkTaskResult& slot, Chunk* target, std::atomic& cancelled); void stageLight(ChunkTaskResult& slot, std::atomic& cancelled); void stageMesh(ChunkTaskResult& slot, std::atomic& cancelled); void stageReady(ChunkTaskResult& slot, const Chunk* source, std::atomic& cancelled); PipelineConfig config; WorldGen generator; World world; ChunkCache chunkStore; JobSystem jobs; mutable std::mutex controlMutex; Vec3i observerChunk; std::vector pending; std::unordered_set pendingSet; std::unordered_set cancelledCoords; std::unordered_map activeIndex; std::vector>> cancelFlags; std::vector completedResults; std::unordered_map resultIndex; PipelineStats counters; }; } // namespace vf