diff --git a/core/src/ChunkPipeline.cpp b/core/src/ChunkPipeline.cpp new file mode 100644 index 0000000..14d09a1 --- /dev/null +++ b/core/src/ChunkPipeline.cpp @@ -0,0 +1,163 @@ +#include "vf/ChunkPipeline.h" + +#include +#include +#include +#include + +namespace vf { + +namespace { + +bool coordLess(const Vec3i& a, const Vec3i& b) { + if (a.x != b.x) return a.x < b.x; + if (a.y != b.y) return a.y < b.y; + return a.z < b.z; +} + +std::uint64_t nanosSince(const std::chrono::steady_clock::time_point& start) { + auto elapsed = std::chrono::steady_clock::now() - start; + return static_cast( + std::chrono::duration_cast(elapsed).count()); +} + +std::string coordLabel(const Vec3i& coord) { + return "chunk(" + std::to_string(coord.x) + "," + std::to_string(coord.y) + "," + + std::to_string(coord.z) + ")"; +} + +} // namespace + +const char* chunkStageName(ChunkStage stage) { + switch (stage) { + case ChunkStage::Generate: return "generate"; + case ChunkStage::Light: return "light"; + case ChunkStage::Mesh: return "mesh"; + case ChunkStage::Ready: return "ready"; + } + return "unknown"; +} + +double PipelineStats::averageStageMs(ChunkStage stage) const { + std::size_t index = static_cast(stage); + if (stageCompleted[index] == 0) return 0.0; + double total = static_cast(stageNanos[index]); + return total / static_cast(stageCompleted[index]) / 1000000.0; +} + +ChunkPipeline::ChunkPipeline(const PipelineConfig& config) + : config(config), + generator(config.gen), + chunkStore(config.cacheBudgetBytes, config.cachePolicy), + jobs(config.threadCount) {} + +void ChunkPipeline::setObserver(const Vec3i& chunkCoord) { + { + std::lock_guard lock(controlMutex); + observerChunk = chunkCoord; + } + chunkStore.setObserver(chunkCoord); +} + +Vec3i ChunkPipeline::observer() const { + std::lock_guard lock(controlMutex); + return observerChunk; +} + +long long ChunkPipeline::distanceSqLocked(const Vec3i& chunkCoord) const { + long long dx = chunkCoord.x - observerChunk.x; + long long dy = chunkCoord.y - observerChunk.y; + long long dz = chunkCoord.z - observerChunk.z; + return dx * dx + dy * dy + dz * dz; +} + +bool ChunkPipeline::inViewVolumeLocked(const Vec3i& chunkCoord) const { + if (chunkCoord.y < 0 || chunkCoord.y >= config.verticalChunks) return false; + long long dx = chunkCoord.x - observerChunk.x; + long long dz = chunkCoord.z - observerChunk.z; + long long radius = config.viewRadius; + return dx * dx + dz * dz <= radius * radius; +} + +bool ChunkPipeline::inViewVolume(const Vec3i& chunkCoord) const { + std::lock_guard lock(controlMutex); + return inViewVolumeLocked(chunkCoord); +} + +JobPriority ChunkPipeline::priorityFor(long long distanceSq) const { + long long radius = config.viewRadius; + long long radiusSq = radius * radius; + if (distanceSq * 9 <= radiusSq) return JobPriority::High; + if (distanceSq * 9 <= radiusSq * 4) return JobPriority::Normal; + return JobPriority::Low; +} + +void ChunkPipeline::enqueue(const Vec3i& chunkCoord) { + std::lock_guard lock(controlMutex); + if (!pendingSet.insert(chunkCoord).second) return; + pending.push_back(chunkCoord); +} + +std::size_t ChunkPipeline::enqueueViewVolume() { + std::vector volume; + { + std::lock_guard lock(controlMutex); + int radius = config.viewRadius; + for (int dx = -radius; dx <= radius; ++dx) { + for (int dz = -radius; dz <= radius; ++dz) { + for (int y = 0; y < config.verticalChunks; ++y) { + Vec3i coord{observerChunk.x + dx, y, observerChunk.z + dz}; + if (inViewVolumeLocked(coord)) volume.push_back(coord); + } + } + } + } + std::sort(volume.begin(), volume.end(), coordLess); + std::size_t added = 0; + for (const Vec3i& coord : volume) { + std::lock_guard lock(controlMutex); + if (!pendingSet.insert(coord).second) continue; + pending.push_back(coord); + ++added; + } + return added; +} + +std::size_t ChunkPipeline::pendingCount() const { + std::lock_guard lock(controlMutex); + return pending.size(); +} + +void ChunkPipeline::cancel(const Vec3i& chunkCoord) { + std::lock_guard lock(controlMutex); + cancelledCoords.insert(chunkCoord); + auto active = activeIndex.find(chunkCoord); + if (active != activeIndex.end()) + cancelFlags[active->second]->store(true, std::memory_order_release); +} + +bool ChunkPipeline::isCancelled(const Vec3i& chunkCoord) const { + std::lock_guard lock(controlMutex); + return cancelledCoords.count(chunkCoord) != 0; +} + +std::vector ChunkPipeline::pendingTasks() const { + std::lock_guard lock(controlMutex); + std::vector tasks; + tasks.reserve(pending.size()); + for (const Vec3i& coord : pending) { + ChunkTask task; + task.coord = coord; + task.stage = ChunkStage::Generate; + task.distanceSq = distanceSqLocked(coord); + task.priority = priorityFor(task.distanceSq); + task.inRange = inViewVolumeLocked(coord); + tasks.push_back(task); + } + std::sort(tasks.begin(), tasks.end(), [](const ChunkTask& a, const ChunkTask& b) { + if (a.distanceSq != b.distanceSq) return a.distanceSq < b.distanceSq; + return coordLess(a.coord, b.coord); + }); + return tasks; +} +