Add race result logger (existing patch from stk-server-docker)
This commit is contained in:
@@ -19,6 +19,7 @@
|
||||
#include "network/protocols/server_lobby.hpp"
|
||||
|
||||
#include "addons/addon.hpp"
|
||||
#include "network/race_result_logger.hpp"
|
||||
#include "config/user_config.hpp"
|
||||
#include "items/network_item_manager.hpp"
|
||||
#include "items/powerup_manager.hpp"
|
||||
@@ -2297,6 +2298,7 @@ void ServerLobby::checkRaceFinished()
|
||||
if (!RaceEventManager::get()->isRaceOver()) return;
|
||||
|
||||
Log::info("ServerLobby", "The game is considered finished.");
|
||||
RaceResultLogger::logResult();
|
||||
// notify the network world that it is stopped
|
||||
RaceEventManager::get()->stop();
|
||||
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
// SuperTuxKart dedicated-server PATCH (not part of upstream STK).
|
||||
//
|
||||
// Captures the per-player classification of a finished networked game and
|
||||
// appends it as one JSON object (newline-delimited) to $STK_RESULTS_FILE.
|
||||
// A separate "result-shipper" sidecar tails that file and POSTs each line to
|
||||
// the stats dashboard. Writing to a local file (rather than doing network I/O
|
||||
// here) keeps the game loop free of blocking/error-prone calls.
|
||||
//
|
||||
// Called from ServerLobby::checkRaceFinished() while World/RaceManager are
|
||||
// still alive (see stk-patches/0001-race-result-hook.patch).
|
||||
|
||||
#include "network/race_result_logger.hpp"
|
||||
|
||||
#include "config/stk_config.hpp"
|
||||
#include "karts/abstract_kart.hpp"
|
||||
#include "modes/linear_world.hpp"
|
||||
#include "modes/world.hpp"
|
||||
#include "network/remote_kart_info.hpp"
|
||||
#include "network/server_config.hpp"
|
||||
#include "race/race_manager.hpp"
|
||||
#include "utils/log.hpp"
|
||||
#include "utils/string_utils.hpp"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <ctime>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
namespace
|
||||
{
|
||||
std::string jsonEscape(const std::string& in)
|
||||
{
|
||||
std::string out;
|
||||
out.reserve(in.size() + 8);
|
||||
for (unsigned char c : in)
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
case '"': out += "\\\""; break;
|
||||
case '\\': out += "\\\\"; break;
|
||||
case '\n': out += "\\n"; break;
|
||||
case '\r': out += "\\r"; break;
|
||||
case '\t': out += "\\t"; break;
|
||||
default:
|
||||
if (c < 0x20)
|
||||
{
|
||||
char buf[8];
|
||||
std::snprintf(buf, sizeof(buf), "\\u%04x", (int)c);
|
||||
out += buf;
|
||||
}
|
||||
else
|
||||
{
|
||||
out += (char)c;
|
||||
}
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void RaceResultLogger::logResult()
|
||||
{
|
||||
const char* path = std::getenv("STK_RESULTS_FILE");
|
||||
if (path == NULL || path[0] == '\0')
|
||||
return; // feature disabled -> behave like stock STK
|
||||
|
||||
World* world = World::getWorld();
|
||||
RaceManager* rm = RaceManager::get();
|
||||
if (world == NULL || rm == NULL)
|
||||
return;
|
||||
|
||||
LinearWorld* lw = rm->modeHasLaps()
|
||||
? static_cast<LinearWorld*>(world) : NULL;
|
||||
|
||||
std::ostringstream o;
|
||||
o << "{";
|
||||
o << "\"schema\":1";
|
||||
o << ",\"ts\":" << (long long)std::time(NULL);
|
||||
o << ",\"server\":\""
|
||||
<< jsonEscape((std::string)ServerConfig::m_server_name) << "\"";
|
||||
o << ",\"track\":\"" << jsonEscape(rm->getTrackName()) << "\"";
|
||||
o << ",\"mode\":\"" << jsonEscape(rm->getMinorModeName()) << "\"";
|
||||
o << ",\"difficulty\":" << (int)rm->getDifficulty();
|
||||
o << ",\"laps\":" << rm->getNumLaps();
|
||||
o << ",\"reverse\":" << (rm->getReverseTrack() ? "true" : "false");
|
||||
|
||||
if (lw != NULL)
|
||||
{
|
||||
int flt = lw->getFastestLapTicks();
|
||||
long long flms = (flt >= 0)
|
||||
? (long long)(stk_config->ticks2Time(flt) * 1000.0f) : -1;
|
||||
o << ",\"fastest_lap_ms\":" << flms;
|
||||
o << ",\"fastest_lap_by\":\""
|
||||
<< jsonEscape(StringUtils::wideToUtf8(lw->getFastestLapKartName()))
|
||||
<< "\"";
|
||||
}
|
||||
|
||||
o << ",\"players\":[";
|
||||
const unsigned n = rm->getNumPlayers();
|
||||
bool first = true;
|
||||
for (unsigned i = 0; i < n; i++)
|
||||
{
|
||||
const RemoteKartInfo& ki = rm->getKartInfo(i);
|
||||
AbstractKart* kart = world->getKart(i);
|
||||
if (!first) o << ",";
|
||||
first = false;
|
||||
o << "{";
|
||||
o << "\"name\":\""
|
||||
<< jsonEscape(StringUtils::wideToUtf8(ki.getPlayerName())) << "\"";
|
||||
o << ",\"online_id\":" << (unsigned)ki.getOnlineId();
|
||||
o << ",\"country\":\"" << jsonEscape(ki.getCountryCode()) << "\"";
|
||||
o << ",\"kart\":\"" << jsonEscape(ki.getKartName()) << "\"";
|
||||
o << ",\"position\":" << (kart != NULL ? kart->getPosition() : -1);
|
||||
o << ",\"time_ms\":"
|
||||
<< (long long)(rm->getKartRaceTime(i) * 1000.0f);
|
||||
o << ",\"score\":" << rm->getKartScore(i);
|
||||
o << ",\"eliminated\":"
|
||||
<< ((kart != NULL && kart->isEliminated()) ? "true" : "false");
|
||||
o << "}";
|
||||
}
|
||||
o << "]}";
|
||||
|
||||
std::ofstream f(path, std::ios::out | std::ios::app);
|
||||
if (!f.is_open())
|
||||
{
|
||||
Log::warn("RaceResultLogger",
|
||||
"Could not open results file '%s'", path);
|
||||
return;
|
||||
}
|
||||
f << o.str() << "\n";
|
||||
f.flush();
|
||||
Log::info("RaceResultLogger",
|
||||
"Wrote race result: track=%s mode=%s players=%u",
|
||||
rm->getTrackName().c_str(), rm->getMinorModeName().c_str(), n);
|
||||
} // logResult
|
||||
@@ -0,0 +1,15 @@
|
||||
// SuperTuxKart dedicated-server PATCH (not part of upstream STK).
|
||||
// Emits one JSON line per finished game so an external service can build
|
||||
// leaderboards / history / records. See apps/stk-server/stk-patches/README.md.
|
||||
#ifndef HEADER_RACE_RESULT_LOGGER_HPP
|
||||
#define HEADER_RACE_RESULT_LOGGER_HPP
|
||||
|
||||
namespace RaceResultLogger
|
||||
{
|
||||
/** Append a single JSON object describing the just-finished game to the
|
||||
* file named by the STK_RESULTS_FILE environment variable. No-op when the
|
||||
* variable is unset/empty, so an unpatched-feeling default is preserved. */
|
||||
void logResult();
|
||||
}
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user