diff --git a/tests/testStreaming.cpp b/tests/testStreaming.cpp new file mode 100644 index 0000000..101c7ae --- /dev/null +++ b/tests/testStreaming.cpp @@ -0,0 +1,70 @@ +#include "testHarness.h" +#include "vf/Streaming.h" + +#include + +using namespace vf; + +namespace { + +long long dist2(ColumnCoord a, ColumnCoord b) { + long long dx = a.x - b.x, dz = a.z - b.z; + return dx * dx + dz * dz; +} + +bool listContains(const std::vector& list, ColumnCoord column) { + return std::find(list.begin(), list.end(), column) != list.end(); +} + +} // namespace + +VF_TEST(streamRadiusIsDiscSortedNearestFirst) { + auto columns = columnsInRadius({0, 0}, 4); + CHECK(!columns.empty()); + CHECK(columns.front() == (ColumnCoord{0, 0})); + for (ColumnCoord c : columns) CHECK(dist2(c, {0, 0}) <= 16); + CHECK(!listContains(columns, {3, 3})); // 18 > 16, outside the disc + for (std::size_t i = 1; i < columns.size(); ++i) + CHECK(dist2(columns[i - 1], {0, 0}) <= dist2(columns[i], {0, 0})); +} + +VF_TEST(streamInitialUpdateLoadsWholeDisc) { + ChunkStreamer streamer({4, 6}); + StreamDelta delta = streamer.update({0, 0}); + CHECK_EQ(delta.load.size(), columnsInRadius({0, 0}, 4).size()); + CHECK(delta.unload.empty()); + CHECK_EQ(streamer.loadedCount(), delta.load.size()); + CHECK(delta.load.front() == (ColumnCoord{0, 0})); + for (std::size_t i = 1; i < delta.load.size(); ++i) + CHECK(dist2(delta.load[i - 1], {0, 0}) <= dist2(delta.load[i], {0, 0})); +} + +VF_TEST(streamRepeatedUpdateIsIdempotent) { + ChunkStreamer streamer({4, 6}); + streamer.update({0, 0}); + StreamDelta again = streamer.update({0, 0}); + CHECK(again.load.empty()); + CHECK(again.unload.empty()); +} + +VF_TEST(streamStepKeepsOverlapResident) { + ChunkStreamer streamer({4, 6}); + streamer.update({0, 0}); + StreamDelta delta = streamer.update({1, 0}); + CHECK(streamer.isLoaded({0, 0})); // still inside both rings + CHECK(!listContains(delta.load, ColumnCoord{0, 0})); // not reloaded + CHECK(!listContains(delta.unload, ColumnCoord{0, 0})); +} + +VF_TEST(streamHysteresisAvoidsThrash) { + ChunkStreamer streamer({4, 6}); + streamer.update({0, 0}); + streamer.update({5, 0}); + // (0,0) is distance 5: past the load radius (4) but within the unload + // radius (6), so it must stay resident rather than thrash out. + CHECK(streamer.isLoaded({0, 0})); + + StreamDelta far = streamer.update({20, 0}); + CHECK(!streamer.isLoaded({0, 0})); // now well past the unload ring + CHECK(listContains(far.unload, ColumnCoord{0, 0})); +}