impl searching on server

This commit is contained in:
HJfod 2024-02-28 01:30:39 +02:00
parent 54ac66ef7c
commit 65645fe753
6 changed files with 102 additions and 31 deletions

View file

@ -211,7 +211,7 @@ std::string server::getServerUserAgent() {
);
}
ServerPromise<ServerModsList> server::getMods(ModsQuery query) {
ServerPromise<ServerModsList> server::getMods(ModsQuery const& query) {
auto req = web::WebRequest();
req.userAgent(getServerUserAgent());

View file

@ -76,6 +76,6 @@ namespace server {
std::string getServerAPIBaseURL();
std::string getServerUserAgent();
ServerPromise<ServerModsList> getMods(ModsQuery query);
ServerPromise<ServerModsList> getMods(ModsQuery const& query);
ServerPromise<ByteVector> getModLogo(std::string const& id);
}

View file

@ -1,5 +1,4 @@
#include "ModList.hpp"
#include <Geode/ui/TextInput.hpp>
#include <Geode/utils/ColorProvider.hpp>
bool ModList::init(ModListSource* src, CCSize const& size) {
@ -10,6 +9,7 @@ bool ModList::init(ModListSource* src, CCSize const& size) {
this->setAnchorPoint({ .5f, .5f });
m_source = src;
src->reset();
m_list = ScrollLayer::create(size);
m_list->m_contentLayer->setLayout(
@ -34,11 +34,26 @@ bool ModList::init(ModListSource* src, CCSize const& size) {
searchBG->ignoreAnchorPointForPosition(false);
m_searchMenu->addChildAtPosition(searchBG, Anchor::Center);
auto searchInput = TextInput::create(size.width, "Search Mods");
searchInput->setScale(.75f);
searchInput->setAnchorPoint({ 0, .5f });
searchInput->setTextAlign(TextInputAlign::Left);
m_searchMenu->addChildAtPosition(searchInput, Anchor::Left, ccp(10, 0));
m_searchInput = TextInput::create(size.width, "Search Mods");
m_searchInput->setScale(.75f);
m_searchInput->setAnchorPoint({ 0, .5f });
m_searchInput->setTextAlign(TextInputAlign::Left);
m_searchInput->setCallback([this](auto const&) {
// This avoids spamming servers for every character typed,
// instead waiting for input to stop to actually do the search
std::thread([this] {
m_searchInputThreads += 1;
std::this_thread::sleep_for(std::chrono::milliseconds(300));
m_searchInputThreads -= 1;
if (m_searchInputThreads == 0) {
Loader::get()->queueInMainThread([this] {
m_source->setQuery(m_searchInput->getString());
this->gotoPage(0);
});
}
}).detach();
});
m_searchMenu->addChildAtPosition(m_searchInput, Anchor::Left, ccp(10, 0));
// Do not add search menu; that's handled by onSearch

View file

@ -3,6 +3,7 @@
#include <Geode/ui/General.hpp>
#include <Geode/ui/ScrollLayer.hpp>
#include <Geode/ui/TextArea.hpp>
#include <Geode/ui/TextInput.hpp>
#include "ModItem.hpp"
#include "ModListSource.hpp"
@ -32,8 +33,10 @@ protected:
CCMenuItemSpriteExtra* m_pagePrevBtn;
CCMenuItemSpriteExtra* m_pageNextBtn;
Ref<CCMenu> m_searchMenu;
TextInput* m_searchInput;
ModListPageUpdated m_pageUpdated = nullptr;
bool m_bigSize = false;
std::atomic<size_t> m_searchInputThreads = 0;
bool init(ModListSource* src, CCSize const& size);

View file

@ -1,37 +1,61 @@
#include "ModListSource.hpp"
#include <server/Server.hpp>
static size_t PER_PAGE = 10;
#define FTS_FUZZY_MATCH_IMPLEMENTATION
#include <Geode/external/fts/fts_fuzzy_match.h>
static constexpr size_t PER_PAGE = 10;
static size_t ceildiv(size_t a, size_t b) {
// https://stackoverflow.com/questions/2745074/fast-ceiling-of-an-integer-division-in-c-c
return a / b + (a % b != 0);
}
static auto loadInstalledModsPage(size_t page) {
return ModListSource::ProviderPromise([page](auto resolve, auto, auto, auto const&) {
Loader::get()->queueInMainThread([page, resolve = std::move(resolve)] {
static bool weightedFuzzyMatch(std::string const& str, std::string const& kw, double weight, double& out) {
int score;
if (fts::fuzzy_match(kw.c_str(), str.c_str(), score)) {
out = std::max(out, score * weight);
return true;
}
return false;
}
static auto loadInstalledModsPage(server::ModsQuery&& query) {
return ModListSource::ProviderPromise([query = std::move(query)](auto resolve, auto, auto, auto const&) {
Loader::get()->queueInMainThread([query = std::move(query), resolve = std::move(resolve)] {
auto content = ModListSource::Page();
auto all = Loader::get()->getAllMods();
std::vector<std::pair<Mod*, double>> mods;
// todo: finish this
for (auto& mod : Loader::get()->getAllMods()) {
// bool someMatched = false;
double weighted = 0;
// if (query.query) {
// someMatched += weightedFuzzyMatch(mod->getName(), *query.query, 2, weighted);
// }
// if (someMatched) {
mods.push_back({ mod, weighted });
// }
}
// Sort list based on score
std::sort(mods.begin(), mods.end(), [](auto a, auto b) {
return a.second < b.second;
});
for (
size_t i = page * PER_PAGE;
i < all.size() && i < (page + 1) * PER_PAGE;
size_t i = query.page * query.pageSize;
i < mods.size() && i < (query.page + 1) * query.pageSize;
i += 1
) {
content.push_back(InstalledModItem::create(all.at(i)));
content.push_back(InstalledModItem::create(mods.at(i).first));
}
resolve({ content, all.size() });
resolve({ content, mods.size() });
});
});
}
static auto loadServerModsPage(size_t page, bool featuredOnly) {
return ModListSource::ProviderPromise([page, featuredOnly](auto resolve, auto reject, auto progress, auto cancelled) {
server::getMods(server::ModsQuery {
.featured = featuredOnly ? std::optional(true) : std::nullopt,
.page = page,
.pageSize = PER_PAGE,
})
static auto loadServerModsPage(server::ModsQuery&& query) {
return ModListSource::ProviderPromise([query = std::move(query)](auto resolve, auto reject, auto progress, auto cancelled) {
server::getMods(query)
.then([resolve, reject](server::ServerModsList list) {
auto content = ModListSource::Page();
for (auto mod : list.mods) {
@ -65,7 +89,12 @@ typename ModListSource::PagePromise ModListSource::loadPage(size_t page, bool up
}
m_cachedPages.erase(page);
return PagePromise([this, page](auto resolve, auto reject, auto progress, auto cancelled) {
m_provider(page)
m_provider(server::ModsQuery {
.query = m_query,
.page = page,
// todo: loader setting to change this
.pageSize = PER_PAGE,
})
.then([page, this, resolve, reject](auto data) {
if (data.second == 0 || data.first.empty()) {
return reject(ModListSource::LoadPageError("No mods found :("));
@ -92,6 +121,21 @@ std::optional<size_t> ModListSource::getItemCount() const {
return m_cachedItemCount;
}
void ModListSource::reset() {
m_query.clear();
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;
m_cachedPages.clear();
m_cachedItemCount = std::nullopt;
}
}
ModListSource* ModListSource::create(Provider* provider) {
auto ret = new ModListSource();
ret->m_provider = provider;
@ -108,8 +152,9 @@ ModListSource* ModListSource::get(ModListSourceType type) {
} break;
case ModListSourceType::Featured: {
static auto inst = Ref(ModListSource::create(+[](size_t page) {
return loadServerModsPage(page, true);
static auto inst = Ref(ModListSource::create(+[](server::ModsQuery&& query) {
query.featured = true;
return loadServerModsPage(std::move(query));
}));
return inst;
} break;
@ -125,8 +170,8 @@ ModListSource* ModListSource::get(ModListSourceType type) {
} break;
case ModListSourceType::All: {
static auto inst = Ref(ModListSource::create(+[](size_t page) {
return loadServerModsPage(page, false);
static auto inst = Ref(ModListSource::create(+[](server::ModsQuery&& query) {
return loadServerModsPage(std::move(query));
}));
return inst;
} break;

View file

@ -2,6 +2,7 @@
#include <Geode/utils/cocos.hpp>
#include <Geode/utils/Promise.hpp>
#include <server/Server.hpp>
#include "ModItem.hpp"
using namespace geode::prelude;
@ -33,11 +34,12 @@ public:
using PagePromise = Promise<Page, LoadPageError, std::optional<uint8_t>>;
using ProviderPromise = Promise<std::pair<Page, size_t>, LoadPageError, std::optional<uint8_t>>;
using Provider = ProviderPromise(size_t page);
using Provider = ProviderPromise(server::ModsQuery&& query);
protected:
std::unordered_map<size_t, Page> m_cachedPages;
std::optional<size_t> m_cachedItemCount;
std::string m_query;
Provider* m_provider = nullptr;
public:
@ -47,6 +49,12 @@ public:
// Get a standard source (lazily created static instance)
static ModListSource* get(ModListSourceType type);
// Reset all filters & cache
void reset();
// Set a query; clears cache
void setQuery(std::string const& query);
// Load page, uses cache if possible unless `update` is true
PagePromise loadPage(size_t page, bool update = false);
std::optional<size_t> getPageCount() const;