Files
ReSkate/Extension/Settings/local_native_settings.cpp
T
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

152 lines
8.0 KiB
C++

#include "Engine/Core/Log/logging.h"
#include "Extension/Profile/runtime_internal.h"
#include "local_native_settings.h"
#include "Engine/Game/Build/addresses.h"
#include "Engine/Game/Build/20260929/engine.h"
#include "Engine/Game/Build/20260929/local_native_settings.h"
#include "Engine/Game/Build/20260929/named_settings.h"
#include "local_user_settings.h"
#include <optional>
#include <string_view>
#include <array>
namespace dingosdk::profile_runtime {
namespace native_types = addr::named_settings;
// Modern DingoUISettingAsset controls use DingoProfileSettingsManager's boxed
// option groups, independently of the legacy DelMar/ECS settings scripts.
NativeSettingsFunctions& native_settings() { static NativeSettingsFunctions f; return f; }
unsigned native_settings_location(std::uintptr_t group) {
auto& s = local_runtime();
if (!group || !s.active.load(std::memory_order_acquire)) return 0;
// Asked on every settings get the game's scripts make: peeked, no system call per read.
std::uintptr_t manager{}, begin{}, end{}, candidate{};
if (!memory::peek(s.base + addr::local_native_settings::profile_settings_manager, manager) || !manager ||
!memory::peek(manager, begin) || !memory::peek(manager + 8, end) ||
!begin || end < begin || end - begin != 3 * sizeof(std::uintptr_t)) return 0;
// LocalPerDevice (0) already has working native disk storage. Only the two
// cloud groups need the offline provider; retain their separate key spaces.
for (unsigned location : {1u, 2u})
if (memory::peek(begin + location * 8, candidate) && candidate == group) return location;
return 0;
}
// Runs on every settings get the game's scripts make: peeked, not read (one ReadProcessMemory
// call per read, ~0.15% of a multiplayer client frame, profiled 2026-10-02).
std::optional<dingosdk::Json> native_setting_json(const NativeSettingValue* input) {
NativeSettingValue value{};
if (!memory::peek(reinterpret_cast<std::uintptr_t>(input), value) || !value.data) return {};
const auto type = value.type - local_runtime().base;
switch (type) {
case native_types::native_bool: {
std::uint8_t byte{};
if (memory::peek(reinterpret_cast<std::uintptr_t>(value.data), byte) && byte <= 1) return byte != 0;
return {};
}
case native_types::native_uint32: return native_scalar<std::uint32_t>(value.data);
case native_types::native_int32: return native_scalar<std::int32_t>(value.data);
case native_types::native_float32: return native_scalar<float>(value.data);
case native_types::native_cstring: {
std::uintptr_t text{};
if (!memory::peek(reinterpret_cast<std::uintptr_t>(value.data), text)) return {};
char buffer[4097];
const auto length = memory::peek_cstring(text, buffer, sizeof(buffer));
if (length < 0) return {};
return std::string(buffer, static_cast<std::size_t>(length));
}
}
return {};
}
namespace {
// The saved option for a native setting key, or null: one store lookup per key and profile change,
// cached on the asking thread in a small direct-mapped table (valid until this thread's next call).
// A hit needs the same key address, location and first 8 key bytes; the key text is read only on
// a miss. May throw.
const dingosdk::Json* saved_native_option(unsigned location, const char* key) {
const auto address = reinterpret_cast<std::uintptr_t>(key);
std::uint64_t prefix{};
(void)memory::peek(address, prefix); // a short key at the end of a page stays 0: a miss refills
struct Entry { std::uintptr_t key{}; unsigned location{}; std::uint64_t prefix{}, changes{}; std::optional<dingosdk::Json> value; };
thread_local std::array<Entry, 256> cache;
const auto changes = profile::Store::changes();
auto& entry = cache[((address >> 3) ^ (address >> 11) ^ location) & 255];
if (entry.key != address || entry.location != location || entry.prefix != prefix || entry.changes != changes) {
char text[256];
const auto length = memory::peek_cstring(address, text, sizeof(text));
if (length <= 0) return nullptr;
for (std::ptrdiff_t i = 0; i < length; ++i) {
const auto c = static_cast<unsigned char>(text[i]);
if (c < 32 || c == 127) return nullptr;
}
entry.changes = 0;
entry.value = local_runtime().store->native_profile_option(location, std::string_view(text, static_cast<std::size_t>(length)));
entry.key = address;
entry.location = location;
entry.prefix = prefix;
entry.changes = changes;
}
return entry.value ? &*entry.value : nullptr;
}
}
bool restore_native_setting(std::uintptr_t group, const char* key, std::uintptr_t type,
const dingosdk::Json& saved) {
const auto base = local_runtime().base;
switch (type - base) {
case native_types::native_bool: return restore_native_scalar<bool>(group, key, type, saved);
case native_types::native_uint32: return restore_native_scalar<std::uint32_t>(group, key, type, saved);
case native_types::native_int32: return restore_native_scalar<std::int32_t>(group, key, type, saved);
case native_types::native_float32: return restore_native_scalar<float>(group, key, type, saved);
case native_types::native_cstring: {
if (!saved.is_string()) return false;
const auto& value = saved.string();
// CString cloning needs engine-owned storage, not a std::string buffer.
const char* text = reinterpret_cast<const char*>(base + addr::engine::empty_cstring);
user_values().assign_string(&text, value.c_str(), static_cast<std::uint32_t>(value.size()));
struct Release { const char** text; ~Release() { local_runtime().destroy_string(text); } } release{&text};
const NativeSettingValue borrowed{type, &text};
return native_settings().set(group, key, type, &borrowed);
}
}
return false;
}
const NativeSettingValue* native_setting_get(std::uintptr_t group, const char* key) {
auto* result = native_settings().get(group, key);
PreserveError preserve;
try {
const auto location = native_settings_location(group);
const auto* saved = location ? saved_native_option(location, key) : nullptr;
if (!saved) return result;
const auto current = native_setting_json(result);
if (!current || *current == *saved) return result;
// Copied before the restore: a get it causes on this thread reuses the cache.
const auto wanted = *saved;
std::string name;
(void)identifier(&key, name);
NativeSettingValue native{};
if (memory::peek(reinterpret_cast<std::uintptr_t>(result), native) && restore_native_setting(group, key, native.type, wanted))
dingosdk::logging::event(dingosdk::logging::Channel::settings, dingosdk::Json{{"event","local_native_setting_restored"},{"location",location},{"key",name}}.dump().c_str());
} catch (...) { dingosdk::logging::event(dingosdk::logging::Channel::settings, "{\"event\":\"local_native_setting_restore_failed\"}"); }
return result;
}
bool native_setting_set(std::uintptr_t group, const char* key, std::uintptr_t type, const NativeSettingValue* value) {
const bool changed = native_settings().set(group, key, type, value);
PreserveError preserve;
try {
const auto location = native_settings_location(group); std::string id;
if (changed && location && identifier(&key, id)) {
// Read back the accepted native value, including type validation.
if (const auto saved = native_setting_json(native_settings().get(group, key))) {
local_runtime().store->set_native_profile_option(location, id, *saved);
dingosdk::logging::event(dingosdk::logging::Channel::settings, dingosdk::Json{{"event","local_native_setting_saved"},{"location",location},{"key",id}}.dump().c_str());
}
}
} catch (...) { dingosdk::logging::event(dingosdk::logging::Channel::settings, "{\"event\":\"local_native_setting_save_failed\"}"); }
return changed;
}
}