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

181 lines
8.5 KiB
C++

#include "gui_internal.h"
#include <cmath>
#include <cstdint>
#include <span>
// The Steam panels: sign-in, password/Steam Guard prompts and the QR code.
namespace dingosdk::launcher_gui::detail {
namespace {
void wipe(std::span<char> buffer) { SecureZeroMemory(buffer.data(), buffer.size()); }
} // namespace
void open_sign_in(Launcher& launcher, Ui& ui, bool validate) {
ui.sign_in = true;
ui.sign_in_validate = validate;
ui.focus = true;
wipe(ui.password);
ui.username.fill(0);
const auto& saved = launcher.settings().steam_username;
std::copy_n(saved.begin(), std::min(saved.size(), ui.username.size() - 1), ui.username.begin());
}
void sign_in_window(Launcher& launcher, const Fonts& fonts, ImVec2 size, Ui& ui) {
const auto panel = begin_panel("##sign_in_panel", size, ImVec2(S(480), S(520)));
auto& settings = launcher.settings();
panel_title(fonts, "SIGN IN TO STEAM");
ImGui::PushTextWrapPos(0);
ImGui::TextDisabled("skate. is downloaded from Steam with DepotDownloader, so the account must own it. "
"Your password is passed straight to Steam and never saved.");
ImGui::PopTextWrapPos();
ImGui::Spacing();
const auto start = [&](bool qr) {
if (!qr) settings.steam_username = ui.username.data();
launcher.save();
launcher.download(ui.sign_in_validate, qr, qr ? std::string() : std::string(ui.password.data()));
wipe(ui.password);
ui.sign_in = false;
};
push_primary_button();
const bool qr = ImGui::Button("SCAN A QR CODE WITH THE STEAM APP", ImVec2(-1, S(46)));
pop_primary_button();
if (qr) start(true);
ImGui::Spacing();
ImGui::PushFont(fonts.caption);
ImGui::TextDisabled("OR SIGN IN WITH YOUR ACCOUNT");
ImGui::PopFont();
if (ui.focus) { ImGui::SetKeyboardFocusHere(); ui.focus = false; }
ImGui::SetNextItemWidth(-1);
ImGui::InputTextWithHint("##username", "Steam account name", ui.username.data(), ui.username.size(),
ImGuiInputTextFlags_CharsNoBlank);
ImGui::SetNextItemWidth(-1);
const bool entered = ImGui::InputTextWithHint("##password", "Password (empty if remembered)", ui.password.data(),
ui.password.size(), ImGuiInputTextFlags_Password | ImGuiInputTextFlags_EnterReturnsTrue);
ImGui::Checkbox("Remember me on this PC", &settings.steam_remember);
ImGui::Checkbox("Type a Steam Guard code instead of approving in the app", &settings.steam_prefer_code);
const std::string_view username(ui.username.data());
const bool valid = !username.empty() && username.find_first_not_of(
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_") == std::string_view::npos;
ImGui::SetCursorPosY(panel.y - S(24) - ImGui::GetFrameHeight());
if (ImGui::Button("CANCEL", ImVec2(S(110), 0))) { wipe(ui.password); ui.sign_in = false; }
ImGui::SameLine(panel.x - S(28) - S(130));
ImGui::BeginDisabled(!valid);
push_primary_button();
const bool sign_in = ImGui::Button("SIGN IN", ImVec2(S(130), 0));
pop_primary_button();
if (sign_in || (entered && valid)) start(false);
ImGui::EndDisabled();
ImGui::End();
}
void prompt_window(Launcher& launcher, const Fonts& fonts, ImVec2 size, const update::Prompt& prompt, Ui& ui) {
const auto panel = begin_panel("##prompt_panel", size, ImVec2(S(440), S(300)));
const bool password = prompt.kind == update::PromptKind::password;
panel_title(fonts, password ? "STEAM PASSWORD" : "STEAM GUARD");
ImGui::PushTextWrapPos(0);
if (prompt.retry) ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(color::danger), "That code was incorrect. Try again.");
switch (prompt.kind) {
case update::PromptKind::password:
ImGui::TextDisabled("Enter the password for %s.", launcher.settings().steam_username.c_str());
break;
case update::PromptKind::authenticator_code:
ImGui::TextDisabled("Enter the code shown in the Steam app on your phone (Steam Guard).");
break;
case update::PromptKind::email_code: {
const auto at = prompt.text.find("email at ");
ImGui::TextDisabled("Enter the code Steam emailed to %s.",
at == std::string::npos ? "you" : prompt.text.c_str() + at + 9);
break;
}
}
ImGui::PopTextWrapPos();
ImGui::Spacing();
if (ImGui::IsWindowAppearing()) ImGui::SetKeyboardFocusHere();
ImGui::SetNextItemWidth(-1);
bool submit = false;
if (password) {
submit = ImGui::InputText("##prompt_password", ui.password.data(), ui.password.size(),
ImGuiInputTextFlags_Password | ImGuiInputTextFlags_EnterReturnsTrue);
} else {
ImGui::PushFont(fonts.heading);
submit = ImGui::InputTextWithHint("##prompt_code", "XXXXX", ui.code.data(), ui.code.size(),
ImGuiInputTextFlags_CharsUppercase | ImGuiInputTextFlags_CharsNoBlank | ImGuiInputTextFlags_EnterReturnsTrue);
ImGui::PopFont();
}
const char* value = password ? ui.password.data() : ui.code.data();
ImGui::SetCursorPosY(panel.y - S(24) - ImGui::GetFrameHeight());
if (ImGui::Button("CANCEL", ImVec2(S(110), 0))) {
wipe(ui.password);
ui.code.fill(0);
launcher.answer(std::nullopt);
}
ImGui::SameLine(panel.x - S(28) - S(130));
ImGui::BeginDisabled(!*value);
push_primary_button();
const bool proceed = ImGui::Button("CONTINUE", ImVec2(S(130), 0));
pop_primary_button();
if ((proceed || submit) && *value) {
launcher.answer(std::string(value));
wipe(ui.password);
ui.code.fill(0);
}
ImGui::EndDisabled();
ImGui::End();
}
void qr_window(Launcher& launcher, const Fonts& fonts, ImVec2 size, const std::vector<std::string>& rows) {
std::size_t first = SIZE_MAX, last = 0;
for (const auto& row : rows) {
const auto begin = row.find('#'), end = row.rfind('#');
if (begin != std::string::npos) { first = std::min(first, begin); last = std::max(last, end); }
}
if (first == SIZE_MAX) return;
const float columns = static_cast<float>(last - first + 1);
const float module = std::floor(S(230) / columns);
const float code = module * columns;
const float quiet = module * 3;
const auto panel = begin_panel("##qr_panel", size, ImVec2(S(440), code + quiet * 2 + S(200)));
panel_title(fonts, "SCAN WITH STEAM");
ImGui::PushTextWrapPos(0);
ImGui::TextDisabled("Open the Steam app on your phone, tap the shield (Steam Guard) and scan this code. "
"The account must own skate.");
ImGui::PopTextWrapPos();
auto* draw = ImGui::GetWindowDrawList();
const auto window = ImGui::GetWindowPos();
const ImVec2 origin(window.x + (panel.x - code - quiet * 2) * 0.5f, ImGui::GetCursorScreenPos().y + S(8));
draw->AddRectFilled(origin, ImVec2(origin.x + code + quiet * 2, origin.y + module * static_cast<float>(rows.size()) + quiet * 2),
rgba(255, 255, 255));
for (std::size_t y = 0; y < rows.size(); ++y)
for (std::size_t x = first; x <= last && x < rows[y].size(); ++x)
if (rows[y][x] == '#') {
const ImVec2 cell(origin.x + quiet + static_cast<float>(x - first) * module,
origin.y + quiet + static_cast<float>(y) * module);
draw->AddRectFilled(cell, ImVec2(cell.x + module, cell.y + module), rgba(0, 0, 0));
}
ImGui::SetCursorPosY(panel.y - S(24) - ImGui::GetFrameHeight());
ImGui::SetCursorPosX(panel.x - S(28) - S(110));
if (ImGui::Button("CANCEL", ImVec2(S(110), 0))) launcher.cancel();
ImGui::End();
}
// Shown once when the launcher opens without Steam, so nobody reaches the main
// menu as Unknown Player wondering why multiplayer is missing.
void steam_offline_window(const Fonts& fonts, ImVec2 size, Ui& ui) {
const auto panel = begin_panel("##steam_offline_panel", size, ImVec2(S(440), S(300)));
panel_title(fonts, "STEAM IS NOT RUNNING");
ImGui::PushTextWrapPos(0);
ImGui::TextDisabled("Steam is not running, or no account is signed in. PLAY still works, but Skate starts in "
"offline mode: you play as Unknown Player and multiplayer is hidden.");
ImGui::Spacing();
ImGui::TextDisabled("To play as your own account, start Steam, sign in, and open ReSkate again.");
ImGui::PopTextWrapPos();
ImGui::SetCursorPosY(panel.y - S(24) - ImGui::GetFrameHeight());
ImGui::SetCursorPosX(panel.x - S(28) - S(110));
if (ImGui::Button("OK", ImVec2(S(110), 0))) ui.steam_offline = false;
ImGui::End();
}
} // namespace dingosdk::launcher_gui::detail