rework ModListSource to be inheritance-based

This commit is contained in:
HJfod 2024-03-29 13:16:05 +02:00
parent bd95800fd2
commit 8f550e57d3
6 changed files with 307 additions and 172 deletions

View file

@ -83,18 +83,18 @@ bool ModsLayer::init() {
mainTabs->setAnchorPoint({ .5f, .0f });
mainTabs->setPosition(m_frame->convertToWorldSpace(tabsTop->getPosition() + ccp(0, 10)));
for (auto item : std::initializer_list<std::tuple<const char*, const char*, ModListSourceType>> {
{ "download.png"_spr, "Installed", ModListSourceType::Installed },
{ "GJ_timeIcon_001.png", "Updates", ModListSourceType::Updates },
{ "globe.png"_spr, "Download", ModListSourceType::All },
{ "GJ_sTrendingIcon_001.png", "Trending", ModListSourceType::Trending },
{ "gj_folderBtn_001.png", "Mod Packs", ModListSourceType::ModPacks },
for (auto item : std::initializer_list<std::tuple<const char*, const char*, ModListSource*>> {
{ "download.png"_spr, "Installed", InstalledModListSource::get(false) },
{ "GJ_timeIcon_001.png", "Updates", InstalledModListSource::get(true) },
{ "globe.png"_spr, "Download", ServerModListSource::get(ServerModListType::Download) },
{ "GJ_sTrendingIcon_001.png", "Trending", ServerModListSource::get(ServerModListType::Trending) },
{ "gj_folderBtn_001.png", "Mod Packs", ModPackListSource::get() },
}) {
auto btn = CCMenuItemSpriteExtra::create(
GeodeTabSprite::create(std::get<0>(item), std::get<1>(item), 100),
this, menu_selector(ModsLayer::onTab)
);
btn->setTag(static_cast<int>(std::get<2>(item)));
btn->setUserData(std::get<2>(item));
mainTabs->addChild(btn);
m_tabs.push_back(btn);
}
@ -160,7 +160,8 @@ bool ModsLayer::init() {
);
this->addChildAtPosition(m_pageMenu, Anchor::TopRight, ccp(-5, -5), false);
this->gotoTab(ModListSourceType::Installed);
// Go to installed mods list
this->gotoTab(InstalledModListSource::get(false));
this->setKeypadEnabled(true);
cocos::handleTouchPriority(this, true);
@ -174,16 +175,14 @@ bool ModsLayer::init() {
return true;
}
void ModsLayer::gotoTab(ModListSourceType type) {
void ModsLayer::gotoTab(ModListSource* src) {
// Update selected tab
for (auto tab : m_tabs) {
auto selected = tab->getTag() == static_cast<int>(type);
auto selected = tab->getUserData() == static_cast<void*>(src);
static_cast<GeodeTabSprite*>(tab->getNormalImage())->select(selected);
tab->setEnabled(!selected);
}
auto src = ModListSource::get(type);
// Remove current list from UI (it's Ref'd so it stays in memory)
if (m_currentSource) {
m_lists.at(m_currentSource)->removeFromParent();
@ -256,7 +255,7 @@ void ModsLayer::updateState() {
}
void ModsLayer::onTab(CCObject* sender) {
this->gotoTab(static_cast<ModListSourceType>(sender->getTag()));
this->gotoTab(static_cast<ModListSource*>(static_cast<CCNode*>(sender)->getUserData()));
}
void ModsLayer::onRefreshList(CCObject*) {

View file

@ -45,5 +45,5 @@ public:
static server::ServerPromise<std::vector<std::string>> checkInstalledModsForUpdates();
void gotoTab(ModListSourceType type);
void gotoTab(ModListSource* src);
};

View file

@ -12,7 +12,7 @@ bool ModList::init(ModListSource* src, CCSize const& size) {
this->setAnchorPoint({ .5f, .5f });
m_source = src;
src->reset();
m_source->reset();
m_list = ScrollLayer::create(size);
m_list->m_contentLayer->setLayout(
@ -46,7 +46,7 @@ bool ModList::init(ModListSource* src, CCSize const& size) {
// If the source is already in memory, we can immediately update the
// search query
if (m_source->isInstalledMods()) {
m_source->setQuery(m_searchInput->getString());
m_source->search(m_searchInput->getString());
this->gotoPage(0);
return;
}
@ -59,7 +59,7 @@ bool ModList::init(ModListSource* src, CCSize const& size) {
m_searchInputThreads -= 1;
if (m_searchInputThreads == 0) {
Loader::get()->queueInMainThread([this] {
m_source->setQuery(m_searchInput->getString());
m_source->search(m_searchInput->getString());
this->gotoPage(0);
});
}
@ -475,4 +475,3 @@ ModList* ModList::create(ModListSource* src, CCSize const& size) {
CC_SAFE_DELETE(ret);
return nullptr;
}

View file

@ -18,7 +18,7 @@ using ModListStatus = std::variant<ModListErrorStatus, ModListUnkProgressStatus,
class ModList : public CCNode {
protected:
Ref<ModListSource> m_source;
ModListSource* m_source;
size_t m_page = 0;
ScrollLayer* m_list;
CCMenu* m_statusContainer;

View file

@ -19,7 +19,7 @@ static bool weightedFuzzyMatch(std::string const& str, std::string const& kw, do
return false;
}
static std::pair<std::vector<Mod*>, size_t> getModsWithQuery(server::ModsQuery const& query) {
static std::pair<std::vector<Mod*>, size_t> getModsWithQuery(InstalledModsQuery const& query) {
std::vector<std::pair<Mod*, double>> mods;
// Filter installed mods based on query
@ -74,8 +74,86 @@ static std::pair<std::vector<Mod*>, size_t> getModsWithQuery(server::ModsQuery c
return { result, mods.size() };
}
static auto loadInstalledModsPage(server::ModsQuery&& query) {
return ModListSource::ProviderPromise([query = std::move(query)](auto resolve, auto, auto, auto const&) {
typename ModListSource::PagePromise ModListSource::loadPage(size_t page, bool update) {
if (!update && m_cachedPages.contains(page)) {
return PagePromise([this, page](auto resolve, auto) {
Loader::get()->queueInMainThread([this, page, resolve] {
resolve(m_cachedPages.at(page));
});
});
}
m_cachedPages.erase(page);
return this->fetchPage(page, PER_PAGE)
.then<Page, ModListSource::LoadPageError>([page, this](auto result) -> Result<Page, ModListSource::LoadPageError> {
if (result) {
auto data = result.unwrap();
if (data.totalModCount == 0 || data.mods.empty()) {
return Err(ModListSource::LoadPageError("No mods found :("));
}
auto pageData = Page();
for (auto mod : std::move(data.mods)) {
pageData.push_back(ModItem::create(std::move(mod)));
}
m_cachedItemCount = data.totalModCount;
m_cachedPages.insert({ page, pageData });
return Ok(pageData);
}
else {
return Err(result.unwrapErr());
}
});
}
std::optional<size_t> ModListSource::getPageCount() const {
return m_cachedItemCount ? std::optional(ceildiv(m_cachedItemCount.value(), PER_PAGE)) : std::nullopt;
}
std::optional<size_t> ModListSource::getItemCount() const {
return m_cachedItemCount;
}
void ModListSource::reset() {
this->clearCache();
this->resetQuery();
}
void ModListSource::clearCache() {
m_cachedPages.clear();
m_cachedItemCount = std::nullopt;
}
void ModListSource::search(std::string const& query) {
this->setSearchQuery(query);
this->clearCache();
}
ModListSource::ModListSource() {}
InstalledModListSource::InstalledModListSource(bool onlyUpdates)
: m_onlyUpdates(onlyUpdates)
{
this->resetQuery();
}
InstalledModListSource* InstalledModListSource::get(bool onlyUpdates) {
if (onlyUpdates) {
static auto inst = new InstalledModListSource(true);
return inst;
}
else {
static auto inst = new InstalledModListSource(false);
return inst;
}
}
void InstalledModListSource::resetQuery() {
m_query = InstalledModsQuery {
.onlyUpdates = m_onlyUpdates,
};
}
InstalledModListSource::ProviderPromise InstalledModListSource::fetchPage(size_t page, size_t pageSize) {
m_query.page = page;
m_query.pageSize = pageSize;
return ModListSource::ProviderPromise([query = m_query](auto resolve, auto, auto, auto const&) {
Loader::get()->queueInMainThread([query = std::move(query), resolve = std::move(resolve)] {
auto content = ModListSource::ProvidedMods();
auto paged = getModsWithQuery(query);
@ -88,8 +166,62 @@ static auto loadInstalledModsPage(server::ModsQuery&& query) {
});
}
static auto loadServerModsPage(server::ModsQuery&& query) {
return server::ServerResultCache<&server::getMods>::shared().get(query)
void InstalledModListSource::setSearchQuery(std::string const& query) {
m_query.query = query.size() ? std::optional(query) : std::nullopt;
}
InstalledModsQuery const& InstalledModListSource::getQuery() const {
return m_query;
}
InstalledModsQuery& InstalledModListSource::getQueryMut() {
this->clearCache();
return m_query;
}
bool InstalledModListSource::isInstalledMods() const {
return true;
}
bool InstalledModListSource::wantsRestart() const {
for (auto mod : Loader::get()->getAllMods()) {
if (mod->getRequestedAction() != ModRequestedAction::None) {
return true;
}
}
return false;
}
void ServerModListSource::resetQuery() {
switch (m_type) {
case ServerModListType::Download: {
m_query = server::ModsQuery {};
} break;
case ServerModListType::Featured: {
m_query = server::ModsQuery {
.featured = true,
};
} break;
case ServerModListType::Trending: {
m_query = server::ModsQuery {
.sorting = server::ModsSort::RecentlyUpdated,
};
} break;
case ServerModListType::Recent: {
m_query = server::ModsQuery {
.sorting = server::ModsSort::RecentlyPublished,
};
} break;
}
}
ServerModListSource::ProviderPromise ServerModListSource::fetchPage(size_t page, size_t pageSize) {
m_query.page = page;
m_query.pageSize = pageSize;
return server::ServerResultCache<&server::getMods>::shared().get(m_query)
.then<ModListSource::ProvidedMods>([](server::ServerModsList list) {
auto content = ModListSource::ProvidedMods();
for (auto&& mod : std::move(list.mods)) {
@ -106,133 +238,77 @@ static auto loadServerModsPage(server::ModsQuery&& query) {
});
}
typename ModListSource::PagePromise ModListSource::loadPage(size_t page, bool update) {
// Return a generic "Coming soon" message if there's no provider set
if (!m_provider.get) {
return PagePromise([this, page](auto, auto reject) {
reject(LoadPageError("Coming soon! ;)"));
});
ServerModListSource::ServerModListSource(ServerModListType type)
: m_type(type)
{
this->resetQuery();
}
ServerModListSource* ServerModListSource::get(ServerModListType type) {
switch (type) {
default:
case ServerModListType::Download: {
static auto inst = new ServerModListSource(ServerModListType::Download);
return inst;
} break;
case ServerModListType::Featured: {
static auto inst = new ServerModListSource(ServerModListType::Featured);
return inst;
} break;
case ServerModListType::Trending: {
static auto inst = new ServerModListSource(ServerModListType::Trending);
return inst;
} break;
case ServerModListType::Recent: {
static auto inst = new ServerModListSource(ServerModListType::Recent);
return inst;
} break;
}
if (!update && m_cachedPages.contains(page)) {
return PagePromise([this, page](auto resolve, auto) {
Loader::get()->queueInMainThread([this, page, resolve] {
resolve(m_cachedPages.at(page));
});
});
}
m_cachedPages.erase(page);
return m_provider.get(server::ModsQuery {
.query = m_query,
.page = page,
// todo: loader setting to change this
.pageSize = PER_PAGE,
})
.then<Page, ModListSource::LoadPageError>([page, this](auto result) -> Result<Page, ModListSource::LoadPageError> {
if (result) {
auto data = result.unwrap();
if (data.totalModCount == 0 || data.mods.empty()) {
return Err(ModListSource::LoadPageError("No mods found :("));
}
auto pageData = Page();
for (auto mod : std::move(data.mods)) {
pageData.push_back(ModItem::create(std::move(mod)));
}
m_cachedItemCount = data.totalModCount;
m_cachedPages.insert({ page, pageData });
return Ok(pageData);
}
else {
return Err(result.unwrapErr());
}
}
void ServerModListSource::setSearchQuery(std::string const& query) {
m_query.query = query.size() ? std::optional(query) : std::nullopt;
}
server::ModsQuery const& ServerModListSource::getQuery() const {
return m_query;
}
server::ModsQuery& ServerModListSource::getQueryMut() {
this->clearCache();
return m_query;
}
bool ServerModListSource::isInstalledMods() const {
return false;
}
bool ServerModListSource::wantsRestart() const {
// todo
return false;
}
void ModPackListSource::resetQuery() {}
ModPackListSource::ProviderPromise ModPackListSource::fetchPage(size_t page, size_t pageSize) {
return ProviderPromise([](auto, auto reject) {
reject(LoadPageError("Coming soon ;)"));
});
}
std::optional<size_t> ModListSource::getPageCount() const {
return m_cachedItemCount ? std::optional(ceildiv(m_cachedItemCount.value(), PER_PAGE)) : std::nullopt;
ModPackListSource::ModPackListSource() {}
ModPackListSource* ModPackListSource::get() {
static auto inst = new ModPackListSource();
return inst;
}
std::optional<size_t> ModListSource::getItemCount() const {
return m_cachedItemCount;
void ModPackListSource::setSearchQuery(std::string const& query) {}
bool ModPackListSource::isInstalledMods() const {
return false;
}
void ModListSource::reset() {
m_query = std::nullopt;
m_cachedPages.clear();
m_cachedItemCount = std::nullopt;
}
void ModListSource::setQuery(std::string const& query) {
// Set query & reset cache
if (m_query != query) {
m_query = query.empty() ? std::nullopt : std::optional(query);
m_cachedPages.clear();
m_cachedItemCount = std::nullopt;
}
}
bool ModListSource::isInstalledMods() const {
return m_provider.isInstalledMods;
}
bool ModListSource::wantsRestart() const {
return m_provider.wantsRestart && m_provider.wantsRestart();
}
ModListSource* ModListSource::create(Provider&& provider) {
auto ret = new ModListSource();
ret->m_provider = std::move(provider);
ret->autorelease();
return ret;
}
ModListSource* ModListSource::get(ModListSourceType type) {
switch (type) {
default:
case ModListSourceType::Installed: {
static auto inst = Ref(ModListSource::create({
.get = loadInstalledModsPage,
.wantsRestart = +[] {
for (auto mod : Loader::get()->getAllMods()) {
if (mod->getRequestedAction() != ModRequestedAction::None) {
return true;
}
}
return false;
},
.isInstalledMods = true,
}));
return inst;
} break;
case ModListSourceType::Updates: {
static auto inst = Ref(ModListSource::create({}));
return inst;
} break;
case ModListSourceType::Featured: {
static auto inst = Ref(ModListSource::create({
.get = +[](server::ModsQuery&& query) {
query.featured = true;
return loadServerModsPage(std::move(query));
},
}));
return inst;
} break;
case ModListSourceType::Trending: {
static auto inst = Ref(ModListSource::create({}));
return inst;
} break;
case ModListSourceType::ModPacks: {
static auto inst = Ref(ModListSource::create({}));
return inst;
} break;
case ModListSourceType::All: {
static auto inst = Ref(ModListSource::create({
.get = loadServerModsPage,
}));
return inst;
} break;
}
bool ModPackListSource::wantsRestart() const {
return false;
}

View file

@ -7,17 +7,15 @@
using namespace geode::prelude;
enum class ModListSourceType {
Installed,
Updates,
Featured,
Trending,
ModPacks,
All,
struct InstalledModsQuery final {
std::optional<std::string> query;
bool onlyUpdates = false;
size_t page = 0;
size_t pageSize = 10;
};
// Handles loading the entries for the mods list
class ModListSource : public CCObject {
class ModListSource {
public:
struct LoadPageError {
std::string message;
@ -40,30 +38,24 @@ public:
};
using ProviderPromise = Promise<ProvidedMods, LoadPageError, std::optional<uint8_t>>;
struct Provider {
ProviderPromise(*get)(server::ModsQuery&& query) = nullptr;
bool(*wantsRestart)() = nullptr;
bool isInstalledMods = false;
};
protected:
std::unordered_map<size_t, Page> m_cachedPages;
std::optional<size_t> m_cachedItemCount;
std::optional<std::string> m_query;
Provider m_provider;
virtual void resetQuery() = 0;
virtual ProviderPromise fetchPage(size_t page, size_t pageSize) = 0;
virtual void setSearchQuery(std::string const& query) = 0;
ModListSource();
public:
// Create a new source with an arbitary provider
static ModListSource* create(Provider&& provider);
// Get a standard source (lazily created static instance)
static ModListSource* get(ModListSourceType type);
ModListSource(ModListSource const&) = delete;
ModListSource(ModListSource&&) = delete;
// Reset all filters & cache
void reset();
// Set a query; clears cache
void setQuery(std::string const& query);
void clearCache();
void search(std::string const& query);
// Load page, uses cache if possible unless `update` is true
PagePromise loadPage(size_t page, bool update = false);
@ -73,6 +65,75 @@ public:
/**
* True if the source consists only of installed mods
*/
bool isInstalledMods() const;
bool wantsRestart() const;
virtual bool isInstalledMods() const = 0;
virtual bool wantsRestart() const = 0;
};
class InstalledModListSource : public ModListSource {
protected:
bool m_onlyUpdates;
InstalledModsQuery m_query;
void resetQuery() override;
ProviderPromise fetchPage(size_t page, size_t pageSize) override;
InstalledModListSource(bool onlyUpdates);
public:
static InstalledModListSource* get(bool onlyUpdates);
void setSearchQuery(std::string const& query) override;
InstalledModsQuery const& getQuery() const;
// Get mutable access to the current query; this clears the cache
InstalledModsQuery& getQueryMut();
bool isInstalledMods() const override;
bool wantsRestart() const override;
};
enum class ServerModListType {
Download,
Featured,
Trending,
Recent,
};
class ServerModListSource : public ModListSource {
protected:
ServerModListType m_type;
server::ModsQuery m_query;
void resetQuery() override;
ProviderPromise fetchPage(size_t page, size_t pageSize) override;
ServerModListSource(ServerModListType type);
public:
static ServerModListSource* get(ServerModListType type);
void setSearchQuery(std::string const& query) override;
server::ModsQuery const& getQuery() const;
// Get mutable access to the current query; this clears the cache
server::ModsQuery& getQueryMut();
bool isInstalledMods() const override;
bool wantsRestart() const override;
};
class ModPackListSource : public ModListSource {
protected:
void resetQuery() override;
ProviderPromise fetchPage(size_t page, size_t pageSize) override;
ModPackListSource();
public:
static ModPackListSource* get();
void setSearchQuery(std::string const& query) override;
bool isInstalledMods() const override;
bool wantsRestart() const override;
};