Skip to content

Commit e6325ce

Browse files
[PSerban93][PSerban93]
authored andcommitted
Refactor achievement file monitoring and notification logic for improved performance and reliability
1 parent b0f499f commit e6325ce

File tree

2 files changed

+50
-91
lines changed

2 files changed

+50
-91
lines changed

main.js

Lines changed: 44 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1201,8 +1201,25 @@ function sendConsoleMessageToUI(message, color) {
12011201
msg.startsWith("steam-achievements:request") ||
12021202
msg.startsWith("steam-achievements:success");
12031203
if (suppress) return;
1204-
if (mainWindow && !mainWindow.isDestroyed()) {
1205-
mainWindow.webContents.send("notify", { message, color });
1204+
if (!mainWindow || mainWindow.isDestroyed()) return;
1205+
const wc = mainWindow.webContents;
1206+
if (!wc || wc.isDestroyed?.() || wc.isCrashed?.()) return;
1207+
// Rate-limit UI notifications to avoid renderer OOM on log storms.
1208+
const now = Date.now();
1209+
if (!sendConsoleMessageToUI._bucket) {
1210+
sendConsoleMessageToUI._bucket = { ts: now, count: 0 };
1211+
}
1212+
const bucket = sendConsoleMessageToUI._bucket;
1213+
if (now - bucket.ts > 2000) {
1214+
bucket.ts = now;
1215+
bucket.count = 0;
1216+
}
1217+
bucket.count += 1;
1218+
if (bucket.count > 15) return;
1219+
try {
1220+
wc.send("notify", { message: msg, color });
1221+
} catch {
1222+
// avoid recursive console error loops
12061223
}
12071224
}
12081225

@@ -4121,6 +4138,8 @@ function processNextProgressNotification() {
41214138
let currentAchievementsFilePath = null;
41224139
let achievementsWatcher = null;
41234140
let extraAchievementFiles = new Set();
4141+
let achievementMonitorToken = 0;
4142+
let achievementMonitorTimer = null;
41244143

41254144
if (!fs.existsSync(cacheDir)) {
41264145
fs.mkdirSync(cacheDir, { recursive: true });
@@ -4397,6 +4416,11 @@ function findAchievementFileDeepForAppId(saveBase, appid, maxDepth = 2) {
43974416

43984417
function monitorAchievementsFile(filePath) {
43994418
if (!filePath) {
4419+
achievementMonitorToken += 1;
4420+
if (achievementMonitorTimer) {
4421+
clearTimeout(achievementMonitorTimer);
4422+
achievementMonitorTimer = null;
4423+
}
44004424
if (extraAchievementFiles.size) {
44014425
for (const fp of extraAchievementFiles) {
44024426
try {
@@ -4413,10 +4437,23 @@ function monitorAchievementsFile(filePath) {
44134437
return;
44144438
}
44154439

4416-
if (currentAchievementsFilePath === filePath && achievementsWatcher) {
4440+
const samePathActive =
4441+
currentAchievementsFilePath === filePath && achievementsWatcher;
4442+
if (samePathActive && fs.existsSync(filePath)) {
4443+
if (achievementMonitorTimer) {
4444+
clearTimeout(achievementMonitorTimer);
4445+
achievementMonitorTimer = null;
4446+
}
44174447
return;
44184448
}
44194449

4450+
achievementMonitorToken += 1;
4451+
if (achievementMonitorTimer) {
4452+
clearTimeout(achievementMonitorTimer);
4453+
achievementMonitorTimer = null;
4454+
}
4455+
const monitorToken = achievementMonitorToken;
4456+
44204457
if (achievementsWatcher && currentAchievementsFilePath) {
44214458
fs.unwatchFile(currentAchievementsFilePath, achievementsWatcher);
44224459
achievementsWatcher = null;
@@ -4836,6 +4873,7 @@ function monitorAchievementsFile(filePath) {
48364873
};
48374874
achievementsWatcher = () => processSnapshot(false);
48384875
const checkFileLoop = () => {
4876+
if (monitorToken !== achievementMonitorToken) return;
48394877
if (fs.existsSync(filePath)) {
48404878
processSnapshot(false);
48414879

@@ -4852,6 +4890,7 @@ function monitorAchievementsFile(filePath) {
48524890
fs.unwatchFile(filePath);
48534891
} catch {}
48544892
fs.watchFile(filePath, { interval: 1000 }, achievementsWatcher);
4893+
achievementMonitorTimer = null;
48554894
} else {
48564895
const baseDir = path.dirname(filePath);
48574896
const tenokePath = path.join(baseDir, "SteamData", "user_stats.ini");
@@ -4903,7 +4942,7 @@ function monitorAchievementsFile(filePath) {
49034942
}
49044943

49054944
// Retry discovery later in case the save file appears after config load
4906-
setTimeout(checkFileLoop, 1000);
4945+
achievementMonitorTimer = setTimeout(checkFileLoop, 1000);
49074946
}
49084947
};
49094948

@@ -5047,32 +5086,7 @@ ipcMain.on(
50475086
const trophyDir = config.save_path || "";
50485087
const xmlRoot = path.join(trophyDir, "Xml");
50495088
const xmlMain = path.join(xmlRoot, "TROP.XML");
5050-
const xmlFiles = [
5051-
xmlMain,
5052-
path.join(xmlRoot, "TROP_00.XML"),
5053-
path.join(xmlRoot, "TROP_01.XML"),
5054-
path.join(xmlRoot, "TROP_02.XML"),
5055-
path.join(xmlRoot, "TROP_03.XML"),
5056-
path.join(xmlRoot, "TROP_04.XML"),
5057-
path.join(xmlRoot, "TROP_05.XML"),
5058-
path.join(xmlRoot, "TROP_06.XML"),
5059-
path.join(xmlRoot, "TROP_07.XML"),
5060-
path.join(xmlRoot, "TROP_08.XML"),
5061-
path.join(xmlRoot, "TROP_09.XML"),
5062-
path.join(xmlRoot, "TROP_10.XML"),
5063-
path.join(xmlRoot, "TROP_11.XML"),
5064-
path.join(xmlRoot, "TROP_12.XML"),
5065-
path.join(xmlRoot, "TROP_13.XML"),
5066-
path.join(xmlRoot, "TROP_14.XML"),
5067-
path.join(xmlRoot, "TROP_15.XML"),
5068-
path.join(xmlRoot, "TROP_16.XML"),
5069-
path.join(xmlRoot, "TROP_17.XML"),
5070-
path.join(xmlRoot, "TROP_18.XML"),
5071-
path.join(xmlRoot, "TROP_19.XML"),
5072-
path.join(xmlRoot, "TROP_20.XML"),
5073-
].filter((p) => fs.existsSync(p));
5074-
achievementsFilePath = xmlFiles.length ? xmlFiles[0] : null;
5075-
const monitorList = xmlFiles.length ? xmlFiles : null;
5089+
achievementsFilePath = fs.existsSync(xmlMain) ? xmlMain : null;
50765090
if (!achievementsFilePath) {
50775091
monitorAchievementsFile(null);
50785092
achievementsFilePath = null;
@@ -5093,15 +5107,6 @@ ipcMain.on(
50935107
pendingMissingAchievementFiles.delete(configName);
50945108
}
50955109
monitorAchievementsFile(achievementsFilePath);
5096-
if (monitorList && achievementsWatcher) {
5097-
for (const extra of monitorList.slice(1)) {
5098-
try {
5099-
fs.unwatchFile(extra);
5100-
fs.watchFile(extra, { interval: 500 }, achievementsWatcher);
5101-
extraAchievementFiles.add(extra);
5102-
} catch {}
5103-
}
5104-
}
51055110
if (overlayWindow && !overlayWindow.isDestroyed()) {
51065111
overlayWindow.webContents.send("load-overlay-data", selectedConfig);
51075112
overlayWindow.webContents.send("set-language", {
@@ -5139,26 +5144,6 @@ ipcMain.on(
51395144
pendingMissingAchievementFiles.delete(configName);
51405145
}
51415146
monitorAchievementsFile(achievementsFilePath);
5142-
if (achievementsWatcher && statsDir && appid) {
5143-
try {
5144-
const extras = fs
5145-
.readdirSync(statsDir)
5146-
.filter(
5147-
(f) =>
5148-
/^usergamestats_.*_[0-9]+\.bin$/i.test(f) &&
5149-
f.toLowerCase().endsWith(`_${appid.toLowerCase()}.bin`),
5150-
)
5151-
.map((f) => path.join(statsDir, f))
5152-
.filter((p) => p !== achievementsFilePath);
5153-
for (const extra of extras) {
5154-
try {
5155-
fs.unwatchFile(extra);
5156-
fs.watchFile(extra, { interval: 500 }, achievementsWatcher);
5157-
extraAchievementFiles.add(extra);
5158-
} catch {}
5159-
}
5160-
} catch {}
5161-
}
51625147
if (overlayWindow && !overlayWindow.isDestroyed()) {
51635148
overlayWindow.webContents.send("load-overlay-data", selectedConfig);
51645149
overlayWindow.webContents.send("set-language", {

utils/watched-folders.js

Lines changed: 6 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1300,46 +1300,20 @@ module.exports = function makeWatchedFolders({
13001300
return Array.from(out);
13011301
}
13021302
if (isSteamOfficialMeta(meta)) {
1303-
if (meta.save_path) out.add(meta.save_path);
1304-
if (meta.appid) {
1303+
if (meta.save_path && meta.appid) {
13051304
out.add(
13061305
path.join(meta.save_path, `UserGameStatsSchema_${meta.appid}.bin`),
13071306
);
1308-
out.add(
1309-
path.join(meta.save_path, `UserGameStats_*_${meta.appid}.bin`),
1310-
);
1307+
const latestUserBin = pickLatestUserBin(meta.save_path, meta.appid);
1308+
if (latestUserBin) out.add(latestUserBin);
13111309
}
13121310
return Array.from(out);
13131311
}
13141312

13151313
if (isPs4Meta(meta)) {
13161314
const trophyDir = resolvePs4TrophyDirForMeta(meta);
13171315
if (trophyDir) {
1318-
out.add(trophyDir);
1319-
out.add(path.join(trophyDir, "Xml"));
1320-
// watch specific files
13211316
out.add(path.join(trophyDir, "Xml", "TROP.XML"));
1322-
out.add(path.join(trophyDir, "Xml", "TROP_00.XML"));
1323-
out.add(path.join(trophyDir, "Xml", "TROP_01.XML"));
1324-
out.add(path.join(trophyDir, "Xml", "TROP_02.XML"));
1325-
out.add(path.join(trophyDir, "Xml", "TROP_03.XML"));
1326-
out.add(path.join(trophyDir, "Xml", "TROP_04.XML"));
1327-
out.add(path.join(trophyDir, "Xml", "TROP_05.XML"));
1328-
out.add(path.join(trophyDir, "Xml", "TROP_06.XML"));
1329-
out.add(path.join(trophyDir, "Xml", "TROP_07.XML"));
1330-
out.add(path.join(trophyDir, "Xml", "TROP_08.XML"));
1331-
out.add(path.join(trophyDir, "Xml", "TROP_09.XML"));
1332-
out.add(path.join(trophyDir, "Xml", "TROP_10.XML"));
1333-
out.add(path.join(trophyDir, "Xml", "TROP_11.XML"));
1334-
out.add(path.join(trophyDir, "Xml", "TROP_12.XML"));
1335-
out.add(path.join(trophyDir, "Xml", "TROP_13.XML"));
1336-
out.add(path.join(trophyDir, "Xml", "TROP_14.XML"));
1337-
out.add(path.join(trophyDir, "Xml", "TROP_15.XML"));
1338-
out.add(path.join(trophyDir, "Xml", "TROP_16.XML"));
1339-
out.add(path.join(trophyDir, "Xml", "TROP_17.XML"));
1340-
out.add(path.join(trophyDir, "Xml", "TROP_18.XML"));
1341-
out.add(path.join(trophyDir, "Xml", "TROP_19.XML"));
1342-
out.add(path.join(trophyDir, "Xml", "TROP_20.XML"));
13431317
}
13441318
return Array.from(out);
13451319
}
@@ -1416,7 +1390,7 @@ module.exports = function makeWatchedFolders({
14161390
} else if (isRpcs3) {
14171391
if (base !== "tropusr.dat") return;
14181392
} else if (isPs4) {
1419-
if (!base.endsWith(".xml")) return;
1393+
if (base !== "trop.xml") return;
14201394
} else if (isSteamOfficial) {
14211395
if (!base.endsWith(".bin") || !base.startsWith("usergamestats_")) return;
14221396
const appidStr = String(meta?.appid || appid || "").toLowerCase();
@@ -3453,7 +3427,7 @@ module.exports = function makeWatchedFolders({
34533427
const isTropusr = base === "tropusr.dat";
34543428
const isTropconf = base === "tropconf.sfm";
34553429
const isRpcs3File = isTropusr || isTropconf;
3456-
const isPs4Xml = base.startsWith("trop") && base.endsWith(".xml");
3430+
const isPs4Xml = base === "trop.xml";
34573431
if (
34583432
!isGpd &&
34593433
!isRpcs3File &&
@@ -3648,7 +3622,7 @@ module.exports = function makeWatchedFolders({
36483622
const isTropusr = base === "tropusr.dat";
36493623
const isTropconf = base === "tropconf.sfm";
36503624
const isRpcs3File = isTropusr || isTropconf;
3651-
const isPs4Xml = base.startsWith("trop") && base.endsWith(".xml");
3625+
const isPs4Xml = base === "trop.xml";
36523626
if (
36533627
!isGpd &&
36543628
!isRpcs3File &&

0 commit comments

Comments
 (0)