Core: Fix Game object lifetime

Use deleteLater() for Game and QProcess objects to avoid deleting
QObject instances at unsafe times.

Add helpers to detect/remove games by platform prefix, used by the
import workflow to avoid re-importing content.
This commit is contained in:
Marco Allegretti 2026-01-24 13:50:54 +01:00
parent 05abbf329b
commit 0897aa8769
3 changed files with 89 additions and 4 deletions

View file

@ -22,8 +22,11 @@ GameLauncher::~GameLauncher()
process->disconnect(); process->disconnect();
process->terminate(); process->terminate();
process->waitForFinished(3000); process->waitForFinished(3000);
delete process; process->deleteLater();
} }
m_runningGames.clear();
m_processToGame.clear();
} }
bool GameLauncher::hasRunningGames() const bool GameLauncher::hasRunningGames() const

View file

@ -202,7 +202,10 @@ void GameModel::addGame(Game *game)
// Check for duplicates // Check for duplicates
for (Game *existing : m_games) { for (Game *existing : m_games) {
if (existing->id() == game->id()) { if (existing->id() == game->id()) {
delete game; if (!game->parent()) {
game->setParent(this);
}
game->deleteLater();
return; return;
} }
} }
@ -289,7 +292,10 @@ void GameModel::removeGame(const QString &id)
endRemoveRows(); endRemoveRows();
} }
delete m_games.takeAt(i); Game *game = m_games.takeAt(i);
if (game) {
game->deleteLater();
}
Q_EMIT countChanged(); Q_EMIT countChanged();
return; return;
} }
@ -317,7 +323,11 @@ Game *GameModel::gameById(const QString &id) const
void GameModel::clear() void GameModel::clear()
{ {
beginResetModel(); beginResetModel();
qDeleteAll(m_games); for (Game *game : m_games) {
if (game) {
game->deleteLater();
}
}
m_games.clear(); m_games.clear();
m_filteredGames.clear(); m_filteredGames.clear();
endResetModel(); endResetModel();
@ -341,6 +351,76 @@ QList<Game *> GameModel::allGames() const
return m_games; return m_games;
} }
bool GameModel::hasPlatformPrefix(const QString &platformPrefix) const
{
if (platformPrefix.isEmpty()) {
return false;
}
for (Game *game : m_games) {
if (!game) {
continue;
}
if (game->platform().startsWith(platformPrefix)) {
return true;
}
}
return false;
}
int GameModel::removeByPlatformPrefix(const QString &platformPrefix)
{
if (platformPrefix.isEmpty()) {
return 0;
}
bool hasMatch = false;
for (Game *game : m_games) {
if (!game) {
continue;
}
if (game->platform().startsWith(platformPrefix)) {
hasMatch = true;
break;
}
}
if (!hasMatch) {
return 0;
}
beginResetModel();
int removed = 0;
for (int i = m_games.count() - 1; i >= 0; --i) {
Game *game = m_games.at(i);
if (!game) {
continue;
}
if (game->platform().startsWith(platformPrefix)) {
m_games.takeAt(i)->deleteLater();
removed++;
}
}
m_filteredGames.clear();
for (Game *game : m_games) {
if (matchesFilter(game)) {
m_filteredGames.append(game);
}
}
applySort();
endResetModel();
if (removed > 0) {
Q_EMIT countChanged();
}
return removed;
}
bool GameModel::matchesFilter(Game *game) const bool GameModel::matchesFilter(Game *game) const
{ {
if (!m_showHidden && game->hidden()) { if (!m_showHidden && game->hidden()) {

View file

@ -83,6 +83,8 @@ public:
Q_INVOKABLE Game *gameById(const QString &id) const; Q_INVOKABLE Game *gameById(const QString &id) const;
Q_INVOKABLE void clear(); Q_INVOKABLE void clear();
Q_INVOKABLE QStringList platforms() const; Q_INVOKABLE QStringList platforms() const;
Q_INVOKABLE bool hasPlatformPrefix(const QString &platformPrefix) const;
Q_INVOKABLE int removeByPlatformPrefix(const QString &platformPrefix);
QList<Game *> allGames() const; QList<Game *> allGames() const;