From d97d5ed325ea3cb8ce5328560eafcf93fe703e8d Mon Sep 17 00:00:00 2001 From: Maschell Date: Thu, 20 Feb 2020 02:03:43 +0100 Subject: [PATCH] - Actually displaying installed title now - Try to clean up the async stuff when the vector gets bigger --- .gitignore | 1 + Makefile | 1 + data/images/noGameIcon.png | Bin 0 -> 9362 bytes src/common/common.h | 1 + src/game/GameList.cpp | 202 ++++++++++++++++++++++++++++++++++++ src/game/GameList.h | 102 ++++++++++++++++++ src/gui/GuiIconGrid.cpp | 171 ++++++++++++++++++++++++++++-- src/gui/GuiIconGrid.h | 62 ++++++++++- src/gui/GuiTitleBrowser.h | 15 ++- src/menu/MainWindow.cpp | 39 ++++++- src/menu/MainWindow.h | 11 +- src/utils/AsyncExecutor.cpp | 7 +- src/utils/AsyncExecutor.h | 4 +- 13 files changed, 589 insertions(+), 27 deletions(-) create mode 100644 data/images/noGameIcon.png create mode 100644 src/game/GameList.cpp create mode 100644 src/game/GameList.h diff --git a/.gitignore b/.gitignore index af18655..af8f58e 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ *.rpx build/ src/resources/filelist.h +*.save-failed diff --git a/Makefile b/Makefile index 3653eca..3601fcd 100644 --- a/Makefile +++ b/Makefile @@ -21,6 +21,7 @@ TARGET := $(notdir $(CURDIR)) BUILD := build SOURCES := src \ src/fs \ + src/game \ src/gui \ src/menu \ src/resources \ diff --git a/data/images/noGameIcon.png b/data/images/noGameIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..687efb3a8f2b0fa5b4e3e278028ad66f5c1958ec GIT binary patch literal 9362 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 DWgBv4 literal 0 HcmV?d00001 diff --git a/src/common/common.h b/src/common/common.h index 7cda38b..3e36f03 100644 --- a/src/common/common.h +++ b/src/common/common.h @@ -6,6 +6,7 @@ extern "C" { #endif #define LAUNCHIINE_VERSION "v0.1" +#define META_PATH "/meta" #ifdef __cplusplus } diff --git a/src/game/GameList.cpp b/src/game/GameList.cpp new file mode 100644 index 0000000..1822928 --- /dev/null +++ b/src/game/GameList.cpp @@ -0,0 +1,202 @@ +#include +#include +#include +#include +#include +#include + +#include "utils/AsyncExecutor.h" +#include "GameList.h" +#include "common/common.h" + +#include "fs/DirList.h" +#include "fs/FSUtils.h" +#include "utils/logger.h" +#include "utils/StringTools.h" + +void GameList::clear() { + for (auto const& x : fullGameList) { + if(x != NULL) { + if(x->imageData != NULL) { + DEBUG_FUNCTION_LINE("Delete the image data\n"); + delete x->imageData; + x->imageData = NULL; + } + delete x; + } + } + gameFilter.clear(); + fullGameList.clear(); + filteredList.clear(); + //! Clear memory of the vector completely + std::vector().swap(filteredList); + std::vector().swap(fullGameList); + titleListChanged(this); +} + + +gameInfo * GameList::getGameInfo(uint64_t titleId) const { + for (uint32_t i = 0; i < filteredList.size(); ++i) { + if(titleId == filteredList[i]->titleId) + return filteredList[i]; + } + + return NULL; +} + +extern "C" int ACPGetTitleMetaXml(uint64_t titleid, ACPMetaXml*); +int32_t GameList::readGameList() { + // Clear list + for (auto const& x : fullGameList) { + delete x; + } + + fullGameList.clear(); + //! Clear memory of the vector completely + std::vector().swap(fullGameList); + + int32_t cnt = 0; + + MCPError mcp = MCP_Open(); + if (mcp < 0) { + return 0; + } + + MCPError titleCount = MCP_TitleCount(mcp); + if (titleCount < 0) { + MCP_Close(mcp); + return 0; + } + + std::vector titles(titleCount); + uint32_t realTitleCount; + + MCPError err = MCP_TitleList(mcp, &realTitleCount, titles.data(), titles.size() * sizeof(decltype(titles)::value_type)); + if (err < 0) { + MCP_Close(mcp); + return 0; + } + if (realTitleCount != titles.size()) { + titles.resize(realTitleCount); + } + + for (auto title_candidate : titles) { + if(true || (title_candidate.titleId & 0xFFFFFFFF00000000L) == 0x0005000000000000L) { + gameInfo* newGameInfo = new gameInfo; + newGameInfo->titleId = title_candidate.titleId; + newGameInfo->gamePath = title_candidate.path; + newGameInfo->name = ""; + newGameInfo->imageData = NULL; + DCFlushRange(newGameInfo, sizeof(gameInfo)); + fullGameList.push_back(newGameInfo); + titleAdded(newGameInfo); + cnt++; + } + } + + return cnt; +} + +void GameList::loadIcons() { + for (int i = 0; i < this->size(); i++) { + gameInfo * newHeader = this->at(i); + + ACPMetaXml* meta = (ACPMetaXml*)calloc(1, 0x4000); //TODO fix wut + if(meta) { + auto acp = ACPGetTitleMetaXml(newHeader->titleId, meta); + if(acp >= 0) { + newHeader->name = meta->shortname_en; + } + free(meta); + } + + if(newHeader->imageData == NULL) { + std::string filepath = "fs:" + newHeader->gamePath + META_PATH + "/iconTex.tga"; + uint8_t *buffer = NULL; + uint32_t bufferSize = 0; + int iResult = FSUtils::LoadFileToMem(filepath.c_str(), &buffer, &bufferSize); + + if(iResult > 0) { + GuiImageData * imageData = new GuiImageData(buffer, bufferSize, GX2_TEX_CLAMP_MODE_MIRROR); + if(imageData) { + newHeader->imageData = imageData; + } + + //! free original image buffer which is converted to texture now and not needed anymore + free(buffer); + } + } + DCFlushRange(newHeader, sizeof(gameInfo)); + titleUpdated(newHeader); + } +} + +void GameList::internalFilterList(std::vector &fullList) { + for (uint32_t i = 0; i < fullList.size(); ++i) { + gameInfo *header = fullList[i]; + + //! TODO: do filtering as needed + + filteredList.push_back(header); + } +} + +int32_t GameList::filterList(const char * filter) { + if(filter) { + gameFilter = filter; + } + + if(fullGameList.size() == 0) { + readGameList(); + } + + filteredList.clear(); + + // Filter current game list if selected + internalFilterList(fullGameList); + + sortList(); + + titleListChanged(this); + + AsyncExecutor::execute([&] { loadIcons();}); + + return filteredList.size(); +} + +void GameList::internalLoadUnfiltered(std::vector & fullList) { + for (uint32_t i = 0; i < fullList.size(); ++i) { + gameInfo *header = fullList[i]; + + filteredList.push_back(header); + } +} + +int32_t GameList::loadUnfiltered() { + if(fullGameList.size() == 0) { + readGameList(); + } + + gameFilter.clear(); + filteredList.clear(); + + // Filter current game list if selected + internalLoadUnfiltered(fullGameList); + + sortList(); + + AsyncExecutor::execute([&] { loadIcons();}); + + titleListChanged(this); + + return filteredList.size(); +} + +void GameList::sortList() { + std::sort(filteredList.begin(), filteredList.end(), nameSortCallback); +} + +bool GameList::nameSortCallback(const gameInfo *a, const gameInfo *b) { + return (strcasecmp(((gameInfo *) a)->name.c_str(), ((gameInfo *) b)->name.c_str()) < 0); +} + diff --git a/src/game/GameList.h b/src/game/GameList.h new file mode 100644 index 0000000..b8c206d --- /dev/null +++ b/src/game/GameList.h @@ -0,0 +1,102 @@ +#ifndef GAME_LIST_H_ +#define GAME_LIST_H_ + +#include +#include +#include +#include +#include + +typedef struct _gameInfo { + uint64_t titleId; + std::string name; + std::string gamePath; + GuiImageData * imageData; +} gameInfo; + +class GameList { +public: + GameList() : selectedGame(0) { }; + ~GameList() { clear(); }; + + int32_t size() const { + return filteredList.size(); + } + int32_t gameCount() const { + return fullGameList.size(); + } + int32_t filterList(const char * gameFilter = NULL); + int32_t loadUnfiltered(); + + gameInfo * at(int32_t i) const { + return operator[](i); + } + gameInfo * operator[](int32_t i) const { + if (i < 0 || i >= (int32_t) filteredList.size()) + return NULL; + return filteredList[i]; + } + gameInfo * getGameInfo(uint64_t titleId) const; + + const char * getCurrentFilter() const { + return gameFilter.c_str(); + } + void sortList(); + void clear(); + bool operator!() const { + return (fullGameList.size() == 0); + } + + //! Gamelist scrolling operators + int32_t operator+=(int32_t i) { + return (selectedGame = (selectedGame+i) % filteredList.size()); + } + int32_t operator-=(int32_t i) { + return (selectedGame = (selectedGame-i+filteredList.size()) % filteredList.size()); + } + int32_t operator++() { + return (selectedGame = (selectedGame+1) % filteredList.size()); + } + int32_t operator--() { + return (selectedGame = (selectedGame-1+filteredList.size()) % filteredList.size()); + } + int32_t operator++(int32_t i) { + return operator++(); + } + int32_t operator--(int32_t i) { + return operator--(); + } + gameInfo * GetCurrentSelected() const { + return operator[](selectedGame); + } + + std::vector & getfilteredList(void) { + return filteredList; + } + std::vector & getFullGameList(void) { + return fullGameList; + } + + sigslot::signal1 titleListChanged; + sigslot::signal1 titleUpdated; + sigslot::signal1 titleAdded; +protected: + + int32_t readGameList(); + + void internalFilterList(std::vector & fullList); + void internalLoadUnfiltered(std::vector & fullList); + + void loadIcons(); + + static bool nameSortCallback(const gameInfo *a, const gameInfo *b); + + static GameList *gameListInstance; + + std::string gameFilter; + int32_t selectedGame; + std::vector filteredList; + std::vector fullGameList; +}; + +#endif diff --git a/src/gui/GuiIconGrid.cpp b/src/gui/GuiIconGrid.cpp index 116d638..8b19dad 100644 --- a/src/gui/GuiIconGrid.cpp +++ b/src/gui/GuiIconGrid.cpp @@ -16,31 +16,155 @@ ****************************************************************************/ #include #include +#include #include "common/common.h" #include "Application.h" #include +#include "utils/logger.h" -GuiIconGrid::GuiIconGrid(int32_t w, int32_t h, int32_t GameIndex) +GuiIconGrid::GuiIconGrid(int32_t w, int32_t h, uint64_t GameIndex) : GuiTitleBrowser(w, h, GameIndex), - particleBgImage(w, h, 50, 60.0f, 90.0f, 0.6f, 1.0f) { + particleBgImage(w, h, 50, 60.0f, 90.0f, 0.6f, 1.0f) + , touchTrigger(GuiTrigger::CHANNEL_1, GuiTrigger::VPAD_TOUCH) + , wpadTouchTrigger(GuiTrigger::CHANNEL_2 | GuiTrigger::CHANNEL_3 | GuiTrigger::CHANNEL_4 | GuiTrigger::CHANNEL_5, GuiTrigger::BUTTON_A) + , noIcon(Resources::GetFile("noGameIcon.png"), Resources::GetFileSize("noGameIcon.png"), GX2_TEX_CLAMP_MODE_MIRROR) { append(&particleBgImage); - + selectedGame = GameIndex; + listOffset = selectedGame / (MAX_COLS * MAX_ROWS); + targetLeftPosition = -listOffset * getWidth(); + currentLeftPosition = targetLeftPosition; } GuiIconGrid::~GuiIconGrid() { - + containerMutex.lock(); + for (auto const& x : gameInfoContainers) { + remove(x.second->button); + delete x.second; + } + gameInfoContainers.clear(); + containerMutex.unlock(); } -void GuiIconGrid::setSelectedGame(int32_t idx) { +void GuiIconGrid::setSelectedGame(uint64_t idx) { this->selectedGame = idx; } -int32_t GuiIconGrid::getSelectedGame(void) { +uint64_t GuiIconGrid::getSelectedGame(void) { return selectedGame; } +void GuiIconGrid::OnGameTitleListUpdated(GameList * gameList) { + containerMutex.lock(); + for(int32_t i = 0; i < gameList->size(); i++) { + gameInfo * info = gameList->at(i); + GameInfoContainer * container = NULL; + + for (auto const& x : gameInfoContainers) { + if(info->titleId == x.first) { + container = x.second; + break; + } + } + + if(container == NULL) { + OnGameTitleAdded(info); + } + } + containerMutex.unlock(); + bUpdatePositions = true; +} + +void GuiIconGrid::OnLaunchClick(GuiButton *button, const GuiController *controller, GuiTrigger *trigger) { + //! do not auto launch when wiimote is pointing to screen and presses A + //if((trigger == &buttonATrigger) && (controller->chan & (GuiTrigger::CHANNEL_2 | GuiTrigger::CHANNEL_3 | GuiTrigger::CHANNEL_4 | GuiTrigger::CHANNEL_5)) && controller->data.validPointer) { + // return; + //} + DEBUG_FUNCTION_LINE("Tried to launch %s (%016llX)\n", gameInfoContainers[getSelectedGame()]->info->name.c_str(),getSelectedGame()); + gameLaunchClicked(this, getSelectedGame()); +} + + +void GuiIconGrid::OnGameButtonClick(GuiButton *button, const GuiController *controller, GuiTrigger *trigger) { + containerMutex.lock(); + for (auto const& x : gameInfoContainers) { + if(x.second->button == button) { + if(selectedGame == (x.second->info->titleId)) { + if(gameLaunchTimer < 30) + OnLaunchClick(button, controller, trigger); + } else { + setSelectedGame(x.second->info->titleId); + gameSelectionChanged(this, selectedGame); + } + gameLaunchTimer = 0; + break; + } + } + containerMutex.unlock(); +} + +void GuiIconGrid::OnGameTitleAdded(gameInfo * info) { + DEBUG_FUNCTION_LINE("Adding %016llX\n", info->titleId); + GuiImage * image = new GuiImage(&noIcon); + GuiButton * button = new GuiButton(noIcon.getWidth(), noIcon.getHeight()); + button->setImage(image); + button->setPosition(0, 0); + button->setEffectGrow(); + button->setTrigger(&touchTrigger); + button->setTrigger(&wpadTouchTrigger); + button->setSoundClick(buttonClickSound); + //button->setClickable( (idx < gameList->size()) ); + //button->setSelectable( (idx < gameList->size()) ); + button->clicked.connect(this, &GuiIconGrid::OnGameButtonClick); + + GameInfoContainer * container = new GameInfoContainer(button, image, info); + containerMutex.lock(); + gameInfoContainers[info->titleId] = container; + containerMutex.unlock(); + this->append(button); + + bUpdatePositions = true; +} +void GuiIconGrid::OnGameTitleUpdated(gameInfo * info) { + DEBUG_FUNCTION_LINE("Updating infos of %016llX\n", info->titleId); + GameInfoContainer * container = NULL; + containerMutex.lock(); + for (auto const& x : gameInfoContainers) { + if(info->titleId == x.first) { + container = x.second; + break; + } + } + + if(container != NULL) { + container->updateImageData(); + } + containerMutex.unlock(); + bUpdatePositions = true; +} void GuiIconGrid::process() { + if(currentLeftPosition < targetLeftPosition) { + currentLeftPosition += 35; + + if(currentLeftPosition > targetLeftPosition) + currentLeftPosition = targetLeftPosition; + + bUpdatePositions = true; + } else if(currentLeftPosition > targetLeftPosition) { + currentLeftPosition -= 35; + + if(currentLeftPosition < targetLeftPosition) + currentLeftPosition = targetLeftPosition; + + bUpdatePositions = true; + } + + if(bUpdatePositions) { + bUpdatePositions = false; + updateButtonPositions(); + } + gameLaunchTimer++; + GuiFrame::process(); } @@ -48,6 +172,37 @@ void GuiIconGrid::update(GuiController * c) { GuiFrame::update(c); } -void GuiIconGrid::draw(CVideo *pVideo) { - GuiFrame::draw(pVideo); +void GuiIconGrid::updateButtonPositions() { + int32_t col = 0, row = 0, listOff = 0; + + int i = 0; + containerMutex.lock(); + for (auto const& x : gameInfoContainers) { + + listOff = i / (MAX_COLS * MAX_ROWS); + + float posX = currentLeftPosition + listOff * width + ( col * (noIcon.getWidth() + noIcon.getWidth() * 0.5f) - (MAX_COLS * 0.5f - 0.5f) * (noIcon.getWidth() + noIcon.getWidth() * 0.5f) ); + float posY = -row * (noIcon.getHeight() + noIcon.getHeight() * 0.5f) + (MAX_ROWS * 0.5f - 0.5f) * (noIcon.getHeight() + noIcon.getHeight() * 0.5f) + 30.0f; + + if(x.second->button != NULL) { + x.second->button->setPosition(posX, posY); + } + + col++; + if(col >= MAX_COLS) { + col = 0; + row++; + } + if(row >= MAX_ROWS) + row = 0; + + i++; + } + containerMutex.unlock(); +} + +void GuiIconGrid::draw(CVideo *pVideo) { + containerMutex.lock(); + GuiFrame::draw(pVideo); + containerMutex.unlock(); } diff --git a/src/gui/GuiIconGrid.h b/src/gui/GuiIconGrid.h index 4335980..714c3c4 100644 --- a/src/gui/GuiIconGrid.h +++ b/src/gui/GuiIconGrid.h @@ -19,25 +19,77 @@ #include #include "gui/GuiTitleBrowser.h" #include +#include "utils/AsyncExecutor.h" +#include "utils/logger.h" class GuiIconGrid : public GuiTitleBrowser, public sigslot::has_slots<> { public: - GuiIconGrid(int32_t w, int32_t h, int32_t GameIndex); + GuiIconGrid(int32_t w, int32_t h, uint64_t selectedTitleId); virtual ~GuiIconGrid(); - void setSelectedGame(int32_t idx); - int32_t getSelectedGame(void); + void setSelectedGame(uint64_t idx); + uint64_t getSelectedGame(void); void update(GuiController * t); void draw(CVideo *pVideo); void process(); + + void OnGameTitleListUpdated(GameList * list); + void OnAddGameTitle(gameInfo * info); + void OnGameTitleUpdated(gameInfo * info); + void OnGameTitleAdded(gameInfo * info); private: static const int32_t MAX_ROWS = 3; static const int32_t MAX_COLS = 5; GuiSound *buttonClickSound; - int32_t selectedGame = 0; - GuiParticleImage particleBgImage; + + GuiTrigger touchTrigger; + GuiTrigger wpadTouchTrigger; + + GuiImageData noIcon; + + void OnLaunchClick(GuiButton *button, const GuiController *controller, GuiTrigger *trigger); + void OnGameButtonClick(GuiButton *button, const GuiController *controller, GuiTrigger *trigger); + void updateButtonPositions(); + + int32_t listOffset; + uint64_t selectedGame; + int32_t currentLeftPosition; + int32_t targetLeftPosition; + uint32_t gameLaunchTimer; + bool bUpdatePositions = false; + + class GameInfoContainer { + public: + GameInfoContainer(GuiButton* button, GuiImage * image, gameInfo * info) { + this->image = image; + this->info = info; + this->button = button; + } + + ~GameInfoContainer() { + if(button != NULL) { + AsyncExecutor::pushForDelete(button); + } + if(image != NULL) { + AsyncExecutor::pushForDelete(image); + } + } + + void updateImageData() { + if(image != NULL && info != NULL && info->imageData != NULL) { + image->setImageData(info->imageData); + } + } + + GuiImage * image; + gameInfo * info; + GuiButton * button; + }; + + CMutex containerMutex; + std::map gameInfoContainers; }; diff --git a/src/gui/GuiTitleBrowser.h b/src/gui/GuiTitleBrowser.h index 8866340..cc72240 100644 --- a/src/gui/GuiTitleBrowser.h +++ b/src/gui/GuiTitleBrowser.h @@ -2,15 +2,20 @@ #include #include +#include "game/GameList.h" class GuiTitleBrowser : public GuiFrame { public: - GuiTitleBrowser(int32_t w, int32_t h, int32_t GameIndex) : GuiFrame(w, h) {} + GuiTitleBrowser(int32_t w, int32_t h, uint64_t GameIndex) : GuiFrame(w, h) {} virtual ~GuiTitleBrowser() {} - virtual void setSelectedGame(int32_t idx) = 0; - virtual int32_t getSelectedGame(void) = 0; + virtual void setSelectedGame(uint64_t idx) = 0; + virtual uint64_t getSelectedGame(void) = 0; - sigslot::signal2 gameLaunchClicked; - sigslot::signal2 gameSelectionChanged; + virtual void OnGameTitleListUpdated(GameList * list) = 0; + virtual void OnGameTitleUpdated(gameInfo * info) = 0; + virtual void OnGameTitleAdded(gameInfo * info) = 0; + + sigslot::signal2 gameLaunchClicked; + sigslot::signal2 gameSelectionChanged; }; diff --git a/src/menu/MainWindow.cpp b/src/menu/MainWindow.cpp index 1815a69..95d1aac 100644 --- a/src/menu/MainWindow.cpp +++ b/src/menu/MainWindow.cpp @@ -40,9 +40,15 @@ MainWindow::MainWindow(int32_t w, int32_t h) pointerValid[i] = false; } SetupMainView(); + gameList.titleListChanged.connect(this, &MainWindow::OnGameTitleListChanged); + gameList.titleUpdated.connect(this, &MainWindow::OnGameTitleUpdated); + gameList.titleAdded.connect(this, &MainWindow::OnGameTitleAdded); + AsyncExecutor::execute([&] {gameList.loadUnfiltered();}); + } MainWindow::~MainWindow() { + gameList.titleListChanged.disconnect(this); while(!tvElements.empty()) { delete tvElements[0]; remove(tvElements[0]); @@ -103,6 +109,31 @@ void MainWindow::process() { } } +void MainWindow::OnGameTitleListChanged(GameList * list) { + currentTvFrame->OnGameTitleListUpdated(list); + if(currentTvFrame != currentDrcFrame) { + currentDrcFrame->OnGameTitleListUpdated(list); + } +} + +void MainWindow::OnGameTitleUpdated(gameInfo * info) { + currentTvFrame->OnGameTitleUpdated(info); + if(currentTvFrame != currentDrcFrame) { + currentDrcFrame->OnGameTitleUpdated(info); + } +} + +void MainWindow::OnGameTitleAdded(gameInfo * info) { + DEBUG_FUNCTION_LINE("%08X\n", info); + if(info == NULL) { + return; + } + currentTvFrame->OnGameTitleAdded(info); + if(currentTvFrame != currentDrcFrame) { + currentDrcFrame->OnGameTitleAdded(info); + } +} + void MainWindow::update(GuiController *controller) { //! dont read behind the initial elements in case one was added //uint32_t tvSize = tvElements.size(); @@ -179,6 +210,7 @@ void MainWindow::SetupMainView() { currentTvFrame->setEffect(EFFECT_FADE, 10, 255); currentTvFrame->setState(GuiElement::STATE_DISABLED); currentTvFrame->effectFinished.connect(this, &MainWindow::OnOpenEffectFinish); + appendTv(currentTvFrame); currentDrcFrame = new GuiIconGrid(width, height,0); @@ -186,7 +218,6 @@ void MainWindow::SetupMainView() { currentDrcFrame->setState(GuiElement::STATE_DISABLED); currentDrcFrame->effectFinished.connect(this, &MainWindow::OnOpenEffectFinish); - if(currentTvFrame != currentDrcFrame) { currentDrcFrame->setEffect(EFFECT_FADE, 10, 255); currentDrcFrame->setState(GuiElement::STATE_DISABLED); @@ -288,11 +319,11 @@ void MainWindow::OnCloseEffectFinish(GuiElement *element) { AsyncExecutor::pushForDelete(element); } -void MainWindow::OnSettingsButtonClicked(GuiElement *element){ +void MainWindow::OnSettingsButtonClicked(GuiElement *element) { } -void MainWindow::OnGameSelectionChange(GuiTitleBrowser *element, int32_t selectedIdx) { +void MainWindow::OnGameSelectionChange(GuiTitleBrowser *element, uint64_t selectedIdx) { if(!currentDrcFrame || !currentTvFrame) return; @@ -303,6 +334,6 @@ void MainWindow::OnGameSelectionChange(GuiTitleBrowser *element, int32_t selecte } } -void MainWindow::OnGameLaunch(GuiTitleBrowser *element, int32_t selectedIdx) { +void MainWindow::OnGameLaunch(GuiTitleBrowser *element, uint64_t selectedIdx) { } diff --git a/src/menu/MainWindow.h b/src/menu/MainWindow.h index 5bb2f9a..c2387ae 100644 --- a/src/menu/MainWindow.h +++ b/src/menu/MainWindow.h @@ -20,6 +20,7 @@ #include #include #include +#include "game/GameList.h" #include "system/CMutex.h" #include "gui/GuiTitleBrowser.h" #include "MainDrcButtonsFrame.h" @@ -116,13 +117,17 @@ private: void OnOpenEffectFinish(GuiElement *element); void OnCloseEffectFinish(GuiElement *element); - void OnGameLaunch(GuiTitleBrowser *element, int32_t gameIdx); - void OnGameSelectionChange(GuiTitleBrowser *element, int32_t selectedIdx); + void OnGameLaunch(GuiTitleBrowser *element, uint64_t gameIdx); + void OnGameSelectionChange(GuiTitleBrowser *element, uint64_t selectedIdx); void OnSettingsButtonClicked(GuiElement *element); void OnLayoutSwitchClicked(GuiElement *element); void OnLayoutSwitchEffectFinish(GuiElement *element); + void OnGameTitleListChanged(GameList * list); + void OnGameTitleUpdated(gameInfo * info); + void OnGameTitleAdded(gameInfo * info); + int32_t width, height; std::vector drcElements; std::vector tvElements; @@ -138,6 +143,8 @@ private: GuiImage *pointerImg[4]; bool pointerValid[4]; + GameList gameList; + CMutex guiMutex; }; diff --git a/src/utils/AsyncExecutor.cpp b/src/utils/AsyncExecutor.cpp index 2458ae5..edec646 100644 --- a/src/utils/AsyncExecutor.cpp +++ b/src/utils/AsyncExecutor.cpp @@ -11,5 +11,10 @@ void AsyncExecutor::execute(std::function func) { if(!instance) { instance = new AsyncExecutor(); } - instance->elements.push_back(std::async(std::launch::async,func)); + instance->elements.push(std::async(std::launch::async,func)); + if(instance->elements.size() >= 25){ + //DEBUG_FUNCTION_LINE("Wait on queue %d\n",instance->elements.size()); + instance->elements.front().get(); + instance->elements.pop(); + } } diff --git a/src/utils/AsyncExecutor.h b/src/utils/AsyncExecutor.h index 4b70be3..599d6ec 100644 --- a/src/utils/AsyncExecutor.h +++ b/src/utils/AsyncExecutor.h @@ -1,6 +1,6 @@ #pragma once -#include +#include #include #include @@ -22,5 +22,5 @@ private: AsyncExecutor() {} ~AsyncExecutor() {} - std::vector> elements; + std::queue> elements; };