Files
EVEngine/test/pixelworld_streaming.cpp
T
西风逍遥游andCursor 24502babef Nest satellite packages under their host modules.
Keep CMake module names and trim switches, but stop flattening editing/editor and runtime bridges as top-level src/modules directories. Parent scans skip nested facets so runtime-3d can drop authoring by directory leaf.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-06 17:56:12 +08:00

308 lines
16 KiB
C++

#include "zeroerr/assert.h"
#include "zeroerr/unittest.h"
#include "pixelworld/PixelWorld.h"
#include "pixelworld/streaming/PixelWorldStreaming.h"
#include "pixelworld/streaming/PixelWorldTransport.h"
#include <algorithm>
#include <cstdint>
#include <string_view>
using namespace eve::pixelworld;
using namespace eve::pixelworld_streaming;
namespace {
eve::SnapshotHashProvider archiveHashProvider() {
return [](std::string_view input) -> eve::Result<eve::ContentId> {
std::uint64_t left = 14695981039346656037ULL;
std::uint64_t right = 1099511628211ULL;
for (const unsigned char byte : input) {
left = (left ^ byte) * 1099511628211ULL;
right = (right ^ (std::uint64_t(byte) + 0x9e3779b97f4a7c15ULL)) * 14029467366897019727ULL;
}
eve::ContentId::Bytes bytes{};
for (int index = 0; index < 8; ++index) {
bytes[std::size_t(index)] = std::uint8_t(left >> (56 - index * 8));
bytes[std::size_t(index + 8)] = std::uint8_t(right >> (56 - index * 8));
}
return eve::Result<eve::ContentId>::success(eve::ContentId(bytes));
};
}
eve::LogicalId chunkArchiveSchema() {
return *eve::LogicalId::parse("pixelworld:chunk-batch");
}
} // namespace
TEST_CASE("pixelworld_streaming_interest_move_emits_entry_and_eviction_correction") {
PixelWorld authority(8301);
authority.setMaterial(1, 1, "stone");
authority.setMaterial(2 * kPixelChunkSize + 1, 1, "water");
PixelChunkStreamCursor cursor;
auto first = cursor.capture(authority, {0, 0, 0, 0}).expect("capture first interest");
CHECK(first.fullResync);
REQUIRE_EQ(first.batch.chunks.size(), std::size_t(1));
PixelWorld replica(8301);
replica.applyChunkBatch(first.batch, 0).expect("apply first interest");
CHECK_EQ(replica.getMaterial(1, 1), int(MaterialId::Stone));
CHECK_EQ(replica.getMaterial(2 * kPixelChunkSize + 1, 1), int(MaterialId::Air));
auto moved = cursor.capture(authority, {2, 0, 2, 0}).expect("move interest");
CHECK(!moved.fullResync);
CHECK_EQ(moved.chunksEntered, std::uint32_t(1));
CHECK_EQ(moved.chunksEvicted, std::uint32_t(1));
REQUIRE_EQ(moved.batch.chunks.size(), std::size_t(2));
replica.applyChunkBatch(moved.batch, replica.revision()).expect("apply moved interest");
CHECK_EQ(replica.getMaterial(1, 1), int(MaterialId::Air));
CHECK_EQ(replica.getMaterial(2 * kPixelChunkSize + 1, 1), int(MaterialId::Water));
const auto unchanged = cursor.capture(authority, {2, 0, 2, 0}).expect("unchanged interest");
CHECK(unchanged.batch.chunks.empty());
}
TEST_CASE("pixelworld_streaming_disconnect_reconnect_applies_authoritative_chunk_delta") {
PixelWorld authority(8302);
authority.setMaterial(4, 4, "wood");
PixelChunkStreamCursor cursor;
auto initial = cursor.capture(authority, {0, 0, 0, 0}).expect("initial sync");
PixelWorld replica(8302);
replica.applyChunkBatch(initial.batch, 0).expect("apply initial sync");
authority.setMaterial(4, 4, "fire");
authority.setMaterial(5, 4, "oil");
auto reconnect = cursor.capture(authority, {0, 0, 0, 0}).expect("reconnect delta");
REQUIRE_EQ(reconnect.batch.chunks.size(), std::size_t(1));
replica.applyChunkBatch(reconnect.batch, replica.revision()).expect("apply reconnect correction");
CHECK_EQ(replica.getCell(4, 4), authority.getCell(4, 4));
CHECK_EQ(replica.getCell(5, 4), authority.getCell(5, 4));
CHECK_EQ(replica.revision(), authority.revision());
}
TEST_CASE("pixelworld_streaming_epoch_change_forces_full_resync_and_stale_writer_rejects") {
PixelWorld authority(8303);
authority.setMaterial(3, 3, "sand");
const auto saved = authority.saveSnapshot().expect("save authority");
PixelChunkStreamCursor cursor;
auto initial = cursor.capture(authority, {0, 0, 0, 0}).expect("initial");
CHECK(initial.fullResync);
authority.restoreSnapshot(saved).expect("restore changes epoch");
auto rebuilt = cursor.capture(authority, {0, 0, 0, 0}).expect("full rebuild");
CHECK(rebuilt.fullResync);
REQUIRE_EQ(rebuilt.batch.chunks.size(), std::size_t(1));
PixelWorld replica(8303);
replica.setMaterial(10, 10, "stone");
const auto before = replica.saveSnapshot().expect("before stale correction");
CHECK(!replica.applyChunkBatch(rebuilt.batch, 0).ok());
const auto after = replica.saveSnapshot().expect("after stale correction");
REQUIRE_EQ(before.size(), after.size());
CHECK(std::equal(before.begin(), before.end(), after.begin()));
}
TEST_CASE("pixelworld_streaming_full_resync_atomically_clears_projection_and_accepts_rewind") {
PixelWorld authority(8304);
authority.setMaterial(3, 3, "stone");
PixelChunkStreamCursor cursor;
const auto populated = cursor.capture(authority, {0, 0, 0, 0}).expect("populated sync");
REQUIRE(populated.batch.fullResync);
PixelWorld replica(8304);
replica.applyChunkBatch(populated.batch, 0).expect("apply populated projection");
CHECK_EQ(replica.getMaterial(3, 3), int(MaterialId::Stone));
PixelWorld emptyAuthority(8304);
const auto emptySnapshot = emptyAuthority.saveSnapshot().expect("empty authority snapshot");
authority.restoreSnapshot(emptySnapshot).expect("rewind authority to empty epoch");
const auto reset = cursor.capture(authority, {0, 0, 0, 0}).expect("rewound full sync");
CHECK(reset.fullResync);
CHECK(reset.batch.fullResync);
CHECK_EQ(reset.batch.sourceRevision, std::uint64_t(0));
CHECK(reset.batch.chunks.empty());
replica.applyChunkBatch(reset.batch, replica.revision()).expect("replace projection after rewind");
CHECK_EQ(replica.revision(), std::uint64_t(0));
CHECK_EQ(replica.chunkCount(), std::size_t(0));
CHECK_EQ(replica.getMaterial(3, 3), int(MaterialId::Air));
}
TEST_CASE("pixelworld_streaming_zstd_chunk_archive_round_trips_and_is_smaller_than_raw") {
const auto hash = archiveHashProvider();
PixelWorld authority(8401);
for (int y = 0; y < kPixelChunkSize; ++y)
for (int x = 0; x < kPixelChunkSize; ++x)
authority.setCell(x, y, {MaterialId::Stone, 20, 0});
authority.setCell(17, 23, {MaterialId::Stone, 20, 0, 73});
PixelChunkStreamCursor cursor;
const auto update = cursor.capture(authority, {0, 0, 0, 0}).expect("capture archive batch");
const auto compressed = archiveChunkBatch(update.batch, eve::PersistentId::nil(),
PixelChunkArchiveCodec::Zstd, hash)
.expect("compress archive");
const auto raw = archiveChunkBatch(update.batch, eve::PersistentId::nil(),
PixelChunkArchiveCodec::None, hash)
.expect("raw archive");
const auto compressedJson = eve::serializeSnapshotEnvelope(compressed).expect("serialize compressed");
const auto rawJson = eve::serializeSnapshotEnvelope(raw).expect("serialize raw");
CHECK(compressedJson.size() < rawJson.size() / 4);
const auto parsed = eve::parseSnapshotEnvelope(compressedJson, hash).expect("parse compressed");
const auto decoded = decodeChunkBatchArchive(parsed, hash).expect("decode archive");
PixelWorld replica(8401);
replica.applyChunkBatch(decoded, 0).expect("apply decoded archive");
CHECK_EQ(replica.getCell(17, 23), authority.getCell(17, 23));
CHECK_EQ(replica.getCell(17, 23).thermalRemainder, std::uint16_t(73));
CHECK_EQ(replica.revision(), authority.revision());
}
TEST_CASE("pixelworld_streaming_chunk_archive_v1_migrates_and_corruption_is_isolated") {
const auto hash = archiveHashProvider();
PixelWorld authority(8402);
authority.setMaterial(3, 4, "water");
PixelChunkStreamCursor cursor;
const auto update = cursor.capture(authority, {0, 0, 0, 0}).expect("capture");
const auto current = archiveChunkBatch(update.batch, eve::PersistentId::nil(),
PixelChunkArchiveCodec::Zstd, hash)
.expect("archive");
auto legacyPayload = current.payload;
legacyPayload.getIf<eve::Value::Object>()->erase("sourceLastEditSequence");
legacyPayload.getIf<eve::Value::Object>()->erase("fullResync");
const auto legacy = eve::makeSnapshotEnvelope(
"pixelworld.chunk-batch", chunkArchiveSchema(), eve::SchemaVersion(1),
eve::PersistentId::nil(), current.revision, current.tick,
std::move(legacyPayload), hash)
.expect("seal legacy");
const auto migrated = decodeChunkBatchArchive(legacy, hash).expect("decode legacy");
CHECK_EQ(migrated.sourceLastEditSequence, std::uint64_t(0));
REQUIRE_EQ(migrated.chunks.size(), std::size_t(1));
auto corruptPayload = current.payload;
auto& chunks = *corruptPayload.getIf<eve::Value::Object>()->at("chunks").getIf<eve::Value::Array>();
auto& stored = *chunks.front().getIf<eve::Value::Object>()->at("stored").getIf<std::string>();
REQUIRE(!stored.empty());
stored[0] = stored[0] == '0' ? '1' : '0';
const auto resealedCorrupt = eve::makeSnapshotEnvelope(
"pixelworld.chunk-batch", chunkArchiveSchema(), eve::SchemaVersion(2),
eve::PersistentId::nil(), current.revision, current.tick,
std::move(corruptPayload), hash)
.expect("seal corrupt physical payload");
PixelWorld untouched(8402);
untouched.setMaterial(9, 9, "wood");
const auto before = untouched.saveSnapshot().expect("before corrupt decode");
CHECK(!decodeChunkBatchArchive(resealedCorrupt, hash).ok());
const auto after = untouched.saveSnapshot().expect("after corrupt decode");
CHECK(std::equal(before.begin(), before.end(), after.begin()));
auto badOuterHash = current;
badOuterHash.contentHash = eve::ContentId::nil();
CHECK(!decodeChunkBatchArchive(badOuterHash, hash).ok());
}
TEST_CASE("pixelworld_transport_retransmits_loss_and_commits_complete_transfer_atomically") {
PixelWorld authority(8501);
authority.setMaterial(1, 1, "stone");
authority.setMaterial(kPixelChunkSize + 1, 1, "water");
authority.setMaterial(2 * kPixelChunkSize + 1, 1, "wood");
auto sender = ReliablePixelChunkSender::create(91).expect("create sender");
auto receiver = ReliablePixelChunkReceiver::create(91).expect("create receiver");
const auto parts = sender.capture(authority, {0, 0, 2, 0}).expect("capture transfer");
REQUIRE_EQ(parts.size(), std::size_t(3));
PixelWorld replica(8501);
CHECK_EQ(receiver.receive(parts[2], replica).expect("part 2").transfersApplied,
std::uint32_t(0));
CHECK_EQ(receiver.receive(parts[0], replica).expect("part 0").transfersApplied,
std::uint32_t(0));
CHECK(receiver.receive(parts[2], replica).expect("duplicate part 2").duplicate);
CHECK_EQ(replica.chunkCount(), std::size_t(0));
REQUIRE_EQ(sender.pendingParts().size(), std::size_t(3));
const auto completed = receiver.receive(sender.pendingParts()[1], replica)
.expect("retransmitted missing part");
CHECK_EQ(completed.transfersApplied, std::uint32_t(1));
CHECK_EQ(completed.ack.acknowledgedThrough, std::uint64_t(1));
CHECK_EQ(replica.getCell(2 * kPixelChunkSize + 1, 1),
authority.getCell(2 * kPixelChunkSize + 1, 1));
const auto released = sender.acknowledge(completed.ack).expect("application ACK");
CHECK_EQ(released.transfersReleased, std::uint32_t(1));
CHECK_EQ(sender.inFlightTransferCount(), std::size_t(0));
}
TEST_CASE("pixelworld_transport_buffers_transfer_order_and_retains_tombstones_until_ACK") {
PixelWorld authority(8502);
authority.setMaterial(1, 1, "stone");
authority.setMaterial(kPixelChunkSize + 1, 1, "water");
auto sender = ReliablePixelChunkSender::create(92).expect("create sender");
auto receiver = ReliablePixelChunkReceiver::create(92).expect("create receiver");
PixelWorld replica(8502);
const auto initial = sender.capture(authority, {0, 0, 1, 0}).expect("initial");
PixelChunkReceiveReceipt receipt;
for (const auto& part : initial)
receipt = receiver.receive(part, replica).expect("receive initial");
sender.acknowledge(receipt.ack).expect("ACK initial");
authority.setMaterial(2, 2, "oil");
const auto second = sender.capture(authority, {0, 0, 1, 0}).expect("second");
authority.setMaterial(kPixelChunkSize + 2, 2, "wood");
const auto third = sender.capture(authority, {0, 0, 1, 0}).expect("third");
REQUIRE_EQ(second.size(), std::size_t(1));
REQUIRE_EQ(third.size(), std::size_t(1));
CHECK_EQ(receiver.receive(third.front(), replica).expect("third early").transfersApplied,
std::uint32_t(0));
const auto ordered = receiver.receive(second.front(), replica).expect("fill transfer gap");
CHECK_EQ(ordered.transfersApplied, std::uint32_t(2));
CHECK_EQ(ordered.ack.acknowledgedThrough, std::uint64_t(3));
CHECK_EQ(replica.getCell(kPixelChunkSize + 2, 2),
authority.getCell(kPixelChunkSize + 2, 2));
sender.acknowledge(ordered.ack).expect("ACK ordered transfers");
const auto evicted = sender.capture(authority, {0, 0, 0, 0}).expect("evict interest");
REQUIRE_EQ(evicted.size(), std::size_t(1));
CHECK(evicted.front().batch.chunks.front().removed);
CHECK_EQ(sender.retainedTombstoneCount(), std::size_t(1));
const auto tombstone = receiver.receive(evicted.front(), replica).expect("apply tombstone");
CHECK_EQ(replica.getMaterial(kPixelChunkSize + 1, 1), int(MaterialId::Air));
sender.acknowledge(tombstone.ack).expect("ACK tombstone");
CHECK_EQ(sender.retainedTombstoneCount(), std::size_t(0));
CHECK_EQ(sender.acknowledge(tombstone.ack).expect("duplicate ACK").transfersReleased,
std::uint32_t(0));
}
TEST_CASE("pixelworld_transport_window_failure_does_not_advance_capture_cursor") {
PixelChunkTransportConfig config;
config.maximumInFlightTransfers = 1;
PixelWorld authority(8503);
authority.setMaterial(1, 1, "stone");
auto sender = ReliablePixelChunkSender::create(93, config).expect("create sender");
auto receiver = ReliablePixelChunkReceiver::create(93, config).expect("create receiver");
PixelWorld replica(8503);
const auto initial = sender.capture(authority, {0, 0, 0, 0}).expect("initial");
authority.setMaterial(2, 2, "water");
CHECK(!sender.capture(authority, {0, 0, 0, 0}).ok());
const auto first = receiver.receive(initial.front(), replica).expect("receive initial");
sender.acknowledge(first.ack).expect("free window");
const auto retry = sender.capture(authority, {0, 0, 0, 0}).expect("retry capture");
REQUIRE_EQ(retry.size(), std::size_t(1));
CHECK_EQ(retry.front().batch.sourceRevision, authority.revision());
CHECK_EQ(int(retry.front().batch.chunks.front().cells[2 * kPixelChunkSize + 2].material),
int(MaterialId::Water));
}
TEST_CASE("pixelworld_transport_rejects_conflicting_duplicate_and_invalid_ACK") {
PixelWorld authority(8504);
authority.setMaterial(1, 1, "stone");
authority.setMaterial(kPixelChunkSize + 1, 1, "water");
auto sender = ReliablePixelChunkSender::create(94).expect("create sender");
auto receiver = ReliablePixelChunkReceiver::create(94).expect("create receiver");
const auto parts = sender.capture(authority, {0, 0, 1, 0}).expect("capture");
REQUIRE_EQ(parts.size(), std::size_t(2));
PixelWorld replica(8504);
receiver.receive(parts.front(), replica).expect("receive partial");
auto conflict = parts.front();
conflict.batch.sourceRevision += 1;
CHECK(!receiver.receive(std::move(conflict), replica).ok());
CHECK(!sender.acknowledge({94, 1, authority.revision() + 1}).ok());
CHECK_EQ(sender.inFlightTransferCount(), std::size_t(1));
}