Speed improvements for Project::readSpeciesIconPaths

This commit is contained in:
GriffinR
2025-02-27 14:44:19 -05:00
parent 6d8b4f21d8
commit e260c642b0
5 changed files with 106 additions and 81 deletions

View File

@@ -51,6 +51,7 @@ public:
QStringList itemNames;
QStringList flagNames;
QStringList varNames;
QStringList speciesNames;
QStringList movementTypes;
QStringList mapTypes;
QStringList mapBattleScenes;
@@ -142,8 +143,8 @@ public:
QVector<poryjson::Json::object> extraEncounterGroups;
bool readSpeciesIconPaths();
QPixmap getSpeciesIcon(const QString &species) const;
QMap<QString, QString> speciesToIconPath;
QString getDefaultSpeciesIconPath(const QString &species);
QPixmap getSpeciesIcon(const QString &species);
void addNewMapsec(const QString &idName);
void removeMapsec(const QString &idName);
@@ -255,6 +256,7 @@ private:
QMap<QString, QString> mapSectionDisplayNames;
QMap<QString, qint64> modifiedFileTimestamps;
QMap<QString, QString> facingDirections;
QMap<QString, QString> speciesToIconPath;
struct EventGraphics
{
@@ -275,6 +277,8 @@ private:
void ignoreWatchedFileTemporarily(QString filepath);
void recordFileChange(const QString &filepath);
QString findSpeciesIconPath(const QStringList &names) const;
int maxEventsPerGroup;
int maxObjectEvents;
static int num_tiles_primary;

View File

@@ -2924,101 +2924,122 @@ QPixmap Project::getEventPixmap(Event::Group group) {
bool Project::readSpeciesIconPaths() {
this->speciesToIconPath.clear();
this->speciesNames.clear();
// Read map of species constants to icon names
const QString srcfilename = projectConfig.getFilePath(ProjectFilePath::pokemon_icon_table);
fileWatcher.addPath(root + "/" + srcfilename);
fileWatcher.addPath(this->root + "/" + srcfilename);
const QString tableName = projectConfig.getIdentifier(ProjectIdentifier::symbol_pokemon_icon_table);
const QMap<QString, QString> monIconNames = parser.readNamedIndexCArray(srcfilename, tableName);
// Read map of icon names to filepaths
const QString incfilename = projectConfig.getFilePath(ProjectFilePath::data_pokemon_gfx);
fileWatcher.addPath(root + "/" + incfilename);
const QMap<QString, QString> iconIncbins = parser.readCIncbinMulti(incfilename);
// Read species constants. If this fails we can get them from the icon table (but we shouldn't rely on it).
const QString speciesPrefix = projectConfig.getIdentifier(ProjectIdentifier::define_species_prefix);
const QString constantsFilename = projectConfig.getFilePath(ProjectFilePath::constants_species);
fileWatcher.addPath(root + "/" + constantsFilename);
QStringList speciesNames = parser.readCDefineNames(constantsFilename, {QString("\\b%1").arg(speciesPrefix)});
if (speciesNames.isEmpty())
speciesNames = monIconNames.keys();
fileWatcher.addPath(this->root + "/" + constantsFilename);
this->speciesNames = parser.readCDefineNames(constantsFilename, {QString("\\b%1").arg(speciesPrefix)});
if (this->speciesNames.isEmpty()) {
this->speciesNames = monIconNames.keys();
}
this->speciesNames.sort();
// For each species, use the information gathered above to find the icon image.
bool missingIcons = false;
for (auto species : speciesNames) {
QString path = QString();
if (monIconNames.contains(species) && iconIncbins.contains(monIconNames.value(species))) {
// We have the icon filepath from the icon table
path = QString("%1/%2").arg(root).arg(this->fixGraphicPath(iconIncbins[monIconNames.value(species)]));
} else {
// Failed to read icon filepath from the icon table, check filepaths where icons are normally located.
// Try to use the icon name (if we have it) to determine the directory, then try the species name.
// The name permuting is overkill, but it's making up for some of the fragility in the way we find icon paths.
QStringList possibleDirNames;
if (monIconNames.contains(species)) {
// Ex: For 'gMonIcon_QuestionMark' try 'question_mark'
static const QRegularExpression re("([a-z])([A-Z0-9])");
QString iconName = monIconNames.value(species);
iconName = iconName.mid(iconName.indexOf("_") + 1); // jump past prefix ('gMonIcon')
possibleDirNames.append(iconName.replace(re, "\\1_\\2").toLower());
}
// Ex: For 'SPECIES_FOO_BAR_BAZ' try 'foo_bar_baz'
possibleDirNames.append(species.mid(speciesPrefix.length()).toLower());
// Permute paths with underscores.
// Ex: Try 'foo_bar/baz', 'foo/bar_baz', 'foobarbaz', 'foo_bar', and 'foo'
QStringList permutedNames;
for (auto dir : possibleDirNames) {
if (!dir.contains("_")) continue;
for (int i = dir.indexOf("_"); i > -1; i = dir.indexOf("_", i + 1)) {
QString temp = dir;
permutedNames.prepend(temp.replace(i, 1, "/"));
permutedNames.append(dir.left(i)); // Prepend the others so the most generic name ('foo') ends up last
// If we successfully found the species icon table we can use this data to get the filepath for each species icon.
// For any species not in the table, or if we failed to find the table at all, we will have to predict where the icon file is.
// That can require checking a lot of files (especially for projects with many species), so to save time on startup we only
// do this on request in Project::getDefaultSpeciesIconPath.
if (!monIconNames.isEmpty()) {
const QString iconGraphicsFile = projectConfig.getFilePath(ProjectFilePath::data_pokemon_gfx);
fileWatcher.addPath(this->root + "/" + iconGraphicsFile);
QMap<QString, QString> iconNameToFilepath = parser.readCIncbinMulti(iconGraphicsFile);
for (auto i = monIconNames.constBegin(); i != monIconNames.constEnd(); i++) {
QString path;
QString species = i.key();
QString iconName = i.value();
if (iconNameToFilepath.contains(iconName)) {
path = fixGraphicPath(iconNameToFilepath.value(iconName));
} else {
// We have an icon name for this species, but we haven't found its filepath.
// Try to find the icon file using the full icon name, and the icon name if we assume it has a prefix.
// Ex: For 'gMonIcon_QuestionMark' search for files by permuting through directories using 'question_mark' and 'g_mon_icon_question_mark.
static const QRegularExpression re_caseChange("([a-z])([A-Z0-9])");
QStringList dirNames;
if (iconName.contains("_")) {
QString iconNameNoPrefix = iconName.mid(iconName.indexOf("_") + 1);
dirNames.append(iconNameNoPrefix.replace(re_caseChange, "\\1_\\2").toLower());
}
permutedNames.prepend(dir.remove("_"));
QString iconNameWithPrefix = iconName; // Leave iconName unchanged by .replace
dirNames.append(iconNameWithPrefix.replace(re_caseChange, "\\1_\\2").toLower());
path = iconNameToFilepath[iconName] = findSpeciesIconPath(dirNames);
}
possibleDirNames.append(permutedNames);
possibleDirNames.removeDuplicates();
for (auto dir : possibleDirNames) {
if (dir.isEmpty()) continue;
const QString stdPath = QString("%1/%2%3/icon.png")
.arg(root)
.arg(projectConfig.getFilePath(ProjectFilePath::pokemon_gfx))
.arg(dir);
if (QFile::exists(stdPath)) {
// Icon found at a normal filepath
path = stdPath;
break;
}
}
if (path.isEmpty() && projectConfig.getPokemonIconPath(species).isEmpty()) {
// Failed to find icon, this species will use a placeholder icon.
logWarn(QString("Failed to find Pokémon icon for '%1'").arg(species));
missingIcons = true;
if (!path.isEmpty()) {
this->speciesToIconPath.insert(species, QString("%1/%2").arg(this->root).arg(path));
}
}
this->speciesToIconPath.insert(species, path);
}
// Logging this alongside every warning (if there are multiple) is obnoxious, just do it once at the end.
if (missingIcons) logInfo("Pokémon icon filepaths can be specified under 'Options->Project Settings'");
return true;
}
QPixmap Project::getSpeciesIcon(const QString &species) const {
QString Project::getDefaultSpeciesIconPath(const QString &species) {
if (this->speciesToIconPath.contains(species)) {
// We already know the icon path for this species (either because we read it from the project, or we found it already).
return this->speciesToIconPath.value(species);
}
if (!this->speciesNames.contains(species)) {
// Don't bother searching for a path if we don't recognize the species name.
return QString();
}
// Ex: For 'SPECIES_FOO_BAR_BAZ' search for files by permuting through directories using 'foo_bar_baz'.
const QString speciesPrefix = projectConfig.getIdentifier(ProjectIdentifier::define_species_prefix);
const QString path = findSpeciesIconPath({species.mid(speciesPrefix.length()).toLower()});
this->speciesToIconPath.insert(species, path);
// We failed to find a default icon path, this species will use a placeholder icon.
// If the user has no custom icon path for this species, tell them they can provide one.
if (path.isEmpty() && projectConfig.getPokemonIconPath(species).isEmpty()) {
logWarn(QString("Failed to find Pokémon icon for '%1'. The filepath can be specified under 'Options->Project Settings'").arg(species));
}
return path;
}
// The name permuting in here is overkill, but it's making up for some of the fragility in the way we find pokémon icon paths.
// For pokeemerald-expansion in particular this function is solely responsible for finding pokémon icons, because they have no icon table.
QString Project::findSpeciesIconPath(const QStringList &names) const {
QStringList possibleDirNames = names;
// Permute paths with underscores.
// Ex: For a base name of 'foo_bar_baz', try 'foo_bar/baz', 'foo/bar_baz', 'foobarbaz', 'foo_bar', and 'foo'.
QStringList permutedNames;
for (auto dir : possibleDirNames) {
if (!dir.contains("_")) continue;
for (int i = dir.indexOf("_"); i > -1; i = dir.indexOf("_", i + 1)) {
QString temp = dir;
permutedNames.prepend(temp.replace(i, 1, "/"));
permutedNames.append(dir.left(i)); // Prepend the others so the most generic name ('foo') ends up last
}
permutedNames.prepend(dir.remove("_"));
}
possibleDirNames.append(permutedNames);
possibleDirNames.removeDuplicates();
const QString basePath = QString("%1/%2").arg(this->root).arg(projectConfig.getFilePath(ProjectFilePath::pokemon_gfx));
for (const auto &dir : possibleDirNames) {
if (dir.isEmpty()) continue;
const QString path = QString("%1%2/icon.png").arg(basePath).arg(dir);
if (QFile::exists(path))
return path;
}
return QString();
}
QPixmap Project::getSpeciesIcon(const QString &species) {
QPixmap pixmap;
if (!QPixmapCache::find(species, &pixmap)) {
// Prefer path from config. If not present, use the path parsed from project files
QString path = projectConfig.getPokemonIconPath(species);
QString path = Project::getExistingFilepath(projectConfig.getPokemonIconPath(species));
if (path.isEmpty()) {
path = this->speciesToIconPath.value(species);
} else {
path = Project::getExistingFilepath(path);
path = getDefaultSpeciesIconPath(species);
}
QImage img(path);

View File

@@ -21,7 +21,7 @@ void SpeciesComboDelegate::paint(QPainter *painter, const QStyleOptionViewItem &
QWidget *SpeciesComboDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &, const QModelIndex &) const {
NoScrollComboBox *editor = new NoScrollComboBox(parent);
editor->setFrame(false);
editor->addItems(this->project->speciesToIconPath.keys());
editor->addItems(this->project->speciesNames);
return editor;
}

View File

@@ -104,7 +104,7 @@ void ProjectSettingsEditor::initUi() {
if (project) {
ui->comboBox_DefaultPrimaryTileset->addItems(project->primaryTilesetLabels);
ui->comboBox_DefaultSecondaryTileset->addItems(project->secondaryTilesetLabels);
ui->comboBox_IconSpecies->addItems(project->speciesToIconPath.keys());
ui->comboBox_IconSpecies->addItems(project->speciesNames);
ui->comboBox_WarpBehaviors->addItems(project->metatileBehaviorMap.keys());
}
ui->comboBox_BaseGameVersion->addItems(ProjectConfig::versionStrings);
@@ -278,11 +278,11 @@ void ProjectSettingsEditor::updatePokemonIconPath(const QString &newSpecies) {
if (!project) return;
// If user was editing a path for a valid species, record filepath text before we wipe it.
if (!this->prevIconSpecies.isEmpty() && this->project->speciesToIconPath.contains(this->prevIconSpecies))
if (!this->prevIconSpecies.isEmpty() && this->project->speciesNames.contains(this->prevIconSpecies))
this->editedPokemonIconPaths[this->prevIconSpecies] = ui->lineEdit_PokemonIcon->text();
QString editedPath = this->editedPokemonIconPaths.value(newSpecies);
QString defaultPath = this->project->speciesToIconPath.value(newSpecies);
QString defaultPath = this->project->getDefaultSpeciesIconPath(newSpecies);
ui->lineEdit_PokemonIcon->setText(this->stripProjectDir(editedPath));
ui->lineEdit_PokemonIcon->setPlaceholderText(this->stripProjectDir(defaultPath));
@@ -567,7 +567,7 @@ void ProjectSettingsEditor::save() {
// Save pokemon icon paths
const QString species = ui->comboBox_IconSpecies->currentText();
if (this->project->speciesToIconPath.contains(species))
if (this->project->speciesNames.contains(species))
this->editedPokemonIconPaths.insert(species, ui->lineEdit_PokemonIcon->text());
for (auto i = this->editedPokemonIconPaths.cbegin(), end = this->editedPokemonIconPaths.cend(); i != end; i++)
projectConfig.setPokemonIconPath(i.key(), i.value());

View File

@@ -22,7 +22,7 @@ WildMonSearch::WildMonSearch(Project *project, QWidget *parent) :
ui->setupUi(this);
// Set up species combo box
ui->comboBox_Search->addItems(project->speciesToIconPath.keys());
ui->comboBox_Search->addItems(project->speciesNames);
ui->comboBox_Search->setCurrentText(QString());
ui->comboBox_Search->lineEdit()->setPlaceholderText(Project::getEmptySpeciesName());
connect(ui->comboBox_Search, &QComboBox::currentTextChanged, this, &WildMonSearch::updateResults);