138 lines
3.9 KiB
C++
138 lines
3.9 KiB
C++
#include "game_server/InternalGameServer.h"
|
|
#include "game_server/WebsocketGameClient.h"
|
|
#include "game_server/WebsocketGameServer.h"
|
|
#include <chrono>
|
|
#include <cstdlib>
|
|
#include <iostream>
|
|
#include <memory>
|
|
#include <thread>
|
|
|
|
namespace {
|
|
|
|
void require(bool condition, const char* message)
|
|
{
|
|
if (condition) return;
|
|
std::cerr << "WebsocketLockstepTests: " << message << '\n';
|
|
std::exit(EXIT_FAILURE);
|
|
}
|
|
|
|
template <class Predicate>
|
|
bool waitFor(Predicate predicate)
|
|
{
|
|
const auto deadline =
|
|
std::chrono::steady_clock::now() + std::chrono::seconds(3);
|
|
while (std::chrono::steady_clock::now() < deadline) {
|
|
if (predicate()) return true;
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
|
}
|
|
return false;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
int main()
|
|
{
|
|
FRG::CampaignConfig campaign;
|
|
campaign.id = "test_lockstep";
|
|
campaign.networkPlayerSlots = {"player", "player2"};
|
|
|
|
FRG::InternalGameServer authoritative;
|
|
authoritative.startCampaign(campaign, {}, true, false);
|
|
nlohmann::json initialState;
|
|
authoritative.save(initialState);
|
|
initialState["players"]["player2"] = initialState["players"]["player"];
|
|
authoritative.load(initialState);
|
|
FRG::WebsocketGameServer server(
|
|
authoritative, {"player", "player2"}, "lockstep-v2:test_lockstep",
|
|
"127.0.0.1", 0);
|
|
auto client = std::make_unique<FRG::WebsocketGameClient>(
|
|
"127.0.0.1", std::to_string(server.port()), "/");
|
|
|
|
require(
|
|
waitFor([&]() { return client->connected(); }),
|
|
"client did not connect");
|
|
require(
|
|
waitFor([&]() {
|
|
client->update(0);
|
|
return client->initialized();
|
|
}),
|
|
"client did not receive initial state");
|
|
require(client->localPlayerId() == "player", "wrong player slot assigned");
|
|
|
|
auto secondClient = std::make_unique<FRG::WebsocketGameClient>(
|
|
"127.0.0.1", std::to_string(server.port()), "/");
|
|
require(
|
|
waitFor([&]() {
|
|
secondClient->update(0);
|
|
return secondClient->initialized();
|
|
}),
|
|
"second client did not receive initial state");
|
|
require(
|
|
secondClient->localPlayerId() == "player2",
|
|
"second client received the wrong player slot");
|
|
|
|
FRG::WebsocketGameClient overflow(
|
|
"127.0.0.1", std::to_string(server.port()), "/");
|
|
require(
|
|
waitFor([&]() {
|
|
overflow.update(0);
|
|
return overflow.rejected();
|
|
}),
|
|
"connection beyond configured slots was not rejected");
|
|
|
|
server.update();
|
|
require(
|
|
waitFor([&]() {
|
|
client->update(0);
|
|
secondClient->update(0);
|
|
return client->snapshot()->players.at("player2").state.enabled &&
|
|
secondClient->snapshot()->players.at("player").state.enabled;
|
|
}) &&
|
|
authoritative.snapshot()->players.at("player").state.enabled &&
|
|
authoritative.snapshot()->players.at("player2").state.enabled,
|
|
"connection presence was not broadcast to all replicas");
|
|
|
|
client->submit(FRG::SetCharacterDestinationCommand{
|
|
{}, "spoofed", Eigen::Vector3f(1.234f, 0.f, 2.345f), "ignored"});
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(20));
|
|
server.update();
|
|
server.update();
|
|
require(
|
|
waitFor([&]() {
|
|
client->update(0);
|
|
return client->snapshot()->revision > 1;
|
|
}),
|
|
"client did not consume finalized lockstep ticks");
|
|
|
|
secondClient.reset();
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
|
server.update();
|
|
require(
|
|
waitFor([&]() {
|
|
client->update(0);
|
|
return !client->snapshot()->players.at("player2").state.enabled;
|
|
}) &&
|
|
!authoritative.snapshot()->players.at("player2").state.enabled,
|
|
"disconnect presence was not propagated");
|
|
|
|
FRG::WebsocketGameClient replacement(
|
|
"127.0.0.1", std::to_string(server.port()), "/");
|
|
require(
|
|
waitFor([&]() {
|
|
replacement.update(0);
|
|
return replacement.initialized();
|
|
}),
|
|
"disconnected player slot was not reusable");
|
|
server.update();
|
|
require(
|
|
waitFor([&]() {
|
|
client->update(0);
|
|
replacement.update(0);
|
|
return client->snapshot()->players.at("player2").state.enabled;
|
|
}) &&
|
|
authoritative.snapshot()->players.at("player2").state.enabled,
|
|
"reconnected player was not enabled");
|
|
|
|
return EXIT_SUCCESS;
|
|
}
|