129 lines
4.3 KiB
C++
129 lines
4.3 KiB
C++
#include "game_server/NetworkProtocol.h"
|
|
#include "LocationState.h"
|
|
#include <cstdlib>
|
|
#include <iostream>
|
|
#include <string>
|
|
|
|
namespace {
|
|
|
|
void require(bool condition, const char* message)
|
|
{
|
|
if (condition) return;
|
|
std::cerr << "NetworkProtocolTests: " << message << '\n';
|
|
std::exit(EXIT_FAILURE);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
int main()
|
|
{
|
|
FRG::LocationNetworkState locationState;
|
|
nlohmann::json savedLocationState;
|
|
locationState.save(savedLocationState);
|
|
const nlohmann::json original{
|
|
{"schemaVersion", 2},
|
|
{"campaignId", "battle_zone_multiplayer"},
|
|
{"players", {{"player", {{"hp", 200.0}}}}},
|
|
{"locations", {{"uni_exterior", {{"state", savedLocationState}}}}}
|
|
};
|
|
FRG::InitialStateMessage initial;
|
|
initial.state = original;
|
|
initial.assignedPlayerId = "player";
|
|
initial.snapshotTick = 42;
|
|
initial.compatibilityFingerprint = "lockstep-v2:battle_zone_multiplayer";
|
|
const std::string message = FRG::makeInitialStateMessage(initial);
|
|
FRG::InitialStateMessage parsed;
|
|
std::string error;
|
|
require(
|
|
FRG::parseInitialStateMessage(message, parsed, error),
|
|
"valid initial-state envelope was rejected");
|
|
require(parsed.state == original, "state payload did not round trip");
|
|
require(parsed.snapshotTick == 42, "snapshot tick did not round trip");
|
|
require(
|
|
parsed.assignedPlayerId == "player",
|
|
"assigned player did not round trip");
|
|
require(
|
|
!parsed.state["locations"]["uni_exterior"]["state"].contains("cameraAzimuth") &&
|
|
!parsed.state["locations"]["uni_exterior"]["state"].contains("cameraInclination"),
|
|
"local camera state leaked into the network payload");
|
|
|
|
FRG::DestinationInputMessage input;
|
|
input.clientSequence = 7;
|
|
input.target = Eigen::Vector3f(1.234f, 0.f, -5.678f);
|
|
FRG::DestinationInputMessage parsedInput;
|
|
require(
|
|
FRG::parseDestinationInputMessage(
|
|
FRG::makeDestinationInputMessage(input), parsedInput, error),
|
|
"destination input did not round trip");
|
|
require(
|
|
parsedInput.clientSequence == 7 &&
|
|
parsedInput.target.isApprox(Eigen::Vector3f(1.23f, 0.f, -5.68f)),
|
|
"destination input was not normalized");
|
|
|
|
FRG::TickMessage tick;
|
|
tick.tickId = 43;
|
|
tick.commands.push_back({1, "player", parsedInput.target});
|
|
tick.stateHash = "0123456789abcdef";
|
|
FRG::TickMessage parsedTick;
|
|
require(
|
|
FRG::parseTickMessage(
|
|
FRG::makeTickMessage(tick), parsedTick, error),
|
|
"tick message did not round trip");
|
|
require(
|
|
parsedTick.tickId == 43 && parsedTick.commands.size() == 1,
|
|
"tick contents did not round trip");
|
|
|
|
require(
|
|
!FRG::parseInitialStateMessage(
|
|
R"({"protocolVersion":1,"type":"initial_state","payload":{}})",
|
|
parsed, error),
|
|
"unknown protocol version was accepted");
|
|
require(
|
|
!FRG::parseDestinationInputMessage(
|
|
R"({"protocolVersion":2,"type":"command","payload":{}})",
|
|
parsedInput, error),
|
|
"unknown message type was accepted");
|
|
require(
|
|
!FRG::parseInitialStateMessage("not json", parsed, error),
|
|
"malformed JSON was accepted");
|
|
require(
|
|
!FRG::parseDestinationInputMessage(
|
|
R"({"protocolVersion":2,"type":"destination_input","payload":{"clientSequence":1,"target":[1e999,0,0]}})",
|
|
parsedInput, error),
|
|
"non-finite destination was accepted");
|
|
std::string errorCode;
|
|
std::string errorDetail;
|
|
require(
|
|
FRG::parseErrorMessage(
|
|
FRG::makeErrorMessage("server_full", "No slot"),
|
|
errorCode, errorDetail) &&
|
|
errorCode == "server_full" && errorDetail == "No slot",
|
|
"error message did not round trip");
|
|
|
|
nlohmann::json hashState = original;
|
|
hashState["savedAt"] = "first";
|
|
const std::string firstHash = FRG::canonicalStateHash(hashState);
|
|
hashState["savedAt"] = "second";
|
|
require(
|
|
firstHash == FRG::canonicalStateHash(hashState),
|
|
"persistence timestamp changed deterministic hash");
|
|
|
|
FRG::LockstepTimeline timeline(2);
|
|
const std::uint64_t firstSequence =
|
|
timeline.schedule("player", Eigen::Vector3f(1.f, 0.f, 1.f));
|
|
const std::uint64_t secondSequence =
|
|
timeline.schedule("player_2", Eigen::Vector3f(2.f, 0.f, 2.f));
|
|
const FRG::TickMessage emptyTick = timeline.advance();
|
|
require(
|
|
emptyTick.tickId == 1 && emptyTick.commands.empty(),
|
|
"input-delay tick was not empty");
|
|
const FRG::TickMessage commandTick = timeline.advance();
|
|
require(
|
|
commandTick.tickId == 2 && commandTick.commands.size() == 2 &&
|
|
commandTick.commands[0].commandSequence == firstSequence &&
|
|
commandTick.commands[1].commandSequence == secondSequence,
|
|
"scheduled commands were not finalized in stable order");
|
|
|
|
return EXIT_SUCCESS;
|
|
}
|