Skip to content

Commit 9a0dc28

Browse files
Merge pull request #748 from GriffinRichards/fix-file-watcher
Condense file watcher warning, reduce resource usage
2 parents bbe34e4 + 573358b commit 9a0dc28

File tree

6 files changed

+88
-30
lines changed

6 files changed

+88
-30
lines changed

include/project.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,6 @@ class Project : public QObject
6161
QMap<QString, uint32_t> metatileBehaviorMap;
6262
QMap<uint32_t, QString> metatileBehaviorMapInverse;
6363
ParseUtil parser;
64-
QFileSystemWatcher fileWatcher;
6564
QSet<QString> modifiedFiles;
6665
bool usingAsmTilesets;
6766
QSet<QString> disabledSettingsNames;
@@ -263,6 +262,7 @@ class Project : public QObject
263262
static QString getMapGroupPrefix();
264263

265264
private:
265+
QPointer<QFileSystemWatcher> fileWatcher;
266266
QMap<QString, qint64> modifiedFileTimestamps;
267267
QMap<QString, QString> facingDirections;
268268
QHash<QString, QString> speciesToIconPath;
@@ -332,6 +332,8 @@ class Project : public QObject
332332
void ignoreWatchedFilesTemporarily(const QStringList &filepaths);
333333
void recordFileChange(const QString &filepath);
334334
void resetFileCache();
335+
void resetFileWatcher();
336+
void logFileWatchStatus();
335337

336338
bool saveMapLayouts();
337339
bool saveMapGroups();

include/ui/preferenceeditor.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ class PreferenceEditor : public QMainWindow
2424
void preferencesSaved();
2525
void themeChanged(const QString &theme);
2626
void scriptSettingsChanged(bool on);
27+
void reloadProjectRequested();
2728

2829
private:
2930
Ui::PreferenceEditor *ui;

src/core/map.cpp

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -163,17 +163,19 @@ QStringList Map::getScriptLabels(Event::Group group) {
163163
m_loggedScriptsFileError = true;
164164
}
165165

166-
if (!m_scriptFileWatcher) {
167-
// Only create the file watcher when it's first needed (even an empty QFileSystemWatcher will consume system resources).
168-
// The other option would be for Porymap to have a single global QFileSystemWatcher, but that has complications of its own.
169-
m_scriptFileWatcher = new QFileSystemWatcher(this);
170-
connect(m_scriptFileWatcher, &QFileSystemWatcher::fileChanged, this, &Map::invalidateScripts);
171-
}
172-
if (!m_scriptFileWatcher->files().contains(scriptsFilepath) && !m_scriptFileWatcher->addPath(scriptsFilepath) && !m_loggedScriptsFileError) {
173-
logWarn(QString("Failed to add scripts file '%1' to file watcher for %2.")
174-
.arg(Util::stripPrefix(scriptsFilepath, projectConfig.projectDir() + "/"))
175-
.arg(m_name));
176-
m_loggedScriptsFileError = true;
166+
if (porymapConfig.monitorFiles) {
167+
if (!m_scriptFileWatcher) {
168+
// Only create the file watcher when it's first needed (even an empty QFileSystemWatcher will consume system resources).
169+
// The other option would be for Porymap to have a single global QFileSystemWatcher, but that has complications of its own.
170+
m_scriptFileWatcher = new QFileSystemWatcher(this);
171+
connect(m_scriptFileWatcher, &QFileSystemWatcher::fileChanged, this, &Map::invalidateScripts);
172+
}
173+
if (!m_scriptFileWatcher->files().contains(scriptsFilepath) && !m_scriptFileWatcher->addPath(scriptsFilepath) && !m_loggedScriptsFileError) {
174+
logWarn(QString("Failed to add scripts file '%1' to file watcher for %2.")
175+
.arg(Util::stripPrefix(scriptsFilepath, projectConfig.projectDir() + "/"))
176+
.arg(m_name));
177+
m_loggedScriptsFileError = true;
178+
}
177179
}
178180

179181
m_scriptsLoaded = true;

src/mainwindow.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2938,6 +2938,7 @@ void MainWindow::on_actionPreferences_triggered() {
29382938
// require us to repopulate the EventFrames and redraw event pixmaps, respectively.
29392939
connect(preferenceEditor, &PreferenceEditor::preferencesSaved, editor, &Editor::updateEvents);
29402940
connect(preferenceEditor, &PreferenceEditor::scriptSettingsChanged, editor->project, &Project::readEventScriptLabels);
2941+
connect(preferenceEditor, &PreferenceEditor::reloadProjectRequested, this, &MainWindow::on_action_Reload_Project_triggered);
29412942
}
29422943

29432944
openSubWindow(preferenceEditor);

src/project.cpp

Lines changed: 53 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,7 @@ int Project::num_pals_total = 13;
3232

3333
Project::Project(QObject *parent) :
3434
QObject(parent)
35-
{
36-
QObject::connect(&this->fileWatcher, &QFileSystemWatcher::fileChanged, this, &Project::recordFileChange);
37-
}
35+
{ }
3836

3937
Project::~Project()
4038
{
@@ -186,6 +184,7 @@ int Project::getSupportedMajorVersion(QString *errorOut) {
186184

187185
bool Project::load() {
188186
this->parser.setUpdatesSplashScreen(true);
187+
resetFileWatcher();
189188
resetFileCache();
190189
this->disabledSettingsNames.clear();
191190
bool success = readGlobalConstants()
@@ -225,14 +224,14 @@ bool Project::load() {
225224
initNewLayoutSettings();
226225
initNewMapSettings();
227226
applyParsedLimits();
227+
logFileWatchStatus();
228228
}
229229
this->parser.setUpdatesSplashScreen(false);
230230
return success;
231231
}
232232

233233
void Project::resetFileCache() {
234234
this->parser.clearFileCache();
235-
this->failedFileWatchPaths.clear();
236235

237236
const QSet<QString> filepaths = {
238237
// Whenever we load a tileset we'll need to parse some data from these files, so we cache them to avoid the overhead of opening the files.
@@ -749,16 +748,21 @@ bool Project::saveMapLayouts() {
749748
}
750749

751750
bool Project::watchFile(const QString &filename) {
751+
if (!porymapConfig.monitorFiles)
752+
return true;
753+
754+
if (!this->fileWatcher) {
755+
// Only create the file watcher when it's first needed (even an empty QFileSystemWatcher will consume system resources).
756+
this->fileWatcher = new QFileSystemWatcher(this);
757+
QObject::connect(this->fileWatcher, &QFileSystemWatcher::fileChanged, this, &Project::recordFileChange);
758+
}
759+
752760
QString filepath = filename.startsWith(this->root) ? filename : QString("%1/%2").arg(this->root).arg(filename);
753-
if (!this->fileWatcher.addPath(filepath) && !this->fileWatcher.files().contains(filepath)) {
761+
if (!this->fileWatcher->addPath(filepath) && !this->fileWatcher->files().contains(filepath)) {
754762
// We failed to watch the file, and this wasn't a file we were already watching.
755-
// Log a warning, but only if A. we actually care that we failed, because 'monitor files' is enabled,
756-
// B. we haven't logged a warning for this file yet, and C. we would have otherwise been able to watch it, because the file exists.
757-
if (porymapConfig.monitorFiles && !this->failedFileWatchPaths.contains(filepath) && QFileInfo::exists(filepath)) {
763+
// Record the filepath for logging later, assuming we should have been able to watch the file.
764+
if (QFileInfo::exists(filepath)) {
758765
this->failedFileWatchPaths.insert(filepath);
759-
logWarn(QString("Failed to add '%1' to file watcher. Currently watching %2 files.")
760-
.arg(Util::stripPrefix(filepath, this->root))
761-
.arg(this->fileWatcher.files().length()));
762766
}
763767
return false;
764768
}
@@ -774,8 +778,11 @@ bool Project::watchFiles(const QStringList &filenames) {
774778
}
775779

776780
bool Project::stopFileWatch(const QString &filename) {
781+
if (!this->fileWatcher)
782+
return true;
783+
777784
QString filepath = filename.startsWith(this->root) ? filename : QString("%1/%2").arg(this->root).arg(filename);
778-
return this->fileWatcher.removePath(filepath);
785+
return this->fileWatcher->removePath(filepath);
779786
}
780787

781788
void Project::ignoreWatchedFileTemporarily(const QString &filepath) {
@@ -794,8 +801,8 @@ void Project::recordFileChange(const QString &filepath) {
794801
// Note: As a safety measure, many applications save an open file by writing a new file and then deleting the old one.
795802
// In your slot function, you can check watcher.files().contains(path).
796803
// If it returns false, check whether the file still exists and then call addPath() to continue watching it.
797-
if (!this->fileWatcher.files().contains(filepath) && QFileInfo::exists(filepath)) {
798-
this->fileWatcher.addPath(filepath);
804+
if (this->fileWatcher && !this->fileWatcher->files().contains(filepath) && QFileInfo::exists(filepath)) {
805+
this->fileWatcher->addPath(filepath);
799806
}
800807

801808
if (this->modifiedFiles.contains(filepath)) {
@@ -815,6 +822,38 @@ void Project::recordFileChange(const QString &filepath) {
815822
emit fileChanged(filepath);
816823
}
817824

825+
// When calling 'watchFile' we record failures rather than log them immediately.
826+
// We do this primarily to condense the warning if we fail to monitor any files.
827+
void Project::logFileWatchStatus() {
828+
if (!this->fileWatcher)
829+
return;
830+
831+
int numSuccessful = this->fileWatcher->files().length();
832+
int numAttempted = numSuccessful + this->failedFileWatchPaths.count();
833+
if (numAttempted == 0)
834+
return;
835+
836+
if (numSuccessful == 0) {
837+
// We failed to watch every file we tried. As of writing this happens if Porymap is running
838+
// on Windows and the project files are in WSL2. Rather than filling the log by
839+
// outputting a warning for every file, just log that we failed to monitor any of them.
840+
logWarn(QString("Failed to monitor project files"));
841+
return;
842+
} else {
843+
logInfo(QString("Successfully monitoring %1/%2 project files").arg(numSuccessful).arg(numAttempted));
844+
}
845+
846+
for (const auto &failedPath : this->failedFileWatchPaths) {
847+
logWarn(QString("Failed to monitor project file '%1'").arg(failedPath));
848+
}
849+
}
850+
851+
void Project::resetFileWatcher() {
852+
this->failedFileWatchPaths.clear();
853+
delete this->fileWatcher;
854+
this->fileWatcher = nullptr;
855+
}
856+
818857
bool Project::saveMapGroups() {
819858
QString mapGroupsFilepath = QString("%1/%2").arg(root).arg(projectConfig.getFilePath(ProjectFilePath::json_map_groups));
820859
QFile mapGroupsFile(mapGroupsFilepath);

src/ui/preferenceeditor.cpp

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#include "ui_preferenceeditor.h"
33
#include "config.h"
44
#include "noscrollcombobox.h"
5+
#include "message.h"
56

67
#include <QAbstractButton>
78
#include <QRegularExpression>
@@ -87,6 +88,8 @@ void PreferenceEditor::updateFields() {
8788
}
8889

8990
void PreferenceEditor::saveFields() {
91+
bool needsProjectReload = false;
92+
9093
bool changedTheme = false;
9194
if (themeSelector->currentText() != porymapConfig.theme) {
9295
porymapConfig.theme = themeSelector->currentText();
@@ -100,7 +103,6 @@ void PreferenceEditor::saveFields() {
100103
porymapConfig.eventSelectionShapeMode = ui->radioButton_OnSprite->isChecked() ? QGraphicsPixmapItem::MaskShape : QGraphicsPixmapItem::BoundingRectShape;
101104
porymapConfig.textEditorOpenFolder = ui->lineEdit_TextEditorOpenFolder->text();
102105
porymapConfig.textEditorGotoLine = ui->lineEdit_TextEditorGotoLine->text();
103-
porymapConfig.monitorFiles = ui->checkBox_MonitorProjectFiles->isChecked();
104106
porymapConfig.reopenOnLaunch = ui->checkBox_OpenRecentProject->isChecked();
105107
porymapConfig.checkForUpdates = ui->checkBox_CheckForUpdates->isChecked();
106108
porymapConfig.eventDeleteWarningDisabled = ui->checkBox_DisableEventWarning->isChecked();
@@ -110,6 +112,11 @@ void PreferenceEditor::saveFields() {
110112
if (ui->checkBox_StatusWarnings->isChecked()) porymapConfig.statusBarLogTypes.insert(LogType::LOG_WARN);
111113
if (ui->checkBox_StatusInformation->isChecked()) porymapConfig.statusBarLogTypes.insert(LogType::LOG_INFO);
112114

115+
if (porymapConfig.monitorFiles != ui->checkBox_MonitorProjectFiles->isChecked()) {
116+
porymapConfig.monitorFiles = ui->checkBox_MonitorProjectFiles->isChecked();
117+
needsProjectReload = true;
118+
}
119+
113120
if (porymapConfig.applicationFont != this->applicationFont) {
114121
porymapConfig.applicationFont = this->applicationFont;
115122
changedTheme = true;
@@ -119,12 +126,18 @@ void PreferenceEditor::saveFields() {
119126
changedTheme = true;
120127
}
121128

122-
porymapConfig.save();
129+
if (changedTheme) {
130+
emit themeChanged(porymapConfig.theme);
131+
}
123132

133+
porymapConfig.save();
124134
emit preferencesSaved();
125135

126-
if (changedTheme) {
127-
emit themeChanged(porymapConfig.theme);
136+
if (needsProjectReload) {
137+
auto message = QStringLiteral("Some changes will only take effect after reloading the project. Reload the project now?");
138+
if (QuestionMessage::show(message, this) == QMessageBox::Yes) {
139+
emit reloadProjectRequested();
140+
}
128141
}
129142
}
130143

0 commit comments

Comments
 (0)