Moved quick selector letter index generation to gamelist sorting loop. This increases (non-optimized) sort time with around 1,5 - 4,5% but the game option GUI opens way faster now due to the caching of the letter index. Also made the quick selector more intuitive.

This commit is contained in:
Leon Styhre 2020-06-11 21:08:48 +02:00
parent 48d70e9f99
commit 7cefe6a2bd
5 changed files with 85 additions and 89 deletions

View file

@ -310,38 +310,77 @@ void FileData::removeChild(FileData* file)
void FileData::sort(ComparisonFunction& comparator, bool ascending) void FileData::sort(ComparisonFunction& comparator, bool ascending)
{ {
mFirstLetterIndex.clear();
std::stable_sort(mChildren.begin(), mChildren.end(), comparator); std::stable_sort(mChildren.begin(), mChildren.end(), comparator);
for (auto it = mChildren.cbegin(); it != mChildren.cend(); it++) { for (auto it = mChildren.cbegin(); it != mChildren.cend(); it++) {
// Build mFirstLetterIndex.
const char firstChar = toupper((*it)->getSortName().front());
mFirstLetterIndex.push_back(std::string(1, firstChar));
// Iterate through any child folders.
if ((*it)->getChildren().size() > 0) if ((*it)->getChildren().size() > 0)
(*it)->sort(comparator, ascending); (*it)->sort(comparator, ascending);
} }
// Sort and make each entry unique in mFirstLetterIndex.
std::sort(mFirstLetterIndex.begin(), mFirstLetterIndex.end());
auto last = std::unique(mFirstLetterIndex.begin(), mFirstLetterIndex.end());
mFirstLetterIndex.erase(last, mFirstLetterIndex.end());
if (!ascending) if (!ascending)
std::reverse(mChildren.begin(), mChildren.end()); std::reverse(mChildren.begin(), mChildren.end());
} }
void FileData::sortFavoritesOnTop(ComparisonFunction& comparator, bool ascending) void FileData::sortFavoritesOnTop(ComparisonFunction& comparator, bool ascending)
{ {
mFirstLetterIndex.clear();
std::vector<FileData*> mChildrenFavorites; std::vector<FileData*> mChildrenFavorites;
std::vector<FileData*> mChildrenOthers; std::vector<FileData*> mChildrenOthers;
for (unsigned int i = 0; i < mChildren.size(); i++) { for (unsigned int i = 0; i < mChildren.size(); i++) {
if (mChildren[i]->getFavorite()) if (mChildren[i]->getFavorite()) {
mChildrenFavorites.push_back(mChildren[i]); mChildrenFavorites.push_back(mChildren[i]);
else }
else {
mChildrenOthers.push_back(mChildren[i]); mChildrenOthers.push_back(mChildren[i]);
// Build mFirstLetterIndex.
const char firstChar = toupper(mChildren[i]->getSortName().front());
mFirstLetterIndex.push_back(std::string(1, firstChar));
}
} }
// If there are only favorites in the gamelist, it makes sense to still generate
// a letter index. For instance to be able to quick jump in the 'favorites'
// collection. Doing this additional work here only for the applicable gamelists is
// probably faster than building a redundant index for all gamelists during sorting.
if (mChildrenOthers.size() == 0 && mChildrenFavorites.size() > 0) {
for (unsigned int i = 0; i < mChildren.size(); i++) {
const char firstChar = toupper(mChildren[i]->getSortName().front());
mFirstLetterIndex.push_back(std::string(1, firstChar));
}
}
// Sort and make each entry unique in mFirstLetterIndex.
std::sort(mFirstLetterIndex.begin(), mFirstLetterIndex.end());
auto last = std::unique(mFirstLetterIndex.begin(), mFirstLetterIndex.end());
mFirstLetterIndex.erase(last, mFirstLetterIndex.end());
// If there were at least one favorite in the gamelist, insert the favorite
// unicode character in the first position.
if (mChildrenOthers.size() > 0 && mChildrenFavorites.size() > 0)
mFirstLetterIndex.insert(mFirstLetterIndex.begin(), FAVORITE_CHAR);
// Sort favorite games and the other games separately. // Sort favorite games and the other games separately.
std::stable_sort(mChildrenFavorites.begin(), mChildrenFavorites.end(), comparator); std::stable_sort(mChildrenFavorites.begin(), mChildrenFavorites.end(), comparator);
std::stable_sort(mChildrenOthers.begin(), mChildrenOthers.end(), comparator); std::stable_sort(mChildrenOthers.begin(), mChildrenOthers.end(), comparator);
// Iterate through any child folders.
for (auto it = mChildrenFavorites.cbegin(); it != mChildrenFavorites.cend(); it++) { for (auto it = mChildrenFavorites.cbegin(); it != mChildrenFavorites.cend(); it++) {
if ((*it)->getChildren().size() > 0) if ((*it)->getChildren().size() > 0)
(*it)->sortFavoritesOnTop(comparator, ascending); (*it)->sortFavoritesOnTop(comparator, ascending);
} }
// Iterate through any child folders.
for (auto it = mChildrenOthers.cbegin(); it != mChildrenOthers.cend(); it++) { for (auto it = mChildrenOthers.cbegin(); it != mChildrenOthers.cend(); it++) {
if ((*it)->getChildren().size() > 0) if ((*it)->getChildren().size() > 0)
(*it)->sortFavoritesOnTop(comparator, ascending); (*it)->sortFavoritesOnTop(comparator, ascending);

View file

@ -57,6 +57,8 @@ public:
inline const std::vector<FileData*>& getChildren() const { return mChildren; } inline const std::vector<FileData*>& getChildren() const { return mChildren; }
inline SystemData* getSystem() const { return mSystem; } inline SystemData* getSystem() const { return mSystem; }
inline SystemEnvironmentData* getSystemEnvData() const { return mEnvData; } inline SystemEnvironmentData* getSystemEnvData() const { return mEnvData; }
const std::vector<std::string>& getFirstLetterIndex() const
{ return mFirstLetterIndex; };
static const std::string getMediaDirectory(); static const std::string getMediaDirectory();
virtual const std::string getMediafilePath( virtual const std::string getMediafilePath(
std::string subdirectory, std::string mediatype) const; std::string subdirectory, std::string mediatype) const;
@ -87,8 +89,7 @@ public:
virtual FileData* getSourceFileData(); virtual FileData* getSourceFileData();
inline std::string getSystemName() const { return mSystemName; }; inline std::string getSystemName() const { return mSystemName; };
// Returns our best guess at the "real" name for this file // Returns our best guess at the "real" name for this file.
// (will attempt to perform MAME name translation).
std::string getDisplayName() const; std::string getDisplayName() const;
// As above, but also remove parenthesis. // As above, but also remove parenthesis.
@ -132,6 +133,9 @@ private:
std::unordered_map<std::string,FileData*> mChildrenByFilename; std::unordered_map<std::string,FileData*> mChildrenByFilename;
std::vector<FileData*> mChildren; std::vector<FileData*> mChildren;
std::vector<FileData*> mFilteredChildren; std::vector<FileData*> mFilteredChildren;
std::vector<std::string> mFirstLetterIndex;
const std::string FAVORITE_CHAR = "\uF005";
}; };
class CollectionFileData : public FileData class CollectionFileData : public FileData

View file

@ -74,14 +74,14 @@ void GuiCollectionSystemsOptions::initializeMenu()
mMenu.addRow(row); mMenu.addRow(row);
bundleCustomCollections = std::make_shared<SwitchComponent>(mWindow);
bundleCustomCollections->setState(Settings::getInstance()->getBool("UseCustomCollectionsSystem"));
mMenu.addWithLabel("GROUP UNTHEMED CUSTOM COLLECTIONS", bundleCustomCollections);
sortFavFirstCustomSwitch = std::make_shared<SwitchComponent>(mWindow); sortFavFirstCustomSwitch = std::make_shared<SwitchComponent>(mWindow);
sortFavFirstCustomSwitch->setState(Settings::getInstance()->getBool("FavFirstCustom")); sortFavFirstCustomSwitch->setState(Settings::getInstance()->getBool("FavFirstCustom"));
mMenu.addWithLabel("SORT FAVORITES ON TOP FOR CUSTOM COLLECTIONS", sortFavFirstCustomSwitch); mMenu.addWithLabel("SORT FAVORITES ON TOP FOR CUSTOM COLLECTIONS", sortFavFirstCustomSwitch);
bundleCustomCollections = std::make_shared<SwitchComponent>(mWindow);
bundleCustomCollections->setState(Settings::getInstance()->getBool("UseCustomCollectionsSystem"));
mMenu.addWithLabel("GROUP UNTHEMED CUSTOM COLLECTIONS", bundleCustomCollections);
toggleSystemNameInCollections = std::make_shared<SwitchComponent>(mWindow); toggleSystemNameInCollections = std::make_shared<SwitchComponent>(mWindow);
toggleSystemNameInCollections->setState(Settings::getInstance()->getBool("CollectionShowSystemInfo")); toggleSystemNameInCollections->setState(Settings::getInstance()->getBool("CollectionShowSystemInfo"));
mMenu.addWithLabel("SHOW SYSTEM NAMES IN COLLECTIONS", toggleSystemNameInCollections); mMenu.addWithLabel("SHOW SYSTEM NAMES IN COLLECTIONS", toggleSystemNameInCollections);

View file

@ -47,79 +47,30 @@ GuiGamelistOptions::GuiGamelistOptions(
mFavoritesSorting = Settings::getInstance()->getBool("FavoritesFirst"); mFavoritesSorting = Settings::getInstance()->getBool("FavoritesFirst");
if (!fromPlaceholder) { if (!fromPlaceholder) {
// Jump to letter. // Jump to letter quick selector.
row.elements.clear(); row.elements.clear();
// Define supported character range. // The letter index is generated in FileData during game system sorting.
// This range includes all numbers, capital letters, and most reasonable symbols. mFirstLetterIndex = file->getParent()->getFirstLetterIndex();
char startChar = '!';
char endChar = '_';
char curChar = (char)toupper(getGamelist()->getCursor()->getSortName()[0]); // Set the quick selector to the first character of the selected game.
if (curChar < startChar || curChar > endChar) if (mFavoritesSorting && file->getFavorite() && mFirstLetterIndex.front() == FAVORITE_CHAR)
curChar = startChar; mCurrentFirstCharacter = FAVORITE_CHAR;
else
mCurrentFirstCharacter = toupper(file->getSortName().front());
mJumpToLetterList = std::make_shared<LetterList>(mWindow, getHelpStyle(), mJumpToLetterList = std::make_shared<LetterList>(mWindow, getHelpStyle(),
"JUMP TO...", false); "JUMP TO...", false);
if (mFavoritesSorting && system->getName() != "favorites" && // Populate the quick selector.
system->getName() != "recent") { for (unsigned int i = 0; i < mFirstLetterIndex.size(); i++) {
// Check whether the first game in the list is a favorite, if it's mJumpToLetterList->add(mFirstLetterIndex[i], mFirstLetterIndex[i], 0);
// not, then there are no favorites currently visible in this gamelist. if (mFirstLetterIndex[i] == mCurrentFirstCharacter)
if (getGamelist()->getCursor()->getParent()->getChildrenListToDisplay()[0]-> mJumpToLetterList->selectEntry(i);
getFavorite()) {
if (getGamelist()->getCursor()->getFavorite())
mJumpToLetterList->add(FAVORITE_CHAR, FAVORITE_CHAR, 1);
else
mJumpToLetterList->add(FAVORITE_CHAR, FAVORITE_CHAR, 0);
}
} }
for (char c = startChar; c <= endChar; c++) {
// Check if c is a valid first letter in the current list.
const std::vector<FileData*>& files =
getGamelist()->getCursor()->getParent()->getChildrenListToDisplay();
for (auto file : files) {
char candidate = (char)toupper(file->getSortName()[0]);
if (c == candidate) {
// If the game is a favorite, continue to the next entry in the list.
if (mFavoritesSorting && system->getName() != "favorites" &&
system->getName() != "recent" && file->getFavorite())
continue;
// If the currently selected game is a favorite, set the character
// as not selected so we don't get two current positions.
if (mFavoritesSorting && system->getName() != "favorites" &&
system->getName() != "recent" &&
getGamelist()->getCursor()->getFavorite())
mJumpToLetterList->add(std::string(1, c), std::string(1, c), 0);
else
mJumpToLetterList->add(std::string(1, c), std::string(1, c), c == curChar);
break;
}
}
}
row.addElement(std::make_shared<TextComponent>(
mWindow, "JUMP TO...", Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true);
row.addElement(mJumpToLetterList, false);
row.input_handler = [&](InputConfig* config, Input input) {
if (config->isMappedTo("a", input) && input.value) {
NavigationSounds::getInstance()->playThemeNavigationSound(SCROLLSOUND);
if (mJumpToLetterList->getSelected() == FAVORITE_CHAR)
jumpToFirstRow();
else
jumpToLetter();
return true;
}
else if (mJumpToLetterList->input(config, input)) {
return true;
}
return false;
};
if (system->getName() != "recent") if (system->getName() != "recent")
mMenu.addRow(row); mMenu.addWithLabel("JUMP TO..", mJumpToLetterList);
// Sort list by selected sort type (persistent throughout the program session). // Sort list by selected sort type (persistent throughout the program session).
mListSort = std::make_shared<SortList>(mWindow, getHelpStyle(), "SORT GAMES BY", false); mListSort = std::make_shared<SortList>(mWindow, getHelpStyle(), "SORT GAMES BY", false);
@ -201,26 +152,30 @@ GuiGamelistOptions::~GuiGamelistOptions()
// If a new sorting type was selected, then sort and update mSortTypeString for the system. // If a new sorting type was selected, then sort and update mSortTypeString for the system.
if ((*mListSort->getSelected()).description != root->getSortTypeString()) { if ((*mListSort->getSelected()).description != root->getSortTypeString()) {
NavigationSounds::getInstance()->playThemeNavigationSound(SCROLLSOUND);
// This will also recursively sort children. // This will also recursively sort children.
root->sort(*mListSort->getSelected(), mFavoritesSorting); root->sort(*mListSort->getSelected(), mFavoritesSorting);
root->setSortTypeString((*mListSort->getSelected()).description); root->setSortTypeString((*mListSort->getSelected()).description);
// Select the first row of the gamelist. // Notify that the root folder was sorted (refresh).
FileData* firstRow = getGamelist()->getCursor()->getParent()->
getChildrenListToDisplay()[0];
getGamelist()->setCursor(firstRow);
// Notify that the root folder was sorted.
getGamelist()->onFileChanged(root, FILE_SORTED); getGamelist()->onFileChanged(root, FILE_SORTED);
} }
// Has the user changed the letter using the quick selector?
if (mCurrentFirstCharacter != mJumpToLetterList->getSelected()) {
if (mJumpToLetterList->getSelected() == FAVORITE_CHAR)
jumpToFirstRow();
else
jumpToLetter();
}
} }
if (mFiltersChanged) { if (mFiltersChanged) {
// Only reload full view if we came from a placeholder as we need to // Only reload full view if we came from a placeholder as we need to
// re-display the remaining elements for whatever new game is selected. // re-display the remaining elements for whatever new game is selected.
ViewController::get()->reloadGameListView(mSystem); ViewController::get()->reloadGameListView(mSystem);
} }
NavigationSounds::getInstance()->playThemeNavigationSound(SCROLLSOUND);
} }
void GuiGamelistOptions::openGamelistFilter() void GuiGamelistOptions::openGamelistFilter()
@ -283,29 +238,27 @@ void GuiGamelistOptions::openMetaDataEd()
void GuiGamelistOptions::jumpToLetter() void GuiGamelistOptions::jumpToLetter()
{ {
char letter = mJumpToLetterList->getSelected()[0]; char letter = mJumpToLetterList->getSelected().front();
// Get first row of the gamelist. // Get first row of the gamelist.
const std::vector<FileData*>& files = getGamelist()->getCursor()-> const std::vector<FileData*>& files = getGamelist()->getCursor()->
getParent()->getChildrenListToDisplay(); getParent()->getChildrenListToDisplay();
for (unsigned int i = 0; i < files.size(); i++) { for (unsigned int i = 0; i < files.size(); i++) {
if (mFavoritesSorting && mSystem->getName() != "favorites") { if (mFavoritesSorting && mFirstLetterIndex.front() == FAVORITE_CHAR) {
if ((char)toupper(files.at(i)->getSortName()[0]) == if ((char)toupper(files.at(i)->getSortName().front()) ==
letter && !files.at(i)->getFavorite()) { letter && !files.at(i)->getFavorite()) {
getGamelist()->setCursor(files.at(i)); getGamelist()->setCursor(files.at(i));
break; break;
} }
} }
else { else {
if ((char)toupper(files.at(i)->getSortName()[0]) == letter) { if ((char)toupper(files.at(i)->getSortName().front()) == letter) {
getGamelist()->setCursor(files.at(i)); getGamelist()->setCursor(files.at(i));
break; break;
} }
} }
} }
delete this;
} }
void GuiGamelistOptions::jumpToFirstRow() void GuiGamelistOptions::jumpToFirstRow()
@ -314,8 +267,6 @@ void GuiGamelistOptions::jumpToFirstRow()
const std::vector<FileData*>& files = getGamelist()->getCursor()-> const std::vector<FileData*>& files = getGamelist()->getCursor()->
getParent()->getChildrenListToDisplay(); getParent()->getChildrenListToDisplay();
getGamelist()->setCursor(files.at(0)); getGamelist()->setCursor(files.at(0));
delete this;
} }
bool GuiGamelistOptions::input(InputConfig* config, Input input) bool GuiGamelistOptions::input(InputConfig* config, Input input)

View file

@ -40,8 +40,6 @@ private:
void jumpToLetter(); void jumpToLetter();
void jumpToFirstRow(); void jumpToFirstRow();
const std::string FAVORITE_CHAR = "\uF005";
MenuComponent mMenu; MenuComponent mMenu;
typedef OptionListComponent<std::string> LetterList; typedef OptionListComponent<std::string> LetterList;
@ -56,6 +54,10 @@ private:
bool fromPlaceholder; bool fromPlaceholder;
bool mFiltersChanged; bool mFiltersChanged;
bool mCancelled; bool mCancelled;
std::vector<std::string> mFirstLetterIndex;
std::string mCurrentFirstCharacter;
const std::string FAVORITE_CHAR = "\uF005";
}; };
#endif // ES_APP_GUIS_GUI_GAME_LIST_OPTIONS_H #endif // ES_APP_GUIS_GUI_GAME_LIST_OPTIONS_H