diff --git a/tests/testNetServer.cpp b/tests/testNetServer.cpp index a1ee6c8..ed3a364 100644 --- a/tests/testNetServer.cpp +++ b/tests/testNetServer.cpp @@ -158,3 +158,165 @@ VF_TEST(aBlockChangeInsideBothViewsReachesEachClientExactlyOnce) { CHECK(server.sessionFor(slotLeft)->view().hasChunk(Vec3i{1, 0, 0})); CHECK(server.sessionFor(slotRight)->view().hasChunk(Vec3i{1, 0, 0})); + server.setBlock(20, 3, 5, Block::Glowstone); + for (uint64_t tick = 80; tick < 120; ++tick) { + server.update(tick); + left.step(tick); + right.step(tick); + } + + CHECK(left.world.blockAt(20, 3, 5) == Block::Glowstone); + CHECK(right.world.blockAt(20, 3, 5) == Block::Glowstone); + CHECK_EQ(left.session->stats().blockChangesApplied, static_cast(1)); + CHECK_EQ(right.session->stats().blockChangesApplied, static_cast(1)); + CHECK_EQ(server.stats().blockUpdatesSent, static_cast(2)); + CHECK_EQ(server.stats().editsApplied, static_cast(1)); +} + +VF_TEST(aBlockChangeIsNeverSentToAClientThatDoesNotHoldThatChunk) { + ServerConfig config = quietServerConfig(); + World world; + NetServer server(world, config); + buildTerrain(world, -6, 6); + + int slotNear = server.acceptClient(); + int slotFar = server.acceptClient(); + placeAt(server, slotNear, 8.0f, 8.0f, 8.0f); + placeAt(server, slotFar, 72.0f, 8.0f, 72.0f); + + TransportConfig transportConfig = config.transport; + SessionConfig sessionConfig = clientConfigFor(config); + RemoteClient near(server, slotNear, transportConfig, sessionConfig, "near"); + RemoteClient far(server, slotFar, transportConfig, sessionConfig, "far"); + for (uint64_t tick = 0; tick < 60; ++tick) { + server.update(tick); + near.step(tick); + far.step(tick); + } + + for (int i = 0; i < 3; ++i) server.setBlock(i, 3, 0, Block::Obsidian); + for (uint64_t tick = 60; tick < 100; ++tick) { + server.update(tick); + near.step(tick); + far.step(tick); + } + + for (int i = 0; i < 3; ++i) CHECK(near.world.blockAt(i, 3, 0) == Block::Obsidian); + CHECK_EQ(near.session->stats().blockChangesApplied, static_cast(3)); + CHECK_EQ(far.session->stats().blockChangesApplied, static_cast(0)); + CHECK_EQ(far.session->stats().multiBlockChangesApplied, static_cast(0)); + CHECK_EQ(far.world.chunkAt(Vec3i{0, 0, 0}), nullptr); + CHECK_EQ(server.stats().blockUpdatesSent, static_cast(3)); +} + +VF_TEST(anEditFromOneClientIsBroadcastToTheOtherThroughTheAuthoritativeWorld) { + ServerConfig config = quietServerConfig(); + World world; + NetServer server(world, config); + buildTerrain(world, -2, 2); + + int slotA = server.acceptClient(); + int slotB = server.acceptClient(); + placeAt(server, slotA, 8.0f, 8.0f, 8.0f); + placeAt(server, slotB, 8.0f, 8.0f, 8.0f); + + TransportConfig transportConfig = config.transport; + SessionConfig sessionConfig = clientConfigFor(config); + RemoteClient builder(server, slotA, transportConfig, sessionConfig, "builder"); + RemoteClient watcher(server, slotB, transportConfig, sessionConfig, "watcher"); + for (uint64_t tick = 0; tick < 40; ++tick) { + server.update(tick); + builder.step(tick); + watcher.step(tick); + } + + builder.session->requestBlockEdit(4, 9, 4, Block::Glowstone); + for (uint64_t tick = 40; tick < 80; ++tick) { + server.update(tick); + builder.step(tick); + watcher.step(tick); + } + + CHECK(world.blockAt(4, 9, 4) == Block::Glowstone); + CHECK(builder.world.blockAt(4, 9, 4) == Block::Glowstone); + CHECK(watcher.world.blockAt(4, 9, 4) == Block::Glowstone); + CHECK_EQ(server.stats().editsApplied, static_cast(1)); + CHECK_EQ(watcher.session->stats().blockChangesApplied, static_cast(1)); +} + +VF_TEST(theBandwidthBudgetDelaysPacketsAcrossTicksWithoutLosingAnyOfThem) { + ServerConfig config = quietServerConfig(); + config.viewRadius = 1; + config.chunksPerClientPerTick = 4; + config.bytesPerClientPerTick = 220; + World world; + NetServer server(world, config); + buildTerrain(world, -3, 3); + + int slot = server.acceptClient(); + placeAt(server, slot, 8.0f, 8.0f, 8.0f); + TransportConfig transportConfig = config.transport; + RemoteClient only(server, slot, transportConfig, clientConfigFor(config), "budgeted"); + + bool everQueued = false; + uint64_t peakQueuedBytes = 0; + for (uint64_t tick = 0; tick < 40; ++tick) { + server.update(tick); + only.step(tick); + if (server.stats().queuedPackets > 0) everQueued = true; + if (server.stats().queuedBytes > peakQueuedBytes) + peakQueuedBytes = server.stats().queuedBytes; + // A single oversized packet is allowed through on its own, but the + // budget must never be exceeded by a whole extra packet. + CHECK(server.stats().bytesSentThisTick <= 220u + 4096u); + } + CHECK(everQueued); + CHECK(peakQueuedBytes > 220u); + + for (uint64_t tick = 40; tick < 400; ++tick) { + server.update(tick); + only.step(tick); + } + CHECK_EQ(server.stats().queuedPackets, static_cast(0)); + CHECK_EQ(only.world.chunkCount(), static_cast(5)); + CHECK_EQ(only.session->stats().chunksApplied, static_cast(5)); + + // Nothing was dropped on the way: the client sees the same blocks. + for (int x = 0; x < chunkSize; ++x) + for (int z = 0; z < chunkSize; ++z) + CHECK(only.world.blockAt(x, 3, z) == world.blockAt(x, 3, z)); +} + +VF_TEST(anUnbudgetedServerSendsTheSameChunksInFarFewerTicks) { + ServerConfig budgeted = quietServerConfig(); + budgeted.viewRadius = 1; + budgeted.chunksPerClientPerTick = 4; + budgeted.bytesPerClientPerTick = 220; + + ServerConfig open = budgeted; + open.bytesPerClientPerTick = 0; + + uint64_t ticksNeeded[2] = {0, 0}; + for (int variant = 0; variant < 2; ++variant) { + const ServerConfig& config = variant == 0 ? budgeted : open; + World world; + NetServer server(world, config); + buildTerrain(world, -3, 3); + int slot = server.acceptClient(); + placeAt(server, slot, 8.0f, 8.0f, 8.0f); + TransportConfig transportConfig = config.transport; + RemoteClient only(server, slot, transportConfig, clientConfigFor(config), "probe"); + + uint64_t tick = 0; + for (; tick < 500; ++tick) { + server.update(tick); + only.step(tick); + if (only.world.chunkCount() == 5) break; + } + ticksNeeded[variant] = tick; + CHECK_EQ(only.world.chunkCount(), static_cast(5)); + } + CHECK(ticksNeeded[0] > ticksNeeded[1]); + CHECK(ticksNeeded[1] < 20u); +} +