mirror of
https://github.com/Dingo-Shenanigans/ReSkate.git
synced 2026-10-24 12:26:20 +02:00
Players reported the game hanging for about a second while skating. A profile showed the memory line in the log walking every heap (HeapSummary) on the client thread, 0.5 to 2 s every two minutes. An audit of everything that runs on the game's update and render threads found more of the same, and Sentry's top crashes had causes of ours. Freezes: - Memory log line: no heap walk or thread snapshot; the GPU part and the write happen on a thread of their own. - Profile database: synchronous=NORMAL in WAL mode. A save is still kept when the game crashes; every play event no longer waits for the disk inside the game's hooks. - ReSkate's own preferences and values (menu clicks, session sliders, Discord) are saved by a background writer in order; reads see a value until it is saved. - Input: the foreground window and the keyboard are sampled on a thread of their own (GetAncestor and GetAsyncKeyState stalled the client tick 40-50 ms when another program held Windows' lock); flight input uses the sample; ClipCursor at most twice a second while a menu has the pointer. - Hooks installed while the game runs go in one transaction per place (HookBatch): throwdowns, remote players, the party, Hall of Meat. - Off the update thread: the host's map package lookup (one table read in the background, not the Mods folder per offer), the trainer's map file and settings save, Board Break's settings save, mod music playlists and their covers, the Spotify station covers. - Off the render thread: the Modding page's and the deck card's Mods folder reads. The overlay's three startup waits share five seconds. - Less work per frame: Steam names reused while one a frame is asked again, 2 friends a tick, steam_api64.dll checked once per process, cosmetic names read in place rather than a system call per character, an outfit load no longer copies the inventory per slot, the throwdown type scan runs at background priority over resident memory, the Board Break deck label is kept a minute. Crashes: - Render-resource pool: the metadata fields are written to the state owner only. The other renderer is 0x2daa00 bytes, and +0x11aae28 is far past its end (store_field crash; heap corruption in every release). - Other players' skateboard effects: a stale skateboard component is no longer followed, and the joint lookup is walked with a bound first (an endless loop in reused memory ended in GameStall). - Launcher: Oodle decodes into the buffer it asks for with corrupt data possible, and its assertions no longer stop the process (heap corruption in the store-copy scan; a breakpoint in oo2core). Timings (Send performance timings): - A frame of half a second or more in a level is sent as game.freeze: where the time went (ReSkate's tick, the game's frame, or outside it), whether the renderer kept presenting, and ReSkate's busiest zones. - Closing the game sends the level session it was in. Music after a connection drop: - Spotify device: an account Spotify could not be reached with is kept and tried again (3 s doubling to a minute); a session that drops is taken up again. Only an account Spotify refuses is asked for anew. - YouTube playlist pictures that could not be fetched are tried again, from 30 s to 10 minutes apart.
67 lines
3.7 KiB
C++
67 lines
3.7 KiB
C++
#pragma once
|
|
#include "Extension/Multiplayer/Session/session.h"
|
|
#include <cstdint>
|
|
#include <string>
|
|
#include <string_view>
|
|
|
|
// A map a multiplayer session needs and this PC does not have, fetched from Thunderstore
|
|
// while the game runs. The host says which package its map is from (session.h: MapNeed). The
|
|
// player is asked; on yes the package is downloaded, installed into Mods and applied as a live
|
|
// mod (live_mods.h). The session stays connected meanwhile and loads the map once it is there;
|
|
// on no, or when the map cannot be had, the session is left.
|
|
// The session waits 30 minutes on its local monotonic clock for the whole of it (checking,
|
|
// confirmation, download, installation, applying and joining); confirmation gets two of them.
|
|
// A download that has begun is not cut short when the session stops waiting: it finishes,
|
|
// installs and is applied, and the player is told to join again. A cancelled worker drains
|
|
// before another attempt can begin.
|
|
//
|
|
// Only maps come in this way. The package has to be listed as a map in ReSkate's Thunderstore
|
|
// community, not deprecated and not rejected by its moderators; what is installed has to be a
|
|
// map and nothing else (level bundles and their registration: no cosmetics, no settings, no
|
|
// scripts), or it is taken out again. Everything is downloaded from thunderstore.io, whatever
|
|
// a host says.
|
|
namespace dingosdk::map_download {
|
|
enum class Stage {
|
|
idle,
|
|
checking, // asking Thunderstore about the package; nothing is shown yet
|
|
asking, // the card asks: download, or not
|
|
downloading,
|
|
installing,
|
|
applying, // the live merge
|
|
joining, // the map is in the game: the session is loading it (or is joined again)
|
|
};
|
|
struct View {
|
|
Stage stage{};
|
|
std::string map, package; // "Vancouver Plaza", "Sandos-Vancouver_Plaza 2.0.0"
|
|
std::string author, version, description; // "Sandos", "2.0.0", and what its Thunderstore page says of it
|
|
// applying: the live merge's steps done of all, and the one in hand.
|
|
std::size_t step{}, steps{};
|
|
std::string step_name;
|
|
bool server{}; // a dedicated server's map, else a player's lobby
|
|
bool moved{}; // the session changed to it with the player in it (else: joining it)
|
|
bool installed{}; // asking: the mod is here but switched off, so nothing is downloaded
|
|
std::string have; // the version installed here, when the host's is another and replaces it
|
|
bool choice{true}; // asking: the answer a controller has picked (true: download)
|
|
std::uint64_t received{}, total{}; // downloading; total 0 when the size is not known
|
|
std::uint32_t yes_bind{}, no_bind{}; // the vote binds, which answer the card
|
|
};
|
|
// Client thread: a session just ended over this map. Ignored while another is in hand.
|
|
void offer(const multiplayer::MapNeed& need);
|
|
// Any thread. Yes downloads (when asking); no declines, or stops a download under way.
|
|
void answer(bool download) noexcept;
|
|
bool asking() noexcept;
|
|
// A controller's pick between the two answers, and what it is.
|
|
void pick(bool download) noexcept;
|
|
bool picked() noexcept;
|
|
void set_binds(std::uint32_t yes, std::uint32_t no) noexcept;
|
|
View view();
|
|
// Client thread, every tick: starts the live apply and the join when their turn comes.
|
|
void tick();
|
|
// The Thunderstore package an enabled mod's level comes from (its folder and manifest), for a
|
|
// host to tell its guests; empty when the level is the game's own or the mod is not a package.
|
|
std::string package_of(std::string_view asset);
|
|
// Reads the table package_of answers from, on a thread of its own, if it has not been (so the
|
|
// first map offer does not wait on the Mods folder). Cheap to call each frame; tick() does.
|
|
void warm_packages() noexcept;
|
|
}
|