test: add tests for scheduled block-tick queue for delayed updates
This commit is contained in:
1 parent
571bec28bc
commit
a65a0c2ae2
1 file changed
+197
@@ -0,0 +1,197 @@
|
||||
#include "testHarness.h"
|
||||
#include "vf/BlockTick.h"
|
||||
|
||||
using namespace vf;
|
||||
|
||||
namespace {
|
||||
|
||||
int countBlock(World& world, Block target, int x0, int x1, int y0, int y1, int z0, int z1) {
|
||||
int count = 0;
|
||||
for (int x = x0; x <= x1; ++x)
|
||||
for (int y = y0; y <= y1; ++y)
|
||||
for (int z = z0; z <= z1; ++z)
|
||||
if (world.blockAt(x, y, z) == target) ++count;
|
||||
return count;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
VF_TEST(scheduledTickFiresAfterExactDelay) {
|
||||
World world;
|
||||
BlockTickScheduler scheduler(1, 0);
|
||||
scheduler.schedule({3, 3, 3}, 4);
|
||||
|
||||
for (int i = 0; i < 3; ++i) CHECK(scheduler.tick(world).empty());
|
||||
|
||||
std::vector<Vec3i> fired = scheduler.tick(world);
|
||||
CHECK_EQ(fired.size(), static_cast<std::size_t>(1));
|
||||
CHECK(fired[0] == Vec3i(3, 3, 3));
|
||||
}
|
||||
|
||||
VF_TEST(scheduledTicksFireInDueThenInsertionOrder) {
|
||||
World world;
|
||||
BlockTickScheduler scheduler(1, 0);
|
||||
scheduler.schedule({1, 1, 1}, 3);
|
||||
scheduler.schedule({2, 2, 2}, 1);
|
||||
scheduler.schedule({3, 3, 3}, 1);
|
||||
|
||||
std::vector<Vec3i> firstTick = scheduler.tick(world);
|
||||
CHECK_EQ(firstTick.size(), static_cast<std::size_t>(2));
|
||||
CHECK(firstTick[0] == Vec3i(2, 2, 2));
|
||||
CHECK(firstTick[1] == Vec3i(3, 3, 3));
|
||||
|
||||
CHECK(scheduler.tick(world).empty());
|
||||
|
||||
std::vector<Vec3i> thirdTick = scheduler.tick(world);
|
||||
CHECK_EQ(thirdTick.size(), static_cast<std::size_t>(1));
|
||||
CHECK(thirdTick[0] == Vec3i(1, 1, 1));
|
||||
}
|
||||
|
||||
VF_TEST(cancelRemovesAPendingTickBeforeItFires) {
|
||||
World world;
|
||||
BlockTickScheduler scheduler(1, 0);
|
||||
scheduler.schedule({5, 5, 5}, 2);
|
||||
CHECK(scheduler.isScheduled({5, 5, 5}));
|
||||
|
||||
scheduler.cancel({5, 5, 5});
|
||||
CHECK(!scheduler.isScheduled({5, 5, 5}));
|
||||
|
||||
scheduler.tick(world);
|
||||
std::vector<Vec3i> fired = scheduler.tick(world);
|
||||
CHECK(fired.empty());
|
||||
}
|
||||
|
||||
VF_TEST(sandFallsOneCellPerTickUntilItLands) {
|
||||
World world;
|
||||
world.setBlock(0, 0, 0, Block::Stone);
|
||||
world.setBlock(0, 5, 0, Block::Sand);
|
||||
BlockTickScheduler scheduler(1, 0);
|
||||
|
||||
scheduler.tick(world);
|
||||
CHECK(world.blockAt(0, 4, 0) == Block::Sand);
|
||||
CHECK(world.blockAt(0, 5, 0) == Block::Air);
|
||||
|
||||
scheduler.tick(world);
|
||||
CHECK(world.blockAt(0, 3, 0) == Block::Sand);
|
||||
|
||||
scheduler.tick(world);
|
||||
CHECK(world.blockAt(0, 2, 0) == Block::Sand);
|
||||
|
||||
scheduler.tick(world);
|
||||
CHECK(world.blockAt(0, 1, 0) == Block::Sand); // landed on the stone
|
||||
|
||||
scheduler.tick(world);
|
||||
CHECK(world.blockAt(0, 1, 0) == Block::Sand); // stays put once supported
|
||||
CHECK_EQ(countBlock(world, Block::Sand, -2, 2, 0, 6, -2, 2), 1);
|
||||
}
|
||||
|
||||
VF_TEST(gravelBehavesLikeSandAndSupportedBlocksDoNotFall) {
|
||||
World world;
|
||||
world.setBlock(1, 0, 1, Block::Stone);
|
||||
world.setBlock(1, 1, 1, Block::Gravel);
|
||||
BlockTickScheduler scheduler(1, 0);
|
||||
|
||||
scheduler.tick(world);
|
||||
scheduler.tick(world);
|
||||
CHECK(world.blockAt(1, 1, 1) == Block::Gravel); // already supported, never moved
|
||||
}
|
||||
|
||||
VF_TEST(stackedFallingBlocksMaintainRelativeOrderWithoutGaps) {
|
||||
World world;
|
||||
world.setBlock(2, 0, 2, Block::Stone);
|
||||
world.setBlock(2, 5, 2, Block::Sand);
|
||||
world.setBlock(2, 6, 2, Block::Gravel);
|
||||
BlockTickScheduler scheduler(1, 0);
|
||||
|
||||
for (int i = 0; i < 5; ++i) scheduler.tick(world);
|
||||
|
||||
CHECK(world.blockAt(2, 1, 2) == Block::Sand);
|
||||
CHECK(world.blockAt(2, 2, 2) == Block::Gravel);
|
||||
CHECK(world.blockAt(2, 3, 2) == Block::Air);
|
||||
}
|
||||
|
||||
VF_TEST(randomTicksSampleConfiguredCountPerLoadedChunk) {
|
||||
World world;
|
||||
world.setBlock(0, 0, 0, Block::Stone);
|
||||
world.setBlock(20, 0, 0, Block::Stone); // second chunk along x
|
||||
BlockTickScheduler scheduler(42, 5);
|
||||
|
||||
scheduler.tick(world);
|
||||
CHECK_EQ(scheduler.lastRandomTicks().size(), static_cast<std::size_t>(10));
|
||||
for (const Vec3i& pos : scheduler.lastRandomTicks()) {
|
||||
CHECK(pos.x >= -16 && pos.x < 32);
|
||||
CHECK(pos.y >= 0 && pos.y < 16);
|
||||
CHECK(pos.z >= 0 && pos.z < 16);
|
||||
}
|
||||
}
|
||||
|
||||
VF_TEST(randomTickSamplingIsDeterministicForAFixedSeed) {
|
||||
World worldA, worldB;
|
||||
worldA.setBlock(0, 0, 0, Block::Stone);
|
||||
worldB.setBlock(0, 0, 0, Block::Stone);
|
||||
BlockTickScheduler a(99, 4);
|
||||
BlockTickScheduler b(99, 4);
|
||||
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
a.tick(worldA);
|
||||
b.tick(worldB);
|
||||
CHECK(a.lastRandomTicks() == b.lastRandomTicks());
|
||||
}
|
||||
}
|
||||
|
||||
VF_TEST(differentSeedsProduceDifferentRandomTickSequences) {
|
||||
World worldA, worldB;
|
||||
worldA.setBlock(0, 0, 0, Block::Stone);
|
||||
worldB.setBlock(0, 0, 0, Block::Stone);
|
||||
BlockTickScheduler a(1, 6);
|
||||
BlockTickScheduler b(2, 6);
|
||||
|
||||
a.tick(worldA);
|
||||
b.tick(worldB);
|
||||
CHECK(a.lastRandomTicks() != b.lastRandomTicks());
|
||||
}
|
||||
|
||||
VF_TEST(growthHookFiresOnRandomTicksAndGrowsExposedDirtToGrass) {
|
||||
World world;
|
||||
world.setBlock(3, 3, 3, Block::Dirt);
|
||||
world.setBlock(3, 3, 4, Block::Dirt);
|
||||
world.setBlock(3, 4, 4, Block::Stone); // covered dirt must not grow
|
||||
|
||||
BlockTickScheduler scheduler(7, 200);
|
||||
scheduler.setRandomTickHook([](World& w, const Vec3i& pos, Block b) {
|
||||
if (b == Block::Dirt) tryGrowGrass(w, pos);
|
||||
});
|
||||
|
||||
bool grewExposed = false;
|
||||
for (int i = 0; i < 100 && !grewExposed; ++i) {
|
||||
scheduler.tick(world);
|
||||
if (world.blockAt(3, 3, 3) == Block::Grass) grewExposed = true;
|
||||
}
|
||||
CHECK(grewExposed);
|
||||
CHECK(world.blockAt(3, 3, 4) == Block::Dirt); // stays dirt, blocked by the stone above
|
||||
}
|
||||
|
||||
VF_TEST(tryGrowGrassRejectsNonDirtAndCoveredDirt) {
|
||||
World world;
|
||||
world.setBlock(0, 0, 0, Block::Stone);
|
||||
CHECK(!tryGrowGrass(world, {0, 0, 0}));
|
||||
|
||||
world.setBlock(0, 1, 0, Block::Dirt);
|
||||
world.setBlock(0, 2, 0, Block::Stone);
|
||||
CHECK(!tryGrowGrass(world, {0, 1, 0}));
|
||||
CHECK(world.blockAt(0, 1, 0) == Block::Dirt);
|
||||
|
||||
world.setBlock(0, 2, 0, Block::Air);
|
||||
CHECK(tryGrowGrass(world, {0, 1, 0}));
|
||||
CHECK(world.blockAt(0, 1, 0) == Block::Grass);
|
||||
}
|
||||
|
||||
VF_TEST(currentTickAdvancesByOnePerCall) {
|
||||
World world;
|
||||
BlockTickScheduler scheduler(1, 0);
|
||||
CHECK_EQ(scheduler.currentTick(), 0);
|
||||
scheduler.tick(world);
|
||||
scheduler.tick(world);
|
||||
scheduler.tick(world);
|
||||
CHECK_EQ(scheduler.currentTick(), 3);
|
||||
}
|
||||
Reference in new issue
Block a user