Files
ReSkate/Extension/Multiplayer/developer_identity_fetch.cpp
AyeZeeBB 4ac1343b95 Crash reports: self-hosted Sentry instead of Backtrace, and opt-in performance timings
Crash dumps now go to the project's own Sentry server. The launcher's
out-of-process helper is unchanged; only where it posts and what it expects
back are:

- The default endpoint is the Sentry project's minidump URL. Report
  attributes travel as Sentry tags, with the release as reskate@<version>.
- An upload counts as accepted when the server answers with an event ID.
- "backtrace" is "crash_report" throughout: the namespace, files, CMake
  targets, DINGOSDK_CRASH_REPORT_URL, DINGOSDK_BUILD_CRASH_REPORT_TESTS and
  the RESKATE_CRASH_REPORT_URL override.

Performance timings are new, and off unless "Send performance timings" is
ticked in the launcher's settings (RESKATE_TRACING=1). A small tracer
(Engine/Core/Debug/trace.h) posts each finished step to Sentry as a
transaction from a background thread; the launcher and the game it starts
share one trace. Timed: the update check, the Steam download, Thunderstore
installs, the mod merge, starting the game, the launch up to the first
playable level, each level load, joining a session, fetching the identity
lists, and the time spent in each level with its average frame rate and
long frames. Turning crash reports off turns timings off too.
2026-10-12 23:24:33 -05:00

70 lines
3.1 KiB
C++

#include "developer_identity.h"
#include "word_lists.h"
#include "Engine/Core/Log/logging.h"
#include "Engine/Core/Debug/trace.h"
#include "Engine/Vfs/https_download.h"
#include <Windows.h>
#include <atomic>
#include <string>
#include <thread>
// The game's way of reading the lists: in the background, driven by the client tick.
namespace dingosdk::multiplayer {
namespace {
constexpr std::uint64_t max_bytes = 256 * 1024, refresh_ms = 10 * 60 * 1000, retry_ms = 60 * 1000;
std::atomic<bool> fetching{};
std::atomic<std::uint64_t> next_fetch{}; // GetTickCount64 time
void fetch() noexcept {
// The log says when the lists arrive, change or stop arriving, not every refresh.
static bool failing{};
bool ok{};
try {
https::Download download;
trace::Scope timing("backend.identity_lists", "http.client");
const auto answer = https::get_text(std::wstring(identity_lists_url.begin(), identity_lists_url.end()), max_bytes, 10,
L"ReSkate-Identity/1", &download);
if (!answer) timing.fail();
if (answer) {
auto lists = parse_identity_lists(*answer);
server_tokens_rule = parse_server_tokens_required(*answer);
const auto developers = lists[0].size(), homies = lists[1].size(), creators = lists[2].size(),
bans = lists[static_cast<std::size_t>(IdentityList::banned)].size();
if (publish_identity_lists(std::move(lists)) || failing)
logging::log(logging::Level::info, logging::Channel::runtime,
"Identity lists: {} developer(s), {} homie(s), {} content creator(s), {} banned.", developers,
homies, creators, bans);
// The chat word lists come in the same answer (word_lists.h).
if (const auto words = use_word_lists(*answer); words.changed)
logging::log(logging::Level::info, logging::Channel::runtime,
"Word lists: {} filtered and {} not allowed at all, from the ReSkate backend.", words.filtered,
words.forbidden);
ok = true;
} else if (!failing) {
logging::log(logging::Level::info, logging::Channel::runtime,
"Identity lists unavailable (HTTP {}, error {}); trying again every minute.", download.http_status,
download.error);
}
} catch (const std::exception &e) {
if (!failing)
logging::log(logging::Level::warning, logging::Channel::runtime,
"Identity lists are invalid ({}); trying again every minute.", e.what());
} catch (...) {}
failing = !ok;
// A failure keeps the lists already in use.
next_fetch = GetTickCount64() + (ok ? refresh_ms : retry_ms);
fetching = false;
}
} // namespace
void refresh_identity_lists() noexcept {
if (GetTickCount64() < next_fetch || fetching.exchange(true)) return;
try {
std::thread(fetch).detach();
} catch (...) {
next_fetch = GetTickCount64() + retry_ms;
fetching = false;
}
}
} // namespace dingosdk::multiplayer