From f6ed9c8f70db64c571690ee1198183a98ecc8e8a Mon Sep 17 00:00:00 2001 From: Chloe <25387744+qimiko@users.noreply.github.com> Date: Mon, 9 Sep 2024 01:55:09 -0700 Subject: [PATCH 1/3] Split update checks into multiple batches (#1066) * skip check with empty mod list * initial batching implementation * remove header that i don't use * use vector insert instead of copy --- loader/src/server/Server.cpp | 89 ++++++++++++++++++++++++++++++------ loader/src/server/Server.hpp | 10 +++- 2 files changed, 85 insertions(+), 14 deletions(-) diff --git a/loader/src/server/Server.cpp b/loader/src/server/Server.cpp index de0fca5d..5fd6990e 100644 --- a/loader/src/server/Server.cpp +++ b/loader/src/server/Server.cpp @@ -789,24 +789,14 @@ ServerRequest> server::checkUpdates(Mod const* mo ); } -ServerRequest> server::checkAllUpdates(bool useCache) { - if (useCache) { - return getCache().get(); - } - - auto modIDs = ranges::map>( - Loader::get()->getAllMods(), - [](auto mod) { return mod->getID(); } - ); - +ServerRequest> server::batchedCheckUpdates(std::vector const& batch) { auto req = web::WebRequest(); req.userAgent(getServerUserAgent()); req.param("platform", GEODE_PLATFORM_SHORT_IDENTIFIER); req.param("gd", GEODE_GD_VERSION_STR); req.param("geode", Loader::get()->getVersion().toNonVString()); - if (modIDs.size()) { - req.param("ids", ranges::join(modIDs, ";")); - } + + req.param("ids", ranges::join(batch, ";")); return req.get(formatServerURL("/mods/updates")).map( [](web::WebResponse* response) -> Result, ServerError> { if (response->ok()) { @@ -830,6 +820,79 @@ ServerRequest> server::checkAllUpdates(bool useCach ); } +void server::queueBatches( + ServerRequest>::PostResult const resolve, + std::shared_ptr>> const batches, + std::shared_ptr> accum +) { + // we have to do the copy here, or else our values die + batchedCheckUpdates(batches->back()).listen([resolve, batches, accum](auto result) { + if (result->ok()) { + auto serverValues = result->unwrap(); + + accum->reserve(accum->size() + serverValues.size()); + accum->insert(accum->end(), serverValues.begin(), serverValues.end()); + + if (batches->size() > 1) { + batches->pop_back(); + queueBatches(resolve, batches, accum); + } + else { + resolve(Ok(*accum)); + } + } + else { + resolve(*result); + } + }); +} + +ServerRequest> server::checkAllUpdates(bool useCache) { + if (useCache) { + return getCache().get(); + } + + auto modIDs = ranges::map>( + Loader::get()->getAllMods(), + [](auto mod) { return mod->getID(); } + ); + + // if there's no mods, the request would just be empty anyways + if (modIDs.empty()) { + // you would think it could infer like literally anything + return ServerRequest>::immediate( + Ok>({}) + ); + } + + auto modBatches = std::make_shared>>(); + auto modCount = modIDs.size(); + std::size_t maxMods = 200u; // this affects 0.03% of users + + if (modCount <= maxMods) { + // no tricks needed + return batchedCheckUpdates(modIDs); + } + + // even out the mod count, so a request with 230 mods sends two 115 mod requests + auto batchCount = modCount / maxMods + 1; + auto maxBatchSize = modCount / batchCount + 1; + + for (std::size_t i = 0u; i < modCount; i += maxBatchSize) { + auto end = std::min(modCount, i + maxBatchSize); + modBatches->emplace_back(modIDs.begin() + i, modIDs.begin() + end); + } + + // chain requests to avoid doing too many large requests at once + return ServerRequest>::runWithCallback( + [modBatches](auto finish, auto progress, auto hasBeenCancelled) { + auto accum = std::make_shared>(); + queueBatches(finish, modBatches, accum); + }, + "Mod Update Check" + ); +} + void server::clearServerCaches(bool clearGlobalCaches) { getCache<&getMods>().clear(); getCache<&getMod>().clear(); diff --git a/loader/src/server/Server.hpp b/loader/src/server/Server.hpp index 908d4eab..34b8f336 100644 --- a/loader/src/server/Server.hpp +++ b/loader/src/server/Server.hpp @@ -151,7 +151,15 @@ namespace server { ServerRequest> getTags(bool useCache = true); ServerRequest> checkUpdates(Mod const* mod); + + ServerRequest> batchedCheckUpdates(std::vector const& batch); + void queueBatches( + ServerRequest>::PostResult const finish, + std::shared_ptr>> const batches, + std::shared_ptr> const accum + ); + ServerRequest> checkAllUpdates(bool useCache = true); - + void clearServerCaches(bool clearGlobalCaches = false); } From 18cf751eb315b880ea1728126c2d9cf6af6bac53 Mon Sep 17 00:00:00 2001 From: Chloe <25387744+qimiko@users.noreply.github.com> Date: Mon, 9 Sep 2024 01:55:20 -0700 Subject: [PATCH 2/3] Show invalid mods on mod search (#1065) * invalid server mod parsing * show message for invalid mod * show invalid on explicit search --- loader/include/Geode/loader/ModMetadata.hpp | 2 + loader/src/loader/ModMetadataImpl.cpp | 6 +++ loader/src/server/Server.cpp | 32 ++++++-------- loader/src/ui/mods/list/ModItem.cpp | 44 ++++++++++++++++--- .../ui/mods/sources/ServerModListSource.cpp | 8 +++- 5 files changed, 68 insertions(+), 24 deletions(-) diff --git a/loader/include/Geode/loader/ModMetadata.hpp b/loader/include/Geode/loader/ModMetadata.hpp index d0e3c793..b3b8454c 100644 --- a/loader/include/Geode/loader/ModMetadata.hpp +++ b/loader/include/Geode/loader/ModMetadata.hpp @@ -236,6 +236,8 @@ namespace geode { void setTags(std::unordered_set const& value); void setNeedsEarlyLoad(bool const& value); void setIsAPI(bool const& value); + void setGameVersion(std::string const& value); + void setGeodeVersion(VersionInfo const& value); ModMetadataLinks& getLinksMut(); #endif diff --git a/loader/src/loader/ModMetadataImpl.cpp b/loader/src/loader/ModMetadataImpl.cpp index cacc4aa6..d3ebf8e5 100644 --- a/loader/src/loader/ModMetadataImpl.cpp +++ b/loader/src/loader/ModMetadataImpl.cpp @@ -654,6 +654,12 @@ void ModMetadata::setNeedsEarlyLoad(bool const& value) { void ModMetadata::setIsAPI(bool const& value) { m_impl->m_isAPI = value; } +void ModMetadata::setGameVersion(std::string const& value) { + m_impl->m_gdVersion = value; +} +void ModMetadata::setGeodeVersion(VersionInfo const& value) { + m_impl->m_geodeVersion = value; +} ModMetadataLinks& ModMetadata::getLinksMut() { return m_impl->m_links; } diff --git a/loader/src/server/Server.cpp b/loader/src/server/Server.cpp index 5fd6990e..cfdb195d 100644 --- a/loader/src/server/Server.cpp +++ b/loader/src/server/Server.cpp @@ -264,22 +264,17 @@ Result ServerModVersion::parse(matjson::Value const& raw) { auto res = ServerModVersion(); - // Verify target Geode version - auto version = root.needs("geode").template get(); - if (!semverCompare(Loader::get()->getVersion(), version)) { - return Err( - "Mod targets version {} but Geode is version {}", - version, Loader::get()->getVersion() - ); - } + res.metadata.setGeodeVersion(root.needs("geode").template get()); // Verify target GD version - auto gd = root.needs("gd").obj().needs(GEODE_PLATFORM_SHORT_IDENTIFIER).template get(); - if (gd != GEODE_GD_VERSION_STR && gd != "*") { - return Err( - "Mod targets GD version {} but current is version {}", - gd, GEODE_GD_VERSION_STR - ); + auto gd_obj = root.needs("gd").obj(); + std::string gd = "0.000"; + if (gd_obj.has(GEODE_PLATFORM_SHORT_IDENTIFIER)) { + gd = gd_obj.has(GEODE_PLATFORM_SHORT_IDENTIFIER).template get(); + } + + if (gd != "*") { + res.metadata.setGameVersion(gd); } // Get server info @@ -571,14 +566,15 @@ ServerRequest server::getMods(ModsQuery const& query, bool useCa auto req = web::WebRequest(); req.userAgent(getServerUserAgent()); - // Always target current GD version and Loader version - req.param("gd", GEODE_GD_VERSION_STR); - req.param("geode", Loader::get()->getVersion().toNonVString()); - // Add search params if (query.query) { req.param("query", *query.query); + } else { + // Target current GD version and Loader version when query is not set + req.param("gd", GEODE_GD_VERSION_STR); + req.param("geode", Loader::get()->getVersion().toNonVString()); } + if (query.platforms.size()) { std::string plats = ""; bool first = true; diff --git a/loader/src/ui/mods/list/ModItem.cpp b/loader/src/ui/mods/list/ModItem.cpp index ee85ab85..063b1a75 100644 --- a/loader/src/ui/mods/list/ModItem.cpp +++ b/loader/src/ui/mods/list/ModItem.cpp @@ -134,12 +134,27 @@ bool ModItem::init(ModSource&& source) { m_viewMenu->setAnchorPoint({ 1.f, .5f }); m_viewMenu->setScale(.55f); - ButtonSprite* spr; - if (Loader::get()->isModInstalled(m_source.getID())) { - spr = createGeodeButton("View", 50, false, true); - } else { - spr = createGeodeButton("Get", 50, false, true, GeodeButtonSprite::Install); + ButtonSprite* spr = nullptr; + if (auto serverMod = m_source.asServer(); serverMod != nullptr) { + auto version = serverMod->latestVersion(); + + auto geodeValid = Loader::get()->isModVersionSupported(version.getGeodeVersion()); + auto gameVersion = version.getGameVersion(); + auto gdValid = gameVersion == "*" || gameVersion == GEODE_STR(GEODE_GD_VERSION); + + if (!geodeValid || !gdValid) { + spr = createGeodeButton("NA", 50, false, true, GeodeButtonSprite::Default); + } } + + if (!spr) { + if (Loader::get()->isModInstalled(m_source.getID())) { + spr = createGeodeButton("View", 50, false, true); + } else { + spr = createGeodeButton("Get", 50, false, true, GeodeButtonSprite::Install); + } + } + auto viewBtn = CCMenuItemSpriteExtra::create( spr, this, menu_selector(ModItem::onView) @@ -473,6 +488,25 @@ void ModItem::onView(CCObject*) { )->show(); } + if (auto serverMod = m_source.asServer(); serverMod != nullptr) { + auto version = serverMod->latestVersion(); + auto geodeVersion = version.getGeodeVersion(); + auto geodeValid = Loader::get()->isModVersionSupported(geodeVersion); + + if (auto res = version.checkGameVersion(); !res) { + FLAlertLayer::create(nullptr, "Unavailable", res.unwrapErr(), "Close", nullptr)->show(); + return; + } else if (!geodeValid) { + auto msg = fmt::format( + "Geode {} is required to view this mod. You currently have {}.", + geodeVersion.toVString(), + Loader::get()->getVersion().toVString() + ); + FLAlertLayer::create(nullptr, "Unavailable", msg, "Close", nullptr)->show(); + return; + } + } + // Always open up the popup for the installed mod page if that is possible ModPopup::create(m_source.convertForPopup())->show(); } diff --git a/loader/src/ui/mods/sources/ServerModListSource.cpp b/loader/src/ui/mods/sources/ServerModListSource.cpp index d85e9f13..9ae8d828 100644 --- a/loader/src/ui/mods/sources/ServerModListSource.cpp +++ b/loader/src/ui/mods/sources/ServerModListSource.cpp @@ -80,7 +80,13 @@ ServerModListSource* ServerModListSource::get(ServerModListType type) { } void ServerModListSource::setSearchQuery(std::string const& query) { - m_query.query = query.size() ? std::optional(query) : std::nullopt; + if (query.empty()) { + m_query.query = std::nullopt; + m_query.platforms = { GEODE_PLATFORM_TARGET }; + } else { + m_query.query = std::optional(query); + m_query.platforms = {}; + } } std::unordered_set ServerModListSource::getModTags() const { From ebd65caa2b9ac586b1dbc310c4428ea28da883aa Mon Sep 17 00:00:00 2001 From: matcool <26722564+matcool@users.noreply.github.com> Date: Mon, 9 Sep 2024 13:26:07 -0300 Subject: [PATCH 3/3] 3.5.0 and changelog --- CHANGELOG.md | 21 +++++++++++++++++++++ VERSION | 2 +- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5118ed95..dc997bcb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,26 @@ # Geode Changelog +## v3.5.0 + * Move CCLighting to cocos headers (#1036) + * Add new `gd::string` constructor (bae22b4) + * Use `getChildren` instead of member in `getChildByID` (fe730ed) + * Fix sprite order in `CCMenuItemExt::createToggler` (d729a12, 59a0ade) + * Add restart button to windows's crashlog window (#1025) + * Update FMOD headers (63b82f9) + * Change SwelvyBG sprites to be 2048x512 (#1029) + * Fix missing `GEODE_DLL` (e4054d4) + * Add code of conduct (80693c1, ab8ace0, ca3a2a3) + * Add ID system to Geode's web requests (#1040, 1f2aa2c, 1b5ae86) + * Add `Notification::cancel` (cd5a66c) + * Update matjson (e5dd2c9) + * Update TulipHook (a31c68f) + * Fix a bug where only 1 word wrap variant can exist (#1058) + * Fix ScrollLayer when anchor point is not ignored (d95a43b) + * Move macOS builds to using apple clang, fixing issues on older macOS versions (#1030) + * Allow dashes when searching for developers (#1023) + * Split update checks into multiple batches (#1066) + * Show invalid mods on mod search (#1065) + ## v3.4.0 * Add an API for modifying the Geode UI via events; see [the corresponding docs page](https://docs.geode-sdk.org/tutorials/modify-geode) (2a3c35f) * Add `openInfoPopup` overload that accepts a mod ID and can open both an installed mod page or a server page (028bbf9) diff --git a/VERSION b/VERSION index fbcbf738..1545d966 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.4.0 \ No newline at end of file +3.5.0