160 lines
5.3 KiB
C++
160 lines
5.3 KiB
C++
#pragma once
|
|
|
|
#include <array>
|
|
#include <atomic>
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <memory>
|
|
#include <mutex>
|
|
#include <unordered_map>
|
|
#include <unordered_set>
|
|
#include <vector>
|
|
|
|
#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<std::uint64_t, chunkStageCount> stageCompleted{};
|
|
std::array<std::uint64_t, chunkStageCount> 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<ChunkTask> pendingTasks() const;
|
|
|
|
// Drains the pending queue through all four stages and folds the results in.
|
|
void run();
|
|
|
|
const std::vector<ChunkTaskResult>& 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<std::uint8_t> 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<bool>& cancelled);
|
|
void stageLight(ChunkTaskResult& slot, std::atomic<bool>& cancelled);
|
|
void stageMesh(ChunkTaskResult& slot, std::atomic<bool>& cancelled);
|
|
void stageReady(ChunkTaskResult& slot, const Chunk* source, std::atomic<bool>& cancelled);
|
|
|
|
PipelineConfig config;
|
|
WorldGen generator;
|
|
World world;
|
|
ChunkCache chunkStore;
|
|
JobSystem jobs;
|
|
|
|
mutable std::mutex controlMutex;
|
|
Vec3i observerChunk;
|
|
std::vector<Vec3i> pending;
|
|
std::unordered_set<Vec3i, Vec3iHash> pendingSet;
|
|
std::unordered_set<Vec3i, Vec3iHash> cancelledCoords;
|
|
std::unordered_map<Vec3i, std::size_t, Vec3iHash> activeIndex;
|
|
std::vector<std::unique_ptr<std::atomic<bool>>> cancelFlags;
|
|
|
|
std::vector<ChunkTaskResult> completedResults;
|
|
std::unordered_map<Vec3i, std::size_t, Vec3iHash> resultIndex;
|
|
PipelineStats counters;
|
|
};
|
|
|
|
} // namespace vf
|