Two hitches on the game thread, and profiler names without symbols

A 30 s profile on a 77-player server had four frames of 20 to 30 ms against a
usual 7.8 ms, from two things:

- The Park Editor read every mod's manifest every five seconds to list park
  mods, on the game's thread and whether or not its list was showing (the
  switch for that is never set): about 11 ms each time. The look every few
  seconds now runs on a thread of its own and is taken up when it is done;
  the one after making, renaming or removing a park is still at once.
- Connecting to a player asked Steam for their name, and waited on the Steam
  client for 7 to 10 ms each time a player came into reach. The link takes
  the name already read, if any: it only labels the link in diagnostics.

Profiler: a release ReSkate.dll has no symbols, and its functions were all
named after the nearest export. The check for that looked at the symbol's
size, but DbgHelp gives an export a size up to the next one. It now asks
whether the module's own symbols were loaded, and writes ReSkate.dll+0x<offset>
when they were not, to be named from the release's symbols afterwards
This commit is contained in:
AyeZeeBB committed 2026-10-11 14:50:27 -05:00
1 parent 314d31fe7b
commit 1b15a04e29
3 files changed
+60 -14

No files matched your search

+11 -6
View File
@@ -202,14 +202,19 @@ std::string symbol_name(const Module& module, std::uintptr_t address) {
info->MaxNameLen = 511;
DWORD64 displacement{};
if (symbols_ready && SymFromAddr(symbol_session, address, &displacement, info) && info->Name[0]) {
// A release ReSkate.dll ships without its symbols, so all this finds in it is the nearest
// export, megabytes away: every function of ours then reads as one of three exports and
// a report cannot say which was slow. Unless the address is inside a symbol with a size
// (the real ones have one), give our own module's address instead: the release's symbols
// (build/release/ReSkate-<version>-symbols) name it afterwards.
// A release ReSkate.dll ships without its symbols (and a ReSkate.pdb left beside it from
// another build is not taken), so all this finds in it is the nearest export, megabytes
// away: every function of ours then reads as one of three exports and a report cannot
// say which was slow. An export is given a size too, up to the next one, so the size
// tells nothing: ask what the names came from. Without the module's own symbols, give
// its address instead: the release's symbols (build/release/ReSkate-<version>-symbols)
// name it afterwards.
const auto here = reinterpret_cast<std::uintptr_t>(&waiting_marker);
const bool own = module.base <= here && here < module.end;
if (!own || (info->Size && displacement < info->Size)) return module.name + "!" + info->Name;
IMAGEHLP_MODULEW64 loaded{};
loaded.SizeOfStruct = sizeof(loaded);
const bool named = !own || (SymGetModuleInfoW64(symbol_session, module.base, &loaded) && loaded.SymType == SymPdb);
if (named) return module.name + "!" + info->Name;
}
return std::format("{}+0x{:x}", module.name, address - module.base);
}
@@ -656,8 +656,12 @@ bool SteamTransport::connect_peer(std::uint64_t id) {
const auto connection =
p.connect(p.sockets, &identity, virtual_port, static_cast<int>(options.size()), options.data());
p.closed.erase(id);
// The link's name only labels it in diagnostics: the one already read, if any, and poll
// fills it in. Asking Steam here waited on the Steam client for ten milliseconds and more,
// a hitch each time a player came into reach.
const auto known = p.names.find(id);
if (connection)
p.links.emplace(id, Impl::Link{connection, GetTickCount64(), false, name(id),
p.links.emplace(id, Impl::Link{connection, GetTickCount64(), false, known != p.names.end() ? known->second.text : std::string{},
p.lanes.setup(p.sockets, connection)});
p.publish_links();
return connection != 0;
@@ -10,6 +10,8 @@
#include "Extension/Objects/network_object_runtime.h"
#include "park_document.h"
#include "park_mods.h"
#include <chrono>
#include <future>
namespace dingosdk::profile_runtime {
namespace park_editor_detail {
@@ -30,17 +32,53 @@ const profile::PlacedObject *find_object(const profile::ObjectLayout &objects, s
return it == objects.end() ? nullptr : &*it;
}
std::atomic<bool> park_mod_list_visible{true};
void refresh_park_mods() {
auto &e = editor_state();
e.next_park_scan = GetTickCount64() + 5000;
// What is in the Mods folder: every mod's manifest is read to find the ones with parks, which
// with many mods takes ten milliseconds and more.
struct ParkScan {
std::vector<editor::ParkMod> mods;
std::vector<std::string> legacy;
};
ParkScan scan_parks(const std::filesystem::path &data_root, const std::filesystem::path &directory) {
ParkScan found;
try {
e.park_mods = e.data_root.empty() ? std::vector<editor::ParkMod>{} : editor::list_park_mods(e.data_root);
e.legacy_parks = e.directory.empty() ? std::vector<std::string>{} : editor::list_parks(e.directory);
if (!data_root.empty()) found.mods = editor::list_park_mods(data_root);
if (!directory.empty()) found.legacy = editor::list_parks(directory);
} catch (...) { /* An unreadable Mods folder just lists nothing. */ }
return found;
}
void use_park_scan(ParkScan found) {
auto &e = editor_state();
e.park_mods = std::move(found.mods);
e.legacy_parks = std::move(found.legacy);
if (!e.project.empty() && std::none_of(e.park_mods.begin(), e.park_mods.end(),
[&](const auto &mod) { return mod.folder == e.project; }))
e.project.clear(); // The project was removed or renamed outside the game.
}
// The look every few seconds runs on a thread of its own and is taken up when it is done
// (poll_park_mods): on the game's thread it was a hitch every five seconds. `park_scans` counts
// the looks asked for, so one that a later look has overtaken is dropped.
std::future<ParkScan> park_scan;
std::uint64_t park_scans{}, park_scan_running{};
// At once: after the player made, renamed or removed a park, when the list has to be right.
void refresh_park_mods() {
auto &e = editor_state();
e.next_park_scan = GetTickCount64() + 5000;
++park_scans;
use_park_scan(scan_parks(e.data_root, e.directory));
}
void poll_park_mods() {
auto &e = editor_state();
if (park_scan.valid()) {
if (park_scan.wait_for(std::chrono::seconds(0)) != std::future_status::ready) return;
auto found = park_scan.get();
if (park_scan_running == park_scans) use_park_scan(std::move(found));
return;
}
if (GetTickCount64() < e.next_park_scan) return;
e.next_park_scan = GetTickCount64() + 5000;
park_scan_running = ++park_scans;
park_scan = std::async(std::launch::async, [data_root = e.data_root, directory = e.directory] { return scan_parks(data_root, directory); });
}
// Multiplayer guests play on the host's objects; only the host swaps layouts.
bool is_lobby_guest() {
return placements_runtime().personal_document.has_value();
@@ -603,8 +641,7 @@ ParkEditorModel local_park_editor() {
model.can_undo = idle() && !e.undo.empty();
model.can_redo = idle() && !e.redo.empty();
model.assets = e.assets;
if (park_mod_list_visible.load(std::memory_order_relaxed) && GetTickCount64() >= e.next_park_scan)
refresh_park_mods();
if (park_mod_list_visible.load(std::memory_order_relaxed)) poll_park_mods();
for (const auto &mod : e.park_mods)
model.park_mods.push_back({mod.folder, mod.details.title, mod.details.author, mod.details.version,
mod.details.description, mod.enabled,