diff --git a/include/datadragon.h b/include/datadragon.h index 54e6e5b..5f6b006 100644 --- a/include/datadragon.h +++ b/include/datadragon.h @@ -1,6 +1,10 @@ #pragma once +#include +#include +#include #include +#include #include #include #include @@ -11,7 +15,10 @@ class DataDragon { public: + using notifyImgfunc_t = std::function; + DataDragon(); + ~DataDragon(); DataDragon(const DataDragon&) = delete; DataDragon& operator=(const DataDragon&) = delete; @@ -33,25 +40,51 @@ public: SPLASH = 2 }; + // might block until version is available const std::string& getVersion(); + // might block until champ data is available const std::vector& getChamps(); + // might block until image is downloaded cv::Mat getImage(const std::string& champid, ImageType imgtype = ImageType::SQUARE); + void getImageAsnyc(const std::string& champid, notifyImgfunc_t func, ImageType imgtype = ImageType::SQUARE); + // might block until champ data is available const ChampData& getBestMatchingChamp(const std::string& name, int* count = nullptr); static const ChampData EMPTYCHAMP; protected: + std::string getImageUrl(const std::string& champid, ImageType type); std::string getCDNString() const; QByteArray requestRaw(const std::string& url); QJsonDocument request(const std::string& url); + void getVersionInternal(); + void getChampsInternal(); + void startThread(); + void stopThread(); + void stopAndJoinThread(); + void threadLoop(); + std::string version; std::vector champs; + std::mutex cachedatamutex; + std::condition_variable cachedatacv; private: + struct Task { + std::string champid; + notifyImgfunc_t func; + ImageType type; + }; + CURL* curl = nullptr; // the curl (does curling) DataDragonImageCache cache[3]; MemoryImageCache memcache; + std::list tasks; + std::mutex tasksmutex; + std::condition_variable tasksnotemptycv; + std::thread bgthread; + bool shouldrun = true; }; diff --git a/include/stagesettings.h b/include/stagesettings.h index 2cd1078..c403a1b 100644 --- a/include/stagesettings.h +++ b/include/stagesettings.h @@ -43,6 +43,7 @@ signals: private: void rescaleImage(); + void applyChampion(const DataDragon::ChampData& cd); Ui::StageSettings *ui; DataDragon* dd = nullptr; diff --git a/src/datadragon.cpp b/src/datadragon.cpp index f70f5c4..b19ea1c 100644 --- a/src/datadragon.cpp +++ b/src/datadragon.cpp @@ -29,6 +29,14 @@ DataDragon::DataDragon() : cache({{"square", ".png"}, {"loading", "_0.jpg"}, {"s curl = curl_easy_init(); curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, arrayWriteCallback); + + startThread(); +} + +DataDragon::~DataDragon() { + stopAndJoinThread(); + + curl_easy_cleanup(curl); } DataDragon::ChampData::ChampData() : key(-1) {} @@ -42,52 +50,24 @@ DataDragon::ChampData::ChampData(const QJsonObject& source) { } const std::string& DataDragon::getVersion() { - if(!version.empty()) { - return version; + std::unique_lock lock(cachedatamutex); + while(version.empty() && shouldrun) { + cachedatacv.wait(lock); } - - QJsonDocument jversions = request("api/versions.json"); - if(jversions.isArray()) { - QJsonArray jverarr = jversions.array(); - if(!jverarr.empty()) { - version = jverarr.at(0).toString().toStdString(); - Log::info << "got League version: " << version; - return version; - } - } - - Log::error << "error parsing version object"; - // empty version str return version; } const std::vector& DataDragon::getChamps() { - if(!champs.empty()) { - return champs; + std::unique_lock lock(cachedatamutex); + while(champs.empty() && shouldrun) { + cachedatacv.wait(lock); } - if(getVersion().empty()) { - return champs; - } - - QJsonDocument jchamps = request(getCDNString() + "data/en_US/champion.json"); - if(jchamps.isObject()) { - QJsonObject obj = jchamps.object(); - auto it = obj.constFind("data"); - if(it != obj.constEnd() && it.value().isObject()) { - QJsonObject jchampsdata = it.value().toObject(); - for(auto champit = jchampsdata.constBegin(); champit != jchampsdata.constEnd(); champit++) { - if(champit.value().isObject()) { - champs.emplace_back(champit.value().toObject()); - } - } - } - } - Log::info << "loaded " << champs.size() << " champs"; - return champs; } cv::Mat DataDragon::getImage(const std::string& champid, ImageType imgtype) { + if(champid.empty()) return {}; + // query mem cache cv::Mat img = memcache.getImage(champid, (int) imgtype); if(!img.empty()) return img; @@ -100,21 +80,8 @@ cv::Mat DataDragon::getImage(const std::string& champid, ImageType imgtype) { return img; } - std::string url; - if(imgtype == ImageType::SQUARE) { - if(getVersion().empty()) { - return {}; - } - - // with version - url = getCDNString() + "img/champion/" + champid + ".png"; - } else if(imgtype == ImageType::LOADING) { - // no version - url = "cdn/img/champion/loading/" + champid + "_0.jpg"; - } else if(imgtype == ImageType::SPLASH) { - // no version! - url = "cdn/img/champion/splash/" + champid + "_0.jpg"; - } + const std::string url = getImageUrl(champid, imgtype); + if(url.empty()) return {}; QByteArray arr = requestRaw(url); if(arr.isEmpty()) { @@ -124,7 +91,7 @@ cv::Mat DataDragon::getImage(const std::string& champid, ImageType imgtype) { // propably an error if(arr.size() < 1000) { - Log::info << "small icon url: " << getCDNString() << "img/champion/" << champid << ".png"; + Log::info << "small icon url: " << url; Log::info << "content: " << std::string(arr.data(), arr.size()); return {}; } @@ -132,6 +99,7 @@ cv::Mat DataDragon::getImage(const std::string& champid, ImageType imgtype) { // store HDD cache cache[(int) imgtype].addImageRaw(arr, champid); + // convert byte array to image data cv::Mat rawData(1, arr.size(), CV_8UC1, (void*) arr.data()); cv::Mat decodedImage = cv::imdecode(rawData, cv::IMREAD_COLOR); @@ -143,6 +111,16 @@ cv::Mat DataDragon::getImage(const std::string& champid, ImageType imgtype) { return decodedImage; } +void DataDragon::getImageAsnyc(const std::string& champid, notifyImgfunc_t func, ImageType imgtype) { + if(!func) return; + + { + std::lock_guard lock(tasksmutex); + tasks.push_back({champid, func, imgtype}); + } + tasksnotemptycv.notify_one(); +} + static std::string toLower(const std::string& in) { return QString::fromStdString(in).toLower().toStdString(); } @@ -202,6 +180,26 @@ const DataDragon::ChampData& DataDragon::getBestMatchingChamp(const std::string& return EMPTYCHAMP; } +std::string DataDragon::getImageUrl(const std::string& champid, ImageType type) { + switch(type) { + case ImageType::SQUARE: { + if(getVersion().empty()) { + return {}; + } + + // with version + return getCDNString() + "img/champion/" + champid + ".png"; + } + case ImageType::LOADING: + // no version + return "cdn/img/champion/loading/" + champid + "_0.jpg"; + case ImageType::SPLASH: + // no version + return "cdn/img/champion/splash/" + champid + "_0.jpg"; + } + return {}; +} + std::string DataDragon::getCDNString() const { return "cdn/" + version + "/"; } @@ -247,6 +245,92 @@ QJsonDocument DataDragon::request(const std::string& url) { return parsed; } +void DataDragon::getVersionInternal() { + std::unique_lock lock(cachedatamutex); + + if(!version.empty()) return; + + QJsonDocument jversions = request("api/versions.json"); + if(jversions.isArray()) { + QJsonArray jverarr = jversions.array(); + if(!jverarr.empty()) { + version = jverarr.at(0).toString().toStdString(); + Log::info << "got League version: " << version; + + lock.unlock(); + cachedatacv.notify_all(); + return; + } + } + Log::error << "error parsing version object"; +} + +void DataDragon::getChampsInternal() { + std::unique_lock lock(cachedatamutex); + if(!champs.empty() && version.empty()) { + return; + } + + QJsonDocument jchamps = request(getCDNString() + "data/en_US/champion.json"); + if(jchamps.isObject()) { + QJsonObject obj = jchamps.object(); + auto it = obj.constFind("data"); + if(it != obj.constEnd() && it.value().isObject()) { + QJsonObject jchampsdata = it.value().toObject(); + for(auto champit = jchampsdata.constBegin(); champit != jchampsdata.constEnd(); champit++) { + if(champit.value().isObject()) { + champs.emplace_back(champit.value().toObject()); + } + } + } + } + Log::info << "loaded " << champs.size() << " champs"; + lock.unlock(); + cachedatacv.notify_all(); +} + +void DataDragon::startThread() { + shouldrun = true; + bgthread = std::thread(&DataDragon::threadLoop, this); +} + +void DataDragon::stopThread() { + shouldrun = false; + tasksnotemptycv.notify_all(); +} + +void DataDragon::stopAndJoinThread() { + stopThread(); + + if(bgthread.joinable()) + bgthread.join(); +} + +void DataDragon::threadLoop() { + // init version and champ list + getVersionInternal(); + getChampsInternal(); + + while(shouldrun) { + // wait for a task in the list + Task t; + { + std::unique_lock lock(tasksmutex); + if(tasks.empty()) { + tasksnotemptycv.wait(lock); + } + if(tasks.empty()) continue; + + t = tasks.front(); + tasks.pop_front(); + } + + cv::Mat img = getImage(t.champid, t.type); + t.func(img); + } +} + + std::ostream& operator<<(std::ostream& str, const DataDragon::ChampData& cd) { return str << "[n: " << cd.name << " " << " k: " << cd.key << " id: " << cd.id << "]"; } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index fa5135b..7f0b841 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -3,8 +3,6 @@ #include -#include - static void applySetting(const Config::StageConfig& sc, StageSettings* ss) { ss->setState(sc.enabled); ss->setChampion(QString::fromStdString(sc.champ)); @@ -33,7 +31,7 @@ MainWindow::~MainWindow() { delete ui; } -void MainWindow::closeEvent(QCloseEvent* event) { +void MainWindow::closeEvent([[maybe_unused]] QCloseEvent* event) { conf.save(); } diff --git a/src/stagesettings.cpp b/src/stagesettings.cpp index 32f2ecc..4a0514c 100644 --- a/src/stagesettings.cpp +++ b/src/stagesettings.cpp @@ -1,6 +1,8 @@ #include "stagesettings.h" #include "ui_stagesettings.h" +#include + #include StageSettings::StageSettings(QWidget *parent) : QWidget(parent), ui(new Ui::StageSettings) { @@ -54,10 +56,7 @@ void StageSettings::championChangedinternal(const QString& str) { int count = 0; const DataDragon::ChampData& cd = dd->getBestMatchingChamp(str.toStdString(), &count); if(cd.key != currentdisplayedChampKey) { - img = (cd.key < 0) ? cv::Mat() : dd->getImage(cd.id); - currentdisplayedChampKey = cd.key; - rescaleImage(); - emit championChanged(QString::fromStdString(cd.name)); + applyChampion(cd); } ui->champcount->setText(tr("Champions matched: %1").arg(QString::fromStdString(std::to_string(count)))); } else { @@ -85,3 +84,16 @@ void StageSettings::rescaleImage() { // set a scaled pixmap to a w x h window keeping its aspect ratio ui->champImg->setPixmap(p.scaled(w, h, Qt::KeepAspectRatio)); // } + +void StageSettings::applyChampion(const DataDragon::ChampData& cd) { + dd->getImageAsnyc(cd.id, [this, cd](cv::Mat img) { + this->img = img; + currentdisplayedChampKey = cd.key; + rescaleImage(); + emit championChanged(QString::fromStdString(cd.name)); + }); + + #define QS(A) arg(QString::fromStdString(A)) + ui->champImg->setToolTip(tr("Champion: %1\nType: %2\nTitle: %3\nID: %4").QS(cd.name).QS(cd.partype).QS(cd.title).arg(cd.key)); + #undef QS +} diff --git a/ts/de_DE.ts b/ts/de_DE.ts index ba9ded6..8dcd4f1 100644 --- a/ts/de_DE.ts +++ b/ts/de_DE.ts @@ -46,17 +46,17 @@ Bannen - + League of Legends Client not found! League of Legends Client nicht gefunden! - + Auto-Acceptor started! - + Auto-Acceptor stoped! Auto Acceptor gestoppt! @@ -80,14 +80,25 @@ Champion: - + Enable %1 Aktiviere %1 - + Champions matched: %1 Übereinstimmende Champions: %1 + + + Champion: %1 +Type: %2 +Title: %3 +ID: %4 + Champion: %1 +Typ: %2 +Titel: %3 +ID: %4 + diff --git a/ts/en.ts b/ts/en.ts index e00bb86..59cffa4 100644 --- a/ts/en.ts +++ b/ts/en.ts @@ -46,17 +46,17 @@ Ban - + League of Legends Client not found! League of Legends Client not found! - + Auto-Acceptor started! Auto-Acceptor started! - + Auto-Acceptor stoped! Auto-Acceptor stopped! @@ -80,14 +80,25 @@ Champion: - + Enable %1 Enable %1 - + Champions matched: %1 Champions matched: %1 + + + Champion: %1 +Type: %2 +Title: %3 +ID: %4 + Champion: %1 +Type: %2 +Title: %3 +ID: %4 +