mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-02 19:03:43 +00:00
feat(dashboard): a packet capture's properties route
/api/inspector/sessions/:id/property serves CaptureProperty's worlds, built once per loaded capture on a worker after the replica pass, with the zone's and the models' names from the locale and the property saved now for each zone and clone (read on the main thread) for links and UGC meshes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -14,7 +14,9 @@
|
||||
|
||||
#include "BinaryPathFinder.h"
|
||||
#include "CaptureBundle.h"
|
||||
#include "CaptureProperty.h"
|
||||
#include "CaptureTools.h"
|
||||
#include "ClientAssets.h"
|
||||
#include "Database.h"
|
||||
#include "DashboardRoutes.h"
|
||||
#include "Game.h"
|
||||
@@ -112,6 +114,20 @@ namespace {
|
||||
}
|
||||
|
||||
const nlohmann::json* ReplicaFields(size_t i) const { return i < replica.size() && replica[i] ? &*replica[i] : nullptr; }
|
||||
|
||||
// The properties the capture saw (CaptureProperty), read once after the replica pass, on a worker thread
|
||||
std::once_flag propertyOnce;
|
||||
std::atomic<bool> propertyBuilt{};
|
||||
nlohmann::json property;
|
||||
|
||||
const nlohmann::json& Property() {
|
||||
Decode();
|
||||
std::call_once(propertyOnce, [this] {
|
||||
property = CaptureProperty::Build(bundle.records, startUs, [this](size_t i) { return ReplicaFields(i); });
|
||||
propertyBuilt = true;
|
||||
});
|
||||
return property;
|
||||
}
|
||||
};
|
||||
std::shared_ptr<Cached> g_Cache;
|
||||
|
||||
@@ -686,6 +702,46 @@ namespace CaptureReplay {
|
||||
{"worlds", WorldsJson(*cached)}, {"idleSeconds", 3}, {"bucket", 1}, {"interval", 1}, {"truncated", false} });
|
||||
});
|
||||
|
||||
Route(eHTTPMethod::GET, "/api/inspector/sessions/:id/property", Perm(PERMISSION),
|
||||
"The properties the capture saw, per world (zone, instance, clone): {worlds: [{zone, zoneName, instance, clone, saved, info, counts, "
|
||||
"models: [{object, lot, spawner, ugcId, behaviors, spans: [{from, to, i, position, rotation}]}], events}]}. From the captured replica "
|
||||
"packets and property game messages; `saved` is the property saved for that zone and clone now (its id for links and UGC meshes)",
|
||||
[](HTTPReply& reply, const HTTPContext& context) {
|
||||
const auto session = FindSession(context, reply);
|
||||
if (!session) return;
|
||||
const auto cached = Load(*session, reply);
|
||||
if (!cached) return;
|
||||
// On the main thread: zone names and the saved property of each world the capture was on
|
||||
std::map<std::pair<uint32_t, uint32_t>, json> saved;
|
||||
std::map<uint32_t, std::string> zoneNames;
|
||||
for (const auto& record : cached->bundle.records) {
|
||||
const auto& h = record.header;
|
||||
if (h.source != static_cast<uint8_t>(eCaptureSource::WORLD) || !h.zoneId || saved.contains({ h.zoneId, h.cloneId })) continue;
|
||||
saved[{ h.zoneId, h.cloneId }] = nullptr;
|
||||
if (!zoneNames.contains(h.zoneId)) zoneNames[h.zoneId] = GameText::ZoneName(h.zoneId);
|
||||
const auto info = h.cloneId ? Database::Get()->GetPropertyInfo(h.zoneId, h.cloneId) : std::nullopt;
|
||||
if (!info) continue;
|
||||
saved[{ h.zoneId, h.cloneId }] = { {"id", std::to_string(info->id)}, {"name", info->name}, {"ownerId", std::to_string(info->ownerId)},
|
||||
{"ownerName", CharacterName(info->ownerId)} };
|
||||
}
|
||||
Workers::Reply(reply, context, cached->propertyBuilt, [cached, saved = std::move(saved), zoneNames = std::move(zoneNames)](HTTPReply& out) {
|
||||
json worlds = cached->Property()["worlds"];
|
||||
for (auto& world : worlds) {
|
||||
const auto zone = world["zone"].get<uint32_t>(), clone = world["clone"].get<uint32_t>();
|
||||
const auto name = zoneNames.find(zone);
|
||||
world["zoneName"] = name == zoneNames.end() ? std::string{} : name->second;
|
||||
const auto found = saved.find({ zone, clone });
|
||||
world["saved"] = found == saved.end() ? json(nullptr) : found->second;
|
||||
// Names from the locale, in the viewer's language (read once at startup, so workers may read them)
|
||||
for (auto& model : world["models"]) model["name"] = ClientAssets::ItemName(model["lot"].get<LOT>());
|
||||
for (auto& event : world["events"]) {
|
||||
if (event.contains("lot")) event["name"] = ClientAssets::ItemName(event["lot"].get<LOT>());
|
||||
}
|
||||
}
|
||||
JsonSuccess(out, { {"worlds", worlds} });
|
||||
});
|
||||
});
|
||||
|
||||
Route(eHTTPMethod::GET, "/api/inspector/sessions/:id/worlds", Perm(PERMISSION),
|
||||
"Which world server each captured character was on, in time order: one entry per move to another zone or instance (zone 0 is character select)",
|
||||
[](HTTPReply& reply, const HTTPContext& context) {
|
||||
|
||||
Reference in New Issue
Block a user