This commit is contained in:
2026-05-14 13:30:06 +08:00
parent 974946cee4
commit e43171521d
91 changed files with 10485 additions and 3254 deletions
@@ -0,0 +1,139 @@
#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