Skip to content

Commit 3d46ca2

Browse files
async gamelists writes
1 parent cfefa1e commit 3d46ca2

3 files changed

Lines changed: 61 additions & 7 deletions

File tree

es-app/src/Gamelist.cpp

Lines changed: 55 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
#include "Gamelist.h"
22

3+
#include <algorithm>
34
#include <chrono>
5+
#include <fstream>
6+
#include <future>
7+
#include <mutex>
8+
#include <sstream>
49

510
#include "utils/FileSystemUtil.h"
611
#include "FileData.h"
@@ -9,6 +14,11 @@
914
#include "Settings.h"
1015
#include "SystemData.h"
1116
#include <pugixml.hpp>
17+
#include <unordered_map>
18+
19+
// Async gamelist write infrastructure
20+
static std::mutex sGamelistWriteMutex;
21+
static std::vector<std::future<void>> sGamelistPendingWrites;
1222

1323
FileData* findOrCreateFile(SystemData* system, const std::string& path, FileType type)
1424
{
@@ -292,6 +302,7 @@ void updateGamelist(SystemData* system)
292302
if(!pathNode)
293303
{
294304
LOG(LogError) << "<" << tag << "> node contains no <path> child!";
305+
fileNode = nextNode;
295306
continue;
296307
}
297308

@@ -325,22 +336,59 @@ void updateGamelist(SystemData* system)
325336
// now write the file
326337

327338
if (numUpdated > 0) {
328-
const auto startTs = std::chrono::system_clock::now();
329-
330339
//make sure the folders leading up to this path exist (or the write will fail)
331340
std::string xmlWritePath(system->getGamelistPath(true));
332341
Utils::FileSystem::createDirectory(Utils::FileSystem::getParent(xmlWritePath));
333342

334343
LOG(LogInfo) << "Added/Updated " << numUpdated << " entities in '" << xmlReadPath << "'";
335344

336-
if (!doc.save_file(xmlWritePath.c_str())) {
337-
LOG(LogError) << "Error saving gamelist.xml to \"" << xmlWritePath << "\" (for system " << system->getName() << ")!";
338-
}
345+
// Serialize the XML document to a string on the main thread,
346+
// then write the string to file on a background thread to
347+
// avoid blocking the UI on slow NAS I/O.
348+
std::stringstream ss;
349+
doc.save(ss);
350+
std::string xmlContent = ss.str();
351+
std::string sysName = system->getName();
352+
353+
{
354+
// Clean up finished futures
355+
std::lock_guard<std::mutex> lock(sGamelistWriteMutex);
356+
sGamelistPendingWrites.erase(
357+
std::remove_if(sGamelistPendingWrites.begin(), sGamelistPendingWrites.end(),
358+
[](std::future<void>& f) {
359+
return f.wait_for(std::chrono::seconds(0)) == std::future_status::ready;
360+
}),
361+
sGamelistPendingWrites.end());
362+
363+
sGamelistPendingWrites.push_back(std::async(std::launch::async,
364+
[xmlContent, xmlWritePath, sysName]() {
365+
const auto startTs = std::chrono::system_clock::now();
366+
367+
std::ofstream outFile(xmlWritePath, std::ios::out | std::ios::trunc);
368+
if (outFile.is_open()) {
369+
outFile << xmlContent;
370+
outFile.close();
371+
} else {
372+
LOG(LogError) << "Error saving gamelist.xml to \"" << xmlWritePath << "\" (for system " << sysName << ")!";
373+
}
339374

340-
const auto endTs = std::chrono::system_clock::now();
341-
LOG(LogInfo) << "Saved gamelist.xml for system \"" << system->getName() << "\" in " << std::chrono::duration_cast<std::chrono::milliseconds>(endTs - startTs).count() << " ms";
375+
const auto endTs = std::chrono::system_clock::now();
376+
LOG(LogInfo) << "Saved gamelist.xml for system \"" << sysName << "\" in " << std::chrono::duration_cast<std::chrono::milliseconds>(endTs - startTs).count() << " ms";
377+
}));
378+
}
342379
}
343380
}else{
344381
LOG(LogError) << "Found no root folder for system \"" << system->getName() << "\"!";
345382
}
346383
}
384+
385+
void waitForGamelistWrites()
386+
{
387+
std::lock_guard<std::mutex> lock(sGamelistWriteMutex);
388+
for (auto& f : sGamelistPendingWrites)
389+
{
390+
if (f.valid())
391+
f.wait();
392+
}
393+
sGamelistPendingWrites.clear();
394+
}

es-app/src/Gamelist.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,8 @@ void parseGamelist(SystemData* system);
1010
// Writes currently loaded metadata for a SystemData to gamelist.xml.
1111
void updateGamelist(SystemData* system);
1212

13+
// Blocks until all pending async gamelist writes have completed.
14+
// Must be called before process exit to avoid data loss.
15+
void waitForGamelistWrites();
16+
1317
#endif // ES_APP_GAME_LIST_H

es-app/src/main.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include "views/ViewController.h"
99
#include "CollectionSystemManager.h"
1010
#include "EmulationStation.h"
11+
#include "Gamelist.h"
1112
#include "InputManager.h"
1213
#include "Log.h"
1314
#include "MameNames.h"
@@ -484,6 +485,7 @@ int main(int argc, char* argv[])
484485

485486
MameNames::deinit();
486487
CollectionSystemManager::deinit();
488+
waitForGamelistWrites();
487489
SystemData::deleteSystems();
488490

489491
// call this ONLY when linking with FreeImage as a static library

0 commit comments

Comments
 (0)