test: cover testEditHistory
This commit is contained in:
1 parent
8454f86641
commit
bb2a850641
1 file changed
+217
@@ -159,3 +159,220 @@ VF_TEST(undoingARandomSequenceOfTwoHundredEditsRestoresTheWorldByteForByte) {
|
||||
CHECK(!history.redo(world));
|
||||
}
|
||||
|
||||
VF_TEST(aNewEditThrowsAwayTheRedoStack) {
|
||||
World world;
|
||||
EditHistory history;
|
||||
|
||||
history.beginBatch("one", 1u);
|
||||
history.recordSet(world, {0, 0, 0}, Block::Stone);
|
||||
history.endBatch();
|
||||
CHECK(history.undo(world));
|
||||
CHECK_EQ(history.redoDepth(), 1u);
|
||||
|
||||
history.beginBatch("two", 2u);
|
||||
history.recordSet(world, {5, 0, 0}, Block::Dirt);
|
||||
history.endBatch();
|
||||
CHECK_EQ(history.redoDepth(), 0u);
|
||||
CHECK(!history.redo(world));
|
||||
CHECK(world.blockAt(0, 0, 0) == Block::Air);
|
||||
CHECK(world.blockAt(5, 0, 0) == Block::Dirt);
|
||||
}
|
||||
|
||||
VF_TEST(theMemoryBudgetDropsTheOldestBatchesFirst) {
|
||||
World probe;
|
||||
EditHistory sizer;
|
||||
sizer.beginBatch("probe", 0u);
|
||||
for (int z = 0; z < 10; ++z) sizer.recordSet(probe, {0, 0, z}, Block::Stone);
|
||||
sizer.endBatch();
|
||||
std::size_t batchBytes = sizer.peekUndo()->byteSize();
|
||||
CHECK(batchBytes > 0u);
|
||||
|
||||
World world;
|
||||
EditHistory history(batchBytes * 3u);
|
||||
for (int batch = 0; batch < 10; ++batch) {
|
||||
history.beginBatch("probe", static_cast<uint64_t>(batch));
|
||||
for (int z = 0; z < 10; ++z) history.recordSet(world, {batch, 0, z}, Block::Stone);
|
||||
CHECK(history.endBatch());
|
||||
}
|
||||
|
||||
CHECK_EQ(history.undoDepth(), 3u);
|
||||
CHECK_EQ(history.stats().droppedBatches, 7u);
|
||||
CHECK(history.stats().bytes <= batchBytes * 3u);
|
||||
|
||||
while (history.undo(world)) {
|
||||
}
|
||||
// The seven oldest batches were evicted, so their blocks stay in the world.
|
||||
for (int batch = 0; batch < 7; ++batch) CHECK(world.blockAt(batch, 0, 5) == Block::Stone);
|
||||
for (int batch = 7; batch < 10; ++batch) CHECK(world.blockAt(batch, 0, 5) == Block::Air);
|
||||
}
|
||||
|
||||
VF_TEST(shrinkingTheBudgetLaterAlsoEvictsTheOldestBatches) {
|
||||
World world;
|
||||
EditHistory history(1u << 20);
|
||||
for (int batch = 0; batch < 6; ++batch) {
|
||||
history.beginBatch("fill", static_cast<uint64_t>(batch));
|
||||
for (int z = 0; z < 4; ++z) history.recordSet(world, {batch, 0, z}, Block::Sand);
|
||||
history.endBatch();
|
||||
}
|
||||
CHECK_EQ(history.undoDepth(), 6u);
|
||||
|
||||
std::size_t oneBatch = history.peekUndo()->byteSize();
|
||||
history.setMemoryBudget(oneBatch * 2u);
|
||||
CHECK_EQ(history.undoDepth(), 2u);
|
||||
CHECK_EQ(history.stats().droppedBatches, 4u);
|
||||
CHECK_EQ(history.memoryBudget(), oneBatch * 2u);
|
||||
}
|
||||
|
||||
VF_TEST(batchesTouchingTheSameBlocksCloseInTimeMergeIntoOne) {
|
||||
World world;
|
||||
EditHistory history;
|
||||
history.setMergeWindow(5u);
|
||||
CHECK_EQ(history.mergeWindowTicks(), 5u);
|
||||
|
||||
history.beginBatch("dab", 100u);
|
||||
history.recordSet(world, {0, 0, 0}, Block::Stone);
|
||||
history.endBatch();
|
||||
|
||||
history.beginBatch("dab", 102u);
|
||||
history.recordSet(world, {0, 0, 0}, Block::Dirt);
|
||||
history.recordSet(world, {1, 0, 0}, Block::Dirt);
|
||||
history.endBatch();
|
||||
|
||||
CHECK_EQ(history.undoDepth(), 1u);
|
||||
CHECK_EQ(history.stats().mergedBatches, 1u);
|
||||
CHECK_EQ(history.stats().opCount, 2u);
|
||||
|
||||
const EditBatch* merged = history.peekUndo();
|
||||
CHECK(merged->touches({0, 0, 0}));
|
||||
CHECK(merged->touches({1, 0, 0}));
|
||||
for (const EditOp& op : merged->ops) {
|
||||
if (op.position == Vec3i(0, 0, 0)) {
|
||||
CHECK(op.before == Block::Air);
|
||||
CHECK(op.after == Block::Dirt);
|
||||
}
|
||||
}
|
||||
|
||||
CHECK(history.undo(world));
|
||||
CHECK(world.blockAt(0, 0, 0) == Block::Air);
|
||||
CHECK(world.blockAt(1, 0, 0) == Block::Air);
|
||||
CHECK_EQ(history.undoDepth(), 0u);
|
||||
}
|
||||
|
||||
VF_TEST(batchesStaySeparateWhenTheyMissInSpaceOrInTime) {
|
||||
World world;
|
||||
EditHistory apart;
|
||||
apart.setMergeWindow(5u);
|
||||
apart.beginBatch("a", 100u);
|
||||
apart.recordSet(world, {0, 0, 0}, Block::Stone);
|
||||
apart.endBatch();
|
||||
apart.beginBatch("b", 101u);
|
||||
apart.recordSet(world, {9, 0, 0}, Block::Stone);
|
||||
apart.endBatch();
|
||||
CHECK_EQ(apart.undoDepth(), 2u);
|
||||
CHECK_EQ(apart.stats().mergedBatches, 0u);
|
||||
|
||||
World other;
|
||||
EditHistory late;
|
||||
late.setMergeWindow(5u);
|
||||
late.beginBatch("a", 100u);
|
||||
late.recordSet(other, {0, 0, 0}, Block::Stone);
|
||||
late.endBatch();
|
||||
late.beginBatch("b", 400u);
|
||||
late.recordSet(other, {0, 0, 0}, Block::Dirt);
|
||||
late.endBatch();
|
||||
CHECK_EQ(late.undoDepth(), 2u);
|
||||
CHECK_EQ(late.stats().mergedBatches, 0u);
|
||||
|
||||
CHECK(late.undo(other));
|
||||
CHECK(other.blockAt(0, 0, 0) == Block::Stone);
|
||||
CHECK(late.undo(other));
|
||||
CHECK(other.blockAt(0, 0, 0) == Block::Air);
|
||||
}
|
||||
|
||||
VF_TEST(aBatchThatChangesNothingIsNotRecorded) {
|
||||
World world;
|
||||
world.setBlock(0, 0, 0, Block::Stone);
|
||||
EditHistory history;
|
||||
|
||||
history.beginBatch("same", 1u);
|
||||
CHECK(!history.recordSet(world, {0, 0, 0}, Block::Stone));
|
||||
CHECK(!history.endBatch());
|
||||
CHECK_EQ(history.undoDepth(), 0u);
|
||||
|
||||
history.beginBatch("thereAndBack", 2u);
|
||||
CHECK(history.recordSet(world, {0, 0, 0}, Block::Dirt));
|
||||
CHECK(history.recordSet(world, {0, 0, 0}, Block::Stone));
|
||||
CHECK(!history.endBatch());
|
||||
CHECK_EQ(history.undoDepth(), 0u);
|
||||
CHECK(world.blockAt(0, 0, 0) == Block::Stone);
|
||||
}
|
||||
|
||||
VF_TEST(repaintingOneBlockInsideABatchKeepsASingleOpAndUndoesToTheOriginal) {
|
||||
World world;
|
||||
world.setBlock(4, 4, 4, Block::Stone);
|
||||
EditHistory history;
|
||||
|
||||
history.beginBatch("repaint", 1u);
|
||||
history.recordSet(world, {4, 4, 4}, Block::Dirt);
|
||||
history.recordSet(world, {4, 4, 4}, Block::Sand);
|
||||
history.recordSet(world, {4, 4, 4}, Block::Glass);
|
||||
CHECK_EQ(history.openBatchSize(), 1u);
|
||||
CHECK(history.endBatch());
|
||||
|
||||
CHECK_EQ(history.stats().opCount, 1u);
|
||||
CHECK(world.blockAt(4, 4, 4) == Block::Glass);
|
||||
CHECK(history.undo(world));
|
||||
CHECK(world.blockAt(4, 4, 4) == Block::Stone);
|
||||
}
|
||||
|
||||
VF_TEST(cancellingAnOpenBatchRollsTheWorldBackAndRecordsNothing) {
|
||||
World world = layeredWorld(3, 2);
|
||||
uint64_t before = worldFingerprint(world);
|
||||
EditHistory history;
|
||||
|
||||
history.beginBatch("oops", 1u);
|
||||
for (int x = 0; x < 3; ++x) history.recordSet(world, {x, 5, 0}, Block::Lava);
|
||||
CHECK(worldFingerprint(world) != before);
|
||||
|
||||
CHECK(history.cancelBatch(world));
|
||||
CHECK_EQ(worldFingerprint(world), before);
|
||||
CHECK_EQ(history.undoDepth(), 0u);
|
||||
CHECK(!history.batchOpen());
|
||||
CHECK(!history.cancelBatch(world));
|
||||
}
|
||||
|
||||
VF_TEST(historyStatsReportBatchesOperationsBytesAndCounters) {
|
||||
World world;
|
||||
EditHistory history;
|
||||
CHECK_EQ(history.stats().bytes, 0u);
|
||||
|
||||
for (int batch = 0; batch < 3; ++batch) {
|
||||
history.beginBatch("b", static_cast<uint64_t>(batch));
|
||||
for (int i = 0; i < 4; ++i)
|
||||
history.recordSet(world, {batch, 0, i}, Block::Stone);
|
||||
history.endBatch();
|
||||
}
|
||||
|
||||
HistoryStats summary = history.stats();
|
||||
CHECK_EQ(summary.batchCount, 3u);
|
||||
CHECK_EQ(summary.opCount, 12u);
|
||||
CHECK(summary.bytes >= 12u * sizeof(EditOp));
|
||||
CHECK_EQ(summary.undoCount, 0u);
|
||||
CHECK_EQ(summary.redoBatchCount, 0u);
|
||||
|
||||
history.undo(world);
|
||||
history.undo(world);
|
||||
summary = history.stats();
|
||||
CHECK_EQ(summary.batchCount, 1u);
|
||||
CHECK_EQ(summary.redoBatchCount, 2u);
|
||||
CHECK_EQ(summary.opCount, 4u);
|
||||
CHECK_EQ(summary.undoCount, 2u);
|
||||
|
||||
history.clear();
|
||||
summary = history.stats();
|
||||
CHECK_EQ(summary.batchCount, 0u);
|
||||
CHECK_EQ(summary.redoBatchCount, 0u);
|
||||
CHECK_EQ(summary.bytes, 0u);
|
||||
CHECK(history.peekUndo() == nullptr);
|
||||
CHECK(history.peekRedo() == nullptr);
|
||||
}
|
||||
Reference in new issue
Block a user