Files
voxelforge/tests/testChunkPipeline.cpp
T

155 lines
5.4 KiB
C++

#include "testHarness.h"
#include "vf/ChunkPipeline.h"
#include <algorithm>
#include <cstring>
#include <vector>
using namespace vf;
namespace {
PipelineConfig baseConfig(unsigned threadCount) {
PipelineConfig config;
config.threadCount = threadCount;
config.viewRadius = 2;
config.verticalChunks = 2;
config.gen.seed = 20250904u;
return config;
}
void primePipeline(ChunkPipeline& pipeline) {
pipeline.setObserver({0, 0, 0});
pipeline.enqueueViewVolume();
pipeline.run();
}
std::vector<std::uint8_t> digestFor(unsigned threadCount) {
ChunkPipeline pipeline(baseConfig(threadCount));
primePipeline(pipeline);
return pipeline.digest();
}
bool sameBytes(const std::vector<std::uint8_t>& a, const std::vector<std::uint8_t>& b) {
if (a.size() != b.size()) return false;
return std::memcmp(a.data(), b.data(), a.size()) == 0;
}
bool sameMesh(const Mesh& a, const Mesh& b) {
if (a.vertices.size() != b.vertices.size()) return false;
if (a.indices.size() != b.indices.size()) return false;
if (!a.vertices.empty() &&
std::memcmp(a.vertices.data(), b.vertices.data(), a.vertices.size() * sizeof(Vertex)) != 0)
return false;
if (!a.indices.empty() &&
std::memcmp(a.indices.data(), b.indices.data(),
a.indices.size() * sizeof(std::uint32_t)) != 0)
return false;
return true;
}
std::size_t viewVolumeChunkCount(const PipelineConfig& config) {
std::size_t count = 0;
for (int dx = -config.viewRadius; dx <= config.viewRadius; ++dx)
for (int dz = -config.viewRadius; dz <= config.viewRadius; ++dz)
if (dx * dx + dz * dz <= config.viewRadius * config.viewRadius)
count += static_cast<std::size_t>(config.verticalChunks);
return count;
}
} // namespace
VF_TEST(pipelineTakesEveryQueuedChunkThroughAllFourStages) {
ChunkPipeline pipeline(baseConfig(4));
std::size_t queued = 0;
pipeline.setObserver({0, 0, 0});
queued = pipeline.enqueueViewVolume();
CHECK_EQ(queued, viewVolumeChunkCount(pipeline.settings()));
CHECK_EQ(pipeline.pendingCount(), queued);
pipeline.run();
CHECK_EQ(pipeline.pendingCount(), static_cast<std::size_t>(0));
CHECK_EQ(pipeline.results().size(), queued);
for (const ChunkTaskResult& produced : pipeline.results()) {
CHECK(produced.stage == ChunkStage::Ready);
CHECK(!produced.cancelled);
CHECK(produced.entryBytes > 0);
CHECK_EQ(produced.light.sun.size(),
static_cast<std::size_t>((chunkSize + 2) * (chunkSize + 2) * (chunkSize + 2)));
}
CHECK_EQ(pipeline.worldView().chunkCount(), queued);
}
VF_TEST(pipelineResultsComeBackInChunkCoordinateOrder) {
ChunkPipeline pipeline(baseConfig(8));
primePipeline(pipeline);
const std::vector<ChunkTaskResult>& produced = pipeline.results();
CHECK(produced.size() > 1);
for (std::size_t i = 1; i < produced.size(); ++i) {
const Vec3i& previous = produced[i - 1].coord;
const Vec3i& current = produced[i].coord;
bool ascending = previous.x < current.x ||
(previous.x == current.x && previous.y < current.y) ||
(previous.x == current.x && previous.y == current.y &&
previous.z < current.z);
CHECK(ascending);
}
}
VF_TEST(oneWorkerAndEightWorkersProduceByteIdenticalOutput) {
std::vector<std::uint8_t> serial = digestFor(0);
CHECK(serial.size() > 100000);
CHECK(sameBytes(serial, digestFor(1)));
CHECK(sameBytes(serial, digestFor(4)));
CHECK(sameBytes(serial, digestFor(8)));
}
VF_TEST(repeatedEightWorkerRunsAgreeWithThemselves) {
std::vector<std::uint8_t> first = digestFor(8);
std::vector<std::uint8_t> second = digestFor(8);
CHECK(sameBytes(first, second));
}
VF_TEST(cancelledChunkNeverReachesResultsOrCache) {
ChunkPipeline pipeline(baseConfig(4));
pipeline.setObserver({0, 0, 0});
std::size_t queued = pipeline.enqueueViewVolume();
const Vec3i dropped{1, 0, 0};
pipeline.cancel(dropped);
CHECK(pipeline.isCancelled(dropped));
pipeline.run();
CHECK_EQ(pipeline.results().size(), queued - 1);
CHECK(pipeline.resultFor(dropped) == nullptr);
CHECK(!pipeline.cache().contains(dropped));
CHECK(pipeline.worldView().chunkAt(dropped) == nullptr);
CHECK_EQ(pipeline.stats().cancelled, static_cast<std::uint64_t>(1));
CHECK(pipeline.resultFor({0, 0, 0}) != nullptr);
}
VF_TEST(chunkOutsideTheViewRadiusIsCancelledBeforeAnyStageRuns) {
ChunkPipeline pipeline(baseConfig(4));
pipeline.setObserver({0, 0, 0});
std::size_t queued = pipeline.enqueueViewVolume();
const Vec3i faraway{9, 0, 9};
pipeline.enqueue(faraway);
CHECK(!pipeline.inViewVolume(faraway));
CHECK_EQ(pipeline.pendingCount(), queued + 1);
pipeline.run();
CHECK_EQ(pipeline.results().size(), queued);
CHECK(pipeline.resultFor(faraway) == nullptr);
CHECK(pipeline.worldView().chunkAt(faraway) == nullptr);
CHECK(!pipeline.cache().contains(faraway));
CHECK_EQ(pipeline.stats().cancelled, static_cast<std::uint64_t>(1));
PipelineStats counted = pipeline.stats();
CHECK_EQ(counted.stageCompleted[static_cast<std::size_t>(ChunkStage::Generate)],
static_cast<std::uint64_t>(queued));
}
VF_TEST(readyChunksLandInTheCachePinnedInsideTheRadius) {
ChunkPipeline pipeline(baseConfig(4));
primePipeline(pipeline);