Files
AyeZeeBB b763631b77 Trim per-frame multiplayer client work; size mesh pool to the GPU
Mesh streaming pool (fixes loading crashes on older GPUs)
- 1.0.0 forced MeshStreaming.PoolSize to 3.5 GiB on every card. An RX 580
  crashed while loading with DXGI_ERROR_DEVICE_RESET, and the oversized pool
  is the likely cause. The pool is now a quarter of the largest hardware
  adapter's dedicated video memory, capped at 3.5 GiB; cards of 6 GB or less
  keep the game's own 520 MB. The card, its memory and the chosen size are
  logged once at startup.

Remote players (busy servers)
- Transform interpolate is inline plain arithmetic: 22 -> 5.8 ns per joint.
  It was 4% of a busy server's client frame, since every player within
  200 m has the whole skeleton interpolated every frame.
- Players out of the camera's view and more than 8 m away are sampled at
  20 Hz, like far players. Their position still updates every frame for
  collision, sound and nametags, and a player coming into view is sampled
  that frame.

Expression hooks (run for every expression the game executes)
- No profiler zones in the expression hooks: taking a sample switched them
  on, inflating the very samples that measured the hooks.
- The throwdown lab pump does one sorted lookup of the running graph and
  skips everything else for graphs it doesn't watch (graph_throttle gets
  throttled_graph for this).

Settings and memory reads
- Script gameplay-setting and native-setting gets are served from small
  per-thread caches, keyed on the asset/key address, its first 8 name
  bytes and a new profile::Store::changes() counter, instead of a store
  lookup each call.
- Per-frame reads in the client tick, native settings and population
  controls use memory::peek instead of ReadProcessMemory.
- Script error logging probes each argument pointer once.
2026-10-02 21:07:43 -05:00

80 lines
4.0 KiB
C++

#include "script_natives.h"
#include "Engine/Core/Log/logging.h"
#include "Engine/Game/Build/addresses.h"
#include "Engine/Game/Build/20260929/script_natives.h"
#include <mutex>
#include <set>
#include <string>
#include "local_challenge_trace.h"
#include "local_entitlement_trigger_runtime.h"
namespace dingosdk::profile_runtime {
namespace {
void script_error(std::uintptr_t, std::uintptr_t*, std::uintptr_t* arguments) {
PreserveError preserve;
// Authored graphs raise the same error every frame, from the same constant text: a text
// pointer already looked at is not read again (each character was a system call).
static std::mutex mutex; static std::set<std::string> seen; static std::set<std::uintptr_t> looked_at, arguments_seen;
std::lock_guard lock(mutex);
try {
// The message position is not known from the binding alone; take the
// first argument that dereferences to readable text.
const auto printable = [](std::uintptr_t at) {
std::string value;
if (looked_at.contains(at)) return value;
if (looked_at.size() < 4096) looked_at.insert(at);
for (std::size_t i = 0; at && i < 400; ++i) {
char c{};
if (!memory::peek(at + i, c) || !c) break; // no system call per character
if (static_cast<unsigned char>(c) < 9 || static_cast<unsigned char>(c) > 126) return std::string{};
value += static_cast<unsigned char>(c) < 32 ? ' ' : c;
}
return value.size() >= 8 ? value : std::string{};
};
std::string message;
for (std::size_t index = 0; arguments && index < 4 && message.empty(); ++index) {
std::uintptr_t argument{}, inner{};
if (!memory::peek(reinterpret_cast<std::uintptr_t>(arguments) + index * 8, argument) || argument < 0x10000) break;
// The same arguments come back every frame: each is probed once (read, not peeked:
// it is often not a pointer, and a failed peek costs an exception).
if (arguments_seen.contains(argument)) continue;
if (arguments_seen.size() < 4096) arguments_seen.insert(argument);
if (read(argument, inner) && inner >= 0x10000) message = printable(inner);
if (message.empty()) message = printable(argument);
}
if (message.empty()) return;
std::uintptr_t resource{}; std::uint32_t graph{};
if (executing_expression) challenge_trace_identity(executing_expression, resource, graph);
// One line per distinct message; authored graphs repeat theirs per frame.
if (seen.size() < 256 && seen.insert(message).second)
logging::log(logging::Level::info, logging::Channel::progression,
"Script error (graph {:#x}): {}", graph, message);
} catch (...) {}
}
}
void install_script_error_log(std::uintptr_t base) noexcept {
static bool attempted{};
if (attempted || !base) return;
attempted = true;
constexpr std::uintptr_t record = addr::script_natives::script_error_record, stub = addr::script_natives::script_error_stub;
try {
std::uint32_t hash{};
if (!read(base + record, hash) || hash != 0xa5910db3) return;
unsigned patched{};
for (const auto offset : {0x30u, 0x40u, 0x48u}) {
std::uintptr_t current{};
const auto slot = base + record + offset;
if (!read(slot, current) || current != base + stub) continue;
DWORD old{};
if (!VirtualProtect(reinterpret_cast<void*>(slot), sizeof(current), PAGE_READWRITE, &old)) continue;
*reinterpret_cast<std::uintptr_t*>(slot) = reinterpret_cast<std::uintptr_t>(&script_error);
VirtualProtect(reinterpret_cast<void*>(slot), sizeof(current), old, &old);
++patched;
}
logging::log(logging::Level::info, logging::Channel::progression,
"Script error log: {} binding slot(s) redirected.", patched);
} catch (...) {}
}
}