#include #include "TestPeerNode.hpp" #include "QuicrPeerLink.hpp" #include "WorldStepProcessor.hpp" #include #include #include #include #include #include using namespace tw::p2p; // ── constants ───────────────────────────────────────────────────────────────── static constexpr int NUM_PEERS = 5; static constexpr uint16_t BASE_PORT = 7600; static constexpr int NUM_TICKS = 120; // 2 s at 60 Hz static constexpr auto TICK_PERIOD = std::chrono::microseconds(static_cast(WorldStepProcessor::FIXED_DELTA_S * 1e6)); static const char* REGISTRY_PATH = "/tmp/tw_p2p_bench_registry.csv"; // ── helpers ─────────────────────────────────────────────────────────────────── static void cleanup_files() { ::unlink(REGISTRY_PATH); for (int i = 1; i <= NUM_PEERS; ++i) { std::string path = std::string("/tmp/peer_node_") + std::to_string(i) + ".csv"; ::unlink(path.c_str()); } } // ── benchmark test ──────────────────────────────────────────────────────────── TEST_CASE("10 peers synchronize and write per-entity CSV", "[p2p][benchmark]") { cleanup_files(); // Phase barrier: all nodes must register before any node connects. std::barrier registered(NUM_PEERS); struct Result { uint64_t stepped = 0; uint64_t rollbacks = 0; }; std::vector results(NUM_PEERS); auto run_node = [&](int idx) { const uint32_t id = static_cast(idx + 1); const uint16_t port = BASE_PORT + static_cast(idx); auto link = std::make_unique(id, port); std::string csv = "/tmp/peer_node_" + std::to_string(id) + ".quicr.csv"; auto node = std::make_unique(id, port, std::move(link), REGISTRY_PATH, csv); node->register_self(); registered.arrive_and_wait(); // block until every peer has registered node->discover_and_connect(); node->wait_for_connections(NUM_PEERS - 1, std::chrono::milliseconds(3000)); auto deadline = std::chrono::steady_clock::now(); for (int i = 0; i < NUM_TICKS; ++i) { deadline += TICK_PERIOD; node->tick(); std::this_thread::sleep_until(deadline); } results[idx].stepped = node->stepped_frames(); results[idx].rollbacks = node->rollbacks(); spdlog::info("[p2p] node {:2d} stepped={:4} rollbacks={:4} rollback_rate={:.1f}%", id, results[idx].stepped, results[idx].rollbacks, 100.0 * static_cast(results[idx].rollbacks) / static_cast(results[idx].stepped + 1)); }; std::vector threads; threads.reserve(NUM_PEERS); for (int i = 0; i < NUM_PEERS; ++i) threads.emplace_back(run_node, i); for (auto& t : threads) t.join(); // ── assertions ──────────────────────────────────────────────────────────── uint64_t min_stepped = std::numeric_limits::max(); uint64_t max_stepped = 0; for (int i = 0; i < NUM_PEERS; ++i) { REQUIRE(results[i].stepped > 0); min_stepped = std::min(min_stepped, results[i].stepped); max_stepped = std::max(max_stepped, results[i].stepped); } double sync_ratio = static_cast(min_stepped) / static_cast(max_stepped); REQUIRE(sync_ratio > 0.80); cleanup_files(); }