Files
ReSkate/Extension/Assets/mod_store.h
T
AyeZeeBB d47a7c8492 Mod Manager: a download queue, and a new look for the featured card
Installs, updates and uninstalls are jobs run one after another in the order asked for: asking for
one while another runs queues it instead of being refused. A job waits while the mods are applied or
a session's map downloads, and works out what its mod needs when it starts, from the Mods folder as
it is then. Buttons say ADD TO QUEUE while a job runs and QUEUED once in line; the foot of the page
says how many wait, and CANCEL ALL empties the queue.

The carousel's middle card: the mod's picture as its backdrop, darkened, and big as a tipped sticker
beside the text; a FEATURED and INSTALLED / UPDATE AVAILABLE tag, the facts as chips, as much of the
description as fits above the buttons, and the buttons under the text with the pad's prompts beside
them.
2026-10-24 20:25:55 -05:00

115 lines
6.1 KiB
C++

#pragma once
#include "mod_store_view.h"
#include "Extension/Assets/live_mods.h"
#include <cstdint>
#include <memory>
#include <string>
#include <string_view>
#include <vector>
// The in-game Mod Manager (the pause menu's Mod Manager tab, Extension/UI/Overlay/mod_manager_page.inl):
// Thunderstore's listing for ReSkate's community, the installed mods, and a queue that downloads and
// installs packages with what they need, updates them and uninstalls them, as the launcher's Mods
// panel does. Everything that reads the disk or the network runs on a thread of its own; the page
// reads snapshots, every frame if it likes.
//
// A change takes effect as any change to the Mods folder does in game: plugins within two seconds
// (the plugin host looks for them), everything else when the mods are applied (live_mods.h), which
// the page offers. The game holds the files of mods it has loaded open, so an update or uninstall
// of one of those cannot be made while it runs: it waits in Mods/.reskate-pending and the launcher
// makes it before the next start (Launcher/mod_manager.h apply_pending).
namespace dingosdk::mod_store {
// The listing, as it was last read: at once from the copy kept on disk
// (%LOCALAPPDATA%\ReSkate\thunderstore\listing-<community>.json), and from Thunderstore when that is
// over 10 minutes old.
struct Listing {
std::vector<ts::Package> packages;
std::uint64_t fetched{}; // when this copy came from Thunderstore (Unix seconds); 0: none yet
bool fetching{};
std::string error; // why the last fetch failed; empty when it did not
unsigned serial{}; // changes with every new copy
};
// Any thread. Starts a fetch when one is due.
std::shared_ptr<const Listing> listing();
// Fetches it again now (unless a fetch is under way).
void refresh();
// An installed mod as the page shows it, its state as the Mods page in the ReSkate menu tells it.
enum class State { loaded, disabled, apply_to_load, apply_to_unload, left_out, not_loaded, set_aside, update_waiting, removal_waiting };
const char* state_label(State state);
struct Mod {
std::string folder, title, author, version, description;
std::vector<std::string> contents; // "Map", "Plugin"... (mod_list.h mod_contents)
bool enabled{}, in_effect{};
State state{};
std::string why; // what the state means for this mod, when it needs saying
bool gone{}; // its folder was deleted, but it is in effect until applied
};
struct Mods {
std::vector<Mod> mods; // in load order, highest first
ts::Installed versions; // every installed folder and its version
std::size_t changes{}; // what the Apply button would apply
live_mods::ApplyEffect effect{}; // and what it does to the level being played
std::size_t waiting{}; // updates and uninstalls waiting for the next start
std::string issue; // mods.json could not be read
bool present{}; // the Mods folder exists
// Read after the last apply had finished: only then do `changes` say what applying would still
// change (a reading from before or during one still has the applied ones in it).
bool settled{};
unsigned serial{};
};
// Any thread. Read on a thread of its own: again every 1.5 s while asked for, and at once after a
// change made here. Null until the first read is done.
std::shared_ptr<const Mods> installed();
// Switches a mod on or off in mods.json (on a thread of its own). Takes effect when applied.
void set_enabled(const std::string& folder, bool on);
// Applies the mods as they are now (live_mods::apply), reloading the level when that is needed for
// the change to show. Returns what happened, for the page.
std::string apply();
// The queue: jobs (an install of some packages, an uninstall) run one at a time, in the order they
// were asked for; one can be asked for while another runs.
struct Progress {
bool busy{}; // a job is in hand, or waits its turn
enum class Step { starting, downloading, installing, uninstalling } step{};
std::string activity; // "Downloading Vancouver Plaza (2 of 3)"
float fraction{-1}; // of the one in hand; below 0 when not known
// The package in hand (Namespace-Name; the folder, uninstalling), its name, the version going
// in and its icon's URL; which of the job's packages it is, of how many (from 0); while it
// downloads, how much has come of how much (0: not known); and the packages still to come.
std::string package, title, version, icon;
std::size_t at{}, of{};
std::uint64_t received{}, total{};
std::vector<std::string> queued; // and every package of the jobs waiting their turn (an uninstall's folder)
std::size_t waiting{}; // jobs waiting their turn after the one in hand
std::string result; // what the last job did, for the page; empty while busy
bool failed{}; // the last job did not do all it was asked to
unsigned serial{}; // changes when a job ends
};
Progress progress();
// Queues an install of the packages (Namespace-Name each, as in the listing) at their newest version,
// with the packages they need (worked out when the job starts). An installed one is updated. Empty,
// or why it was not queued (a package not listed, or already queued or in hand).
std::string install(const std::vector<std::string>& packages);
// Queues an uninstall of Mods/<folder>: to the Recycle Bin, or at the next start when the game holds
// its files.
std::string uninstall(const std::string& folder);
// Whether a package (Namespace-Name) is being installed now, or waits in the queue.
bool queued(std::string_view package);
// Stops the job in hand and empties the queue; what is installed stays.
void cancel();
// A package's README, fetched on a thread of its own the first time it is asked for; null until
// then (and `failed` when it could not be had).
struct Readme {
ts::Readme text;
bool failed{};
};
std::shared_ptr<const Readme> readme(const ts::Package& package);
// "YYYY-MM-DD" today (UTC), for the front page.
std::string today();
}