140 lines
4.0 KiB
C++
140 lines
4.0 KiB
C++
#include "persistence/AsyncProjectWriter.h"
|
|
|
|
#include "persistence/AnimationBundleStore.h"
|
|
#include "persistence/EntityBundleStore.h"
|
|
|
|
#include <QCoreApplication>
|
|
#include <QDir>
|
|
#include <QEventLoop>
|
|
#include <QTimer>
|
|
#include <QtConcurrent/QtConcurrent>
|
|
|
|
namespace core::persistence {
|
|
|
|
AsyncProjectWriter::AsyncProjectWriter(QObject* parent)
|
|
: QObject(parent) {
|
|
m_debounce = new QTimer(this);
|
|
m_debounce->setSingleShot(true);
|
|
m_debounce->setInterval(80);
|
|
connect(m_debounce, &QTimer::timeout, this, [this]() { scheduleDrain(); });
|
|
}
|
|
|
|
void AsyncProjectWriter::setProjectDir(const QString& projectDirAbs) {
|
|
m_projectDir = projectDirAbs;
|
|
}
|
|
|
|
void AsyncProjectWriter::requestWriteEntity(const Project::Entity& entity) {
|
|
if (m_projectDir.isEmpty() || entity.id.isEmpty()) {
|
|
return;
|
|
}
|
|
{
|
|
std::lock_guard lock(m_mutex);
|
|
m_pendingEntities.insert(entity.id, entity);
|
|
}
|
|
if (m_debounce) {
|
|
m_debounce->start();
|
|
}
|
|
}
|
|
|
|
void AsyncProjectWriter::requestWriteAnimation(const Project::Animation& animation) {
|
|
if (m_projectDir.isEmpty() || animation.id.isEmpty()) {
|
|
return;
|
|
}
|
|
{
|
|
std::lock_guard lock(m_mutex);
|
|
m_pendingAnimations.insert(animation.id, animation);
|
|
}
|
|
if (m_debounce) {
|
|
m_debounce->start();
|
|
}
|
|
}
|
|
|
|
void AsyncProjectWriter::scheduleDrain() {
|
|
const QString dir = m_projectDir;
|
|
if (dir.isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
QHash<QString, Project::Entity> ents;
|
|
QHash<QString, Project::Animation> anims;
|
|
{
|
|
std::lock_guard lock(m_mutex);
|
|
ents = std::move(m_pendingEntities);
|
|
anims = std::move(m_pendingAnimations);
|
|
m_pendingEntities.clear();
|
|
m_pendingAnimations.clear();
|
|
}
|
|
if (ents.isEmpty() && anims.isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
m_outstandingDrains.fetch_add(1, std::memory_order_acq_rel);
|
|
|
|
(void)QtConcurrent::run([this, dir, ents = std::move(ents), anims = std::move(anims)]() mutable {
|
|
bool anyFail = false;
|
|
for (auto it = ents.begin(); it != ents.end(); ++it) {
|
|
const Project::Entity& e = it.value();
|
|
if (!EntityBundleStore::save(dir, e)) {
|
|
anyFail = true;
|
|
QMetaObject::invokeMethod(this, [this, id = e.id]() { emit entityWriteFailed(id); },
|
|
Qt::QueuedConnection);
|
|
}
|
|
}
|
|
for (auto it = anims.begin(); it != anims.end(); ++it) {
|
|
const Project::Animation& a = it.value();
|
|
if (!AnimationBundleStore::save(dir, a)) {
|
|
anyFail = true;
|
|
QMetaObject::invokeMethod(this, [this, id = a.id]() { emit animationWriteFailed(id); },
|
|
Qt::QueuedConnection);
|
|
}
|
|
}
|
|
Q_UNUSED(anyFail);
|
|
m_outstandingDrains.fetch_sub(1, std::memory_order_acq_rel);
|
|
});
|
|
|
|
// drain 期间若又有新请求,再开一轮
|
|
{
|
|
std::lock_guard lock(m_mutex);
|
|
if (!m_pendingEntities.isEmpty() || !m_pendingAnimations.isEmpty()) {
|
|
if (m_debounce) {
|
|
m_debounce->start();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void AsyncProjectWriter::flushBlocking(int timeoutMs) {
|
|
if (m_projectDir.isEmpty()) {
|
|
return;
|
|
}
|
|
if (m_debounce) {
|
|
m_debounce->stop();
|
|
}
|
|
|
|
QMetaObject::invokeMethod(
|
|
this,
|
|
[this]() {
|
|
scheduleDrain();
|
|
},
|
|
Qt::QueuedConnection);
|
|
|
|
QTimer killer;
|
|
killer.setSingleShot(true);
|
|
killer.start(std::max(0, timeoutMs));
|
|
while (killer.isActive()) {
|
|
bool idle = false;
|
|
{
|
|
std::lock_guard lock(m_mutex);
|
|
idle =
|
|
m_pendingEntities.isEmpty() && m_pendingAnimations.isEmpty() &&
|
|
m_outstandingDrains.load(std::memory_order_acquire) == 0;
|
|
}
|
|
if (idle) {
|
|
break;
|
|
}
|
|
QCoreApplication::processEvents(QEventLoop::AllEvents, 20);
|
|
}
|
|
}
|
|
|
|
} // namespace core::persistence
|