Files
ReSkate/Launcher/gui_internal.h
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

358 lines
13 KiB
C++

#pragma once
#include "launch.h"
#include "text_encoding.h"
#include "thunderstore.h"
#include "Engine/Vfs/mod_list.h"
#include "Extension/UI/skate_theme.h"
#include <Windows.h>
#include <imgui.h>
#include <algorithm>
#include <array>
#include <atomic>
#include <condition_variable>
#include <cstdint>
#include <deque>
#include <filesystem>
#include <map>
#include <mutex>
#include <optional>
#include <string>
#include <string_view>
#include <thread>
#include <vector>
// Shared by the launcher window's files (gui*.cpp).
namespace dingosdk::launcher_gui::detail {
namespace fs = std::filesystem;
namespace update = launcher_update;
// ---------------------------------------------------------------- look
constexpr float design_width = 1180.0f;
constexpr float design_height = 680.0f;
inline ImU32 rgba(int r, int g, int b, float a = 1.0f) {
return IM_COL32(r, g, b, static_cast<int>(std::clamp(a, 0.0f, 1.0f) * 255.0f));
}
namespace color {
inline const ImU32 background_top = rgba(9, 11, 15);
inline const ImU32 background_bottom = rgba(17, 20, 27);
inline const ImU32 text = rgba(236, 239, 244);
inline const ImU32 muted = rgba(128, 137, 151);
inline const ImU32 danger = rgba(255, 92, 92);
inline const ImU32 panel = rgba(26, 26, 26, 0.97f);
// Sampled from skate.'s own menus (HUB screen).
inline const ImU32 tile = skate_theme::tile;
inline const ImU32 tile_grey = skate_theme::tile_light;
inline const ImU32 blue = skate_theme::blue;
inline const ImU32 good = skate_theme::good;
inline const ImU32 warning = skate_theme::warning;
inline const ImU32 avatar = skate_theme::avatar;
inline const ImU32 ink = skate_theme::black;
inline const ImU32 outline = rgba(255, 255, 255, 0.12f);
}
// Blue button with black text, the colours of skate.'s selected tile.
using skate_theme::push_primary_button;
using skate_theme::pop_primary_button;
struct Fonts {
ImFont* body{}; // Montserrat SemiBold
ImFont* caption{};
ImFont* bold{}; // Montserrat ExtraBold
ImFont* heading{};
ImFont* tile{}; // tile headers, like the HUB's "BOUNTIES"
ImFont* action{}; // the PLAY tile
ImFont* title{}; // brushed page title, like the HUB's "HUB"
};
inline float g_scale = 1.0f;
inline float S(float value) { return value * g_scale; }
// Optional photo behind the launcher; zero id means the drawn background.
struct Background {
ImTextureID id{};
float width{}, height{};
};
inline Background g_background;
// White tile icons (assets/launcher/icon_*.png), drawn faded like the HUB's.
inline Background g_icon_mods, g_icon_settings;
using launcher_text::utf8;
using launcher_text::wide;
class Renderer;
// The window's renderer, for textures made after startup (package icons).
inline Renderer* g_renderer{};
void panel_title(const Fonts& fonts, const char* text);
// A centred modal panel; returns its size.
ImVec2 begin_panel(const char* id, ImVec2 size, ImVec2 panel);
void open_path(const fs::path& path);
// Opens an https:// page in the default browser; anything else is ignored.
void open_url(std::string_view url);
// ---------------------------------------------------------------- window
inline bool g_drag_allowed = true;
// Set while Settings waits for a key to bind; the window procedure stores the
// next key press here instead of letting ImGui see it.
inline std::atomic<bool> g_capturing_key{};
inline std::atomic<unsigned> g_captured_key{};
// Paths dropped on the window, picked up by the next frame.
inline std::mutex g_dropped_mutex;
inline std::vector<fs::path> g_dropped;
// ---------------------------------------------------------------- settings
struct Settings {
bool windowed{};
int width{1920};
int height{1080};
bool loose_files{true};
bool gpu_diagnostics{};
bool offline{};
int menu_key{static_cast<int>(launcher::default_menu_key)};
int console_key{static_cast<int>(launcher::default_console_key)};
int log_level{2};
std::string arguments;
// Off: never replace ReSkate.dll or the launcher (keeps a test build someone handed out).
bool updates{true};
// Off: a crash uploads nothing (RESKATE_CRASH_REPORTING=0 for the launcher and the game).
bool crash_reports{true};
std::string steam_username; // never the password
bool steam_remember{true};
bool steam_prefer_code{};
};
inline constexpr std::array<const char*, 7> log_levels{"trace", "debug", "info", "warning", "error", "critical", "off"};
// ---------------------------------------------------------------- state
enum class Phase { checking, update_available, updating, game_missing, game_outdated, downloading,
ready, launching, failed };
struct State {
Phase phase{Phase::checking};
std::string status{"Checking for updates"};
std::string detail;
float progress{-1};
std::vector<std::string> qr;
std::optional<update::Prompt> prompt;
std::optional<update::Config> config;
};
// Update checks, the Steam download and the game launch, one at a time on a worker thread.
class Launcher {
public:
Launcher(const launcher_app::Session& session, std::vector<std::wstring> arguments);
~Launcher();
State snapshot();
bool busy() const { return busy_; }
bool restart_requested() const { return restart_; }
// Process id of the Skate this launcher started, once it exists.
DWORD game() const { return game_; }
// Injection finished and the game is running on its own; the launcher closes
// as soon as this is true.
bool launched() const { return launched_; }
Settings& settings() { return settings_; }
const launcher_app::Session& session() const { return session_; }
void save();
void check();
void apply_updates();
// `qr` signs in with a QR code; otherwise the saved Steam username is used.
// The password only lives in memory until DepotDownloader asks for it; it
// may be empty when DepotDownloader remembers the login.
void download(bool validate, bool qr, std::string password);
void play();
void cancel();
// Answers the pending Steam prompt; nothing cancels the download.
void answer(std::optional<std::string> value);
void restart();
private:
launcher_app::Session session_;
std::vector<std::wstring> arguments_;
Settings settings_;
bool relaunched_{};
bool binaries_{true};
std::mutex mutex_;
std::condition_variable answered_;
State state_;
std::string password_;
bool qr_login_{true};
std::optional<std::string> answer_;
bool has_answer_{};
std::thread worker_;
std::atomic<bool> busy_{};
std::atomic<bool> cancel_{};
std::atomic<bool> restart_{};
std::atomic<DWORD> game_{};
std::atomic<bool> launched_{};
template<class Task> void start(Task task);
static void wipe(std::string& value);
std::optional<std::string> prompt(const update::Prompt& prompt);
void set(Phase phase, std::string status, std::string detail = {}, float progress = -1);
void set_progress(std::string detail, float progress);
void fail(const std::string& message);
std::optional<update::Config> config();
update::Progress progress_for(std::string label);
bool launcher_outdated(const update::Config& config) const;
bool runtime_outdated(const update::Config& config) const;
void run_check();
void run_updates();
void run_download(bool validate);
void run_play();
};
// Panels the main screen can show; one at a time.
struct Ui {
bool settings{};
int settings_tab{}; // GAME, DISPLAY, KEYS, ADVANCED
int binding{}; // 1 = menu key, 2 = console key, while waiting for a press
std::string key_error;
bool mods{};
bool sign_in{};
bool sign_in_validate{};
bool focus{};
std::array<char, 65> username{};
std::array<char, 256> password{};
std::array<char, 16> code{};
// The MODS tile's "2 of 3 enabled", re-read every few seconds.
std::string mods_detail;
double mods_checked{-100};
// Steam display name for the name plate, re-read every few seconds.
std::string steam_name;
double steam_checked{-100};
// One-time notice that Skate will start offline because Steam is not
// running or not signed in. Decided on the first Steam check.
bool steam_offline{};
bool steam_offline_seen{};
};
// The Thunderstore listing, fetched in the background when the launcher
// starts and again on Refresh. `packages` belongs to the UI thread; the worker
// hands a finished fetch over through `incoming`.
struct Store {
std::thread worker;
std::atomic<bool> loading{};
std::atomic<bool> stop{};
std::mutex mutex;
bool arrived{}; // worker -> UI, under mutex
std::vector<thunderstore::Package> incoming;
std::string incoming_error;
bool loaded{}; // a listing arrived (it may be empty)
std::string error; // why the last fetch failed
std::vector<thunderstore::Package> packages;
double fetched{-1e9}; // ImGui time the last fetch started
// The BROWSE page.
std::array<char, 96> search{};
std::string category; // empty = all
int sort{};
std::string selected; // full_name
~Store() {
stop = true;
if (worker.joinable()) worker.join();
}
};
// Package icons: fetched and decoded on a worker (cached on disk under
// %LOCALAPPDATA%\ReSkate\thunderstore\icons), uploaded on the UI thread.
struct Icons {
struct Entry { ImTextureID id{}; bool queued{}; bool failed{}; int used{}; };
std::map<std::string, Entry, std::less<>> entries; // by icon URL; UI thread
struct Decoded { std::string url; std::vector<unsigned char> pixels; unsigned width{}, height{}; };
std::thread worker;
std::mutex mutex;
std::condition_variable wake;
std::deque<std::string> queue;
std::vector<Decoded> done;
bool stop{};
~Icons() {
{ std::lock_guard lock(mutex); stop = true; }
wake.notify_all();
if (worker.joinable()) worker.join();
}
};
// The Mods panel's list plus one background install at a time.
struct ModsPanel {
bool scanned{};
fs::path root;
mods::ModList list;
int selected{-1};
int tab{}; // 0 INSTALLED, 1 BROWSE
std::string message;
bool message_error{};
std::string confirm_remove; // folder awaiting "Remove" confirmation
fs::path conflict_source; // install waiting for "Replace" confirmation
std::string conflict_name;
std::thread worker;
std::atomic<bool> installing{};
std::atomic<bool> cancel{};
std::atomic<float> progress{-1};
std::mutex mutex;
std::string activity; // what the worker is doing, under mutex
bool finished{};
std::string finished_name, finished_error, finished_conflict, finished_note;
fs::path finished_source;
Store store;
Icons icons;
~ModsPanel() {
cancel = true;
if (worker.joinable()) worker.join();
}
};
void scan(ModsPanel& panel, const launcher_app::Session& session);
void start_install(ModsPanel& panel, const fs::path& source, bool replace);
// ---------------------------------------------------------------- Thunderstore (gui_mods_browse.cpp)
// Starts a listing fetch when none ran yet, or when `force`.
void refresh_listing(ModsPanel& panel, double time, bool force = false);
// Takes over a fetch the worker finished; call once a frame.
void collect_listing(ModsPanel& panel);
thunderstore::Installed installed_versions(const mods::ModList& list);
// The package an installed mod folder came from, if the listing has it.
const thunderstore::Package* package_for(const Store& store, std::string_view folder);
std::vector<const thunderstore::Package*> updates(const Store& store, const thunderstore::Installed& installed);
// Downloads and installs (or updates) each package in turn on the panel's worker.
void start_store_install(ModsPanel& panel, std::vector<thunderstore::Package> packages);
// The package's icon texture, or empty while it loads; uploads finished icons.
ImTextureID package_icon(ModsPanel& panel, const thunderstore::Package& package);
void pump_icons(ModsPanel& panel);
// The BROWSE page body, between the tabs and the footer.
void browse_page(const Fonts& fonts, ModsPanel& panel, float height, bool installing);
void open_sign_in(Launcher& launcher, Ui& ui, bool validate);
void sign_in_window(Launcher& launcher, const Fonts& fonts, ImVec2 size, Ui& ui);
void prompt_window(Launcher& launcher, const Fonts& fonts, ImVec2 size, const update::Prompt& prompt, Ui& ui);
void qr_window(Launcher& launcher, const Fonts& fonts, ImVec2 size, const std::vector<std::string>& rows);
void steam_offline_window(const Fonts& fonts, ImVec2 size, Ui& ui);
void settings_window(Launcher& launcher, const Fonts& fonts, ImVec2 size, Ui& ui);
void mods_window(Launcher& launcher, const Fonts& fonts, ImVec2 size, Ui& ui, ModsPanel& panel, HWND window);
// The main screen: background, tiles, status and whichever panel is open.
void frame(Launcher& launcher, const Fonts& fonts, HWND window, Ui& ui, ModsPanel& mods_panel);
} // namespace dingosdk::launcher_gui::detail