#include "persistence/AsyncProjectWriter.h" #include "persistence/AnimationBundleStore.h" #include "persistence/EntityBundleStore.h" #include #include #include #include #include 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 ents; QHash 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