#include "nmPebiResultPlaybackController.h" #include "nmPebiResultSceneController.h" #include #include #include namespace { const char* const PRESSURE_PROPERTY_ID = "pressure"; /* Qt 4.8 的 lupdate 无法从变量参数中推导翻译原文,必须显式登记。 */ static const char* const s_pPlaybackTranslationMarkers[] = { QT_TRANSLATE_NOOP("nmPebiResultPlaybackController", "Result playback is unavailable."), QT_TRANSLATE_NOOP("nmPebiResultPlaybackController", "The selected result property is unavailable."), QT_TRANSLATE_NOOP("nmPebiResultPlaybackController", "The selected result frame is unavailable."), QT_TRANSLATE_NOOP("nmPebiResultPlaybackController", "Playback speed must be between 1 and 60 steps per second.") }; QString playbackText(const char* pText) { Q_UNUSED(s_pPlaybackTranslationMarkers); return QCoreApplication::translate( "nmPebiResultPlaybackController", pText); } bool isPressureProperty(const QString& sPropertyId) { return sPropertyId == QString::fromLatin1(PRESSURE_PROPERTY_ID); } } nmPebiResultPlaybackController::nmPebiResultPlaybackController( nmPebiResultSceneController* pSceneController, QObject* pParent) : QObject(pParent), m_pSceneController(pSceneController), m_pTimer(new QTimer(this)), m_nObservedBindingRevision(0), m_nPlaybackBindingRevision(0), m_nLastPressureFrameIndex(0), m_nStepsPerSecond(DefaultPlaybackSpeed), m_bLoopEnabled(false), m_bPlaying(false) { Q_ASSERT(m_pSceneController != nullptr); m_pTimer->setInterval(calculatePlaybackInterval()); connect(m_pTimer, SIGNAL(timeout()), this, SLOT(onPlaybackTimeout())); } nmPebiResultPlaybackController::~nmPebiResultPlaybackController() { stopTimerWithoutSignal(); m_pSceneController = nullptr; } void nmPebiResultPlaybackController::synchronizeFromScene() { const quint64 nPreviousBindingRevision = m_nObservedBindingRevision; const bool bAvailable = synchronizeSceneIdentity(); const bool bSnapshotChanged = bAvailable && nPreviousBindingRevision != m_nObservedBindingRevision; // 无快照或新快照必须终止旧回放;同快照同步只校正 Scene 已提交状态。 if(!bAvailable || bSnapshotChanged) { stopTimerWithoutSignal(); } else { const QString sPropertyId = getCurrentPropertyId(); const int nFrameCount = getPropertyFrameCount(sPropertyId); if(!isPropertyTimeDependent(sPropertyId) || nFrameCount <= 1) { stopTimerWithoutSignal(); } // 同一快照的显式同步以 Scene 的最后成功压力帧校正记忆值; // 静态属性同步则保留此前压力帧,便于切回压力时恢复。 if(isPressureProperty(sPropertyId) && getCurrentFrameIndex() >= 0) { m_nLastPressureFrameIndex = getCurrentFrameIndex(); } } emit stateChanged(); } bool nmPebiResultPlaybackController::selectProperty( const QString& sPropertyId) { stopTimerWithoutSignal(); if(!synchronizeSceneIdentity() || !getAvailablePropertyIds().contains(sPropertyId)) { return finishFailedOperation(playbackText( "The selected result property is unavailable.")); } const QString sPreviousPropertyId = getCurrentPropertyId(); int nCandidateLastPressureFrame = m_nLastPressureFrameIndex; if(isPressureProperty(sPreviousPropertyId) && getCurrentFrameIndex() >= 0) { // 切换前只保存局部候选;Scene 提交失败时不改变正式记忆值。 nCandidateLastPressureFrame = getCurrentFrameIndex(); } int nTargetFrame = 0; if(isPressureProperty(sPropertyId)) { const int nFrameCount = getPropertyFrameCount(sPropertyId); if(nFrameCount <= 0) { return finishFailedOperation(playbackText( "The selected result property is unavailable.")); } nTargetFrame = qBound( 0, nCandidateLastPressureFrame, nFrameCount - 1); } QString sError; if(!m_pSceneController->applyPropertyFrame( sPropertyId, nTargetFrame, &sError)) { return finishFailedOperation(sError); } // 属性和帧均已由 Scene 原子提交,此时才提交最后压力帧候选。 if(isPressureProperty(sPropertyId)) { m_nLastPressureFrameIndex = nTargetFrame; } else if(isPressureProperty(sPreviousPropertyId)) { m_nLastPressureFrameIndex = nCandidateLastPressureFrame; } emit stateChanged(); return true; } bool nmPebiResultPlaybackController::selectFrame(int nFrameIndex) { stopTimerWithoutSignal(); if(!synchronizeSceneIdentity()) { return finishFailedOperation(playbackText( "Result playback is unavailable.")); } const QString sPropertyId = getCurrentPropertyId(); const int nFrameCount = getPropertyFrameCount(sPropertyId); if(!isPropertyTimeDependent(sPropertyId) || nFrameIndex < 0 || nFrameIndex >= nFrameCount) { return finishFailedOperation(playbackText( "The selected result frame is unavailable.")); } QString sError; if(!m_pSceneController->applyPropertyFrame( sPropertyId, nFrameIndex, &sError)) { return finishFailedOperation(sError); } if(isPressureProperty(sPropertyId)) { m_nLastPressureFrameIndex = nFrameIndex; } emit stateChanged(); return true; } bool nmPebiResultPlaybackController::selectFirstFrame() { return selectFrame(0); } bool nmPebiResultPlaybackController::selectPreviousFrame() { return selectFrame(qMax(0, getCurrentFrameIndex() - 1)); } bool nmPebiResultPlaybackController::selectNextFrame() { const int nLastFrame = getCurrentPropertyFrameCount() - 1; return selectFrame(qMin(nLastFrame, getCurrentFrameIndex() + 1)); } bool nmPebiResultPlaybackController::selectLastFrame() { return selectFrame(getCurrentPropertyFrameCount() - 1); } bool nmPebiResultPlaybackController::togglePlayback() { if(m_bPlaying) { pause(); return true; } if(!synchronizeSceneIdentity()) { return finishFailedOperation(playbackText( "Result playback is unavailable.")); } const QString sPropertyId = getCurrentPropertyId(); const int nFrameCount = getPropertyFrameCount(sPropertyId); if(!isPropertyTimeDependent(sPropertyId) || nFrameCount <= 1) { return finishFailedOperation(playbackText( "Result playback is unavailable.")); } if(getCurrentFrameIndex() == nFrameCount - 1) { QString sError; if(!m_pSceneController->applyPropertyFrame( sPropertyId, 0, &sError)) { return finishFailedOperation(sError); } if(isPressureProperty(sPropertyId)) { m_nLastPressureFrameIndex = 0; } } const quint64 nBindingRevision = m_pSceneController->getSnapshotBindingRevision(); if(nBindingRevision == 0 || nBindingRevision != m_nObservedBindingRevision) { return finishFailedOperation(playbackText( "Result playback is unavailable.")); } // Timer 归属只在全部校验和末帧复位成功后发布,防止旧回调推进新绑定。 m_nPlaybackBindingRevision = nBindingRevision; m_pTimer->setInterval(calculatePlaybackInterval()); m_pTimer->start(); m_bPlaying = true; emit stateChanged(); return true; } void nmPebiResultPlaybackController::pause() { if(stopTimerWithoutSignal()) { emit stateChanged(); } } bool nmPebiResultPlaybackController::setPlaybackSettings( int nStepsPerSecond, bool bLoopEnabled) { if(nStepsPerSecond < MinimumPlaybackSpeed || nStepsPerSecond > MaximumPlaybackSpeed) { // 公开接口不能依赖设置对话框兜底;非法参数也执行统一失败收尾。 return finishFailedOperation(playbackText( "Playback speed must be between 1 and 60 steps per second.")); } if(m_nStepsPerSecond == nStepsPerSecond && m_bLoopEnabled == bLoopEnabled) { return true; } // 两个设置在完整校验后一次提交,避免速度失败但循环开关已改变。 m_nStepsPerSecond = nStepsPerSecond; m_bLoopEnabled = bLoopEnabled; if(m_pTimer->isActive()) { m_pTimer->setInterval(calculatePlaybackInterval()); } emit stateChanged(); return true; } bool nmPebiResultPlaybackController::isAvailable() const { return m_pSceneController != nullptr && m_pSceneController->hasSnapshot(); } QString nmPebiResultPlaybackController::getObservedSnapshotId() const { return m_sObservedSnapshotId; } quint64 nmPebiResultPlaybackController::getObservedBindingRevision() const { return m_nObservedBindingRevision; } QStringList nmPebiResultPlaybackController::getAvailablePropertyIds() const { return isAvailable() ? m_pSceneController->getAvailablePropertyIds() : QStringList(); } QString nmPebiResultPlaybackController::getCurrentPropertyId() const { return isAvailable() ? m_pSceneController->getCurrentPropertyId() : QString(); } int nmPebiResultPlaybackController::getCurrentFrameIndex() const { return isAvailable() ? m_pSceneController->getCurrentFrameIndex() : -1; } bool nmPebiResultPlaybackController::isPropertyTimeDependent( const QString& sPropertyId) const { return isAvailable() && m_pSceneController->isPropertyTimeDependent(sPropertyId); } int nmPebiResultPlaybackController::getPropertyFrameCount( const QString& sPropertyId) const { return isAvailable() ? m_pSceneController->getPropertyFrameCount(sPropertyId) : 0; } bool nmPebiResultPlaybackController::getPropertyTimeAt( const QString& sPropertyId, int nFrameIndex, double& dTime) const { return isAvailable() && m_pSceneController->getPropertyTimeAt( sPropertyId, nFrameIndex, dTime); } bool nmPebiResultPlaybackController::isCurrentPropertyTimeDependent() const { return isPropertyTimeDependent(getCurrentPropertyId()); } int nmPebiResultPlaybackController::getCurrentPropertyFrameCount() const { return getPropertyFrameCount(getCurrentPropertyId()); } int nmPebiResultPlaybackController::getLastPressureFrameIndex() const { return m_nLastPressureFrameIndex; } int nmPebiResultPlaybackController::getPlaybackSpeed() const { return m_nStepsPerSecond; } int nmPebiResultPlaybackController::getPlaybackIntervalMilliseconds() const { return calculatePlaybackInterval(); } bool nmPebiResultPlaybackController::isLoopEnabled() const { return m_bLoopEnabled; } bool nmPebiResultPlaybackController::isPlaying() const { return m_bPlaying && m_pTimer != nullptr && m_pTimer->isActive(); } void nmPebiResultPlaybackController::onPlaybackTimeout() { // 先验证 Timer 启动时捕获的绑定,已经排队的旧事件不得推进后来绑定的快照。 const bool bTimerOwnsCurrentBinding = m_bPlaying && m_pTimer != nullptr && m_pTimer->isActive() && m_pSceneController != nullptr && m_pSceneController->hasSnapshot() && m_nPlaybackBindingRevision != 0 && m_pSceneController->getSnapshotBindingRevision() == m_nPlaybackBindingRevision; if(!bTimerOwnsCurrentBinding) { stopTimerWithoutSignal(); emit stateChanged(); return; } // Timer 归属通过后再做普通身份同步;任何不一致都只停止,不提交下一帧。 if(!synchronizeSceneIdentity() || !m_bPlaying || m_nPlaybackBindingRevision == 0) { stopTimerWithoutSignal(); emit stateChanged(); return; } const QString sPropertyId = getCurrentPropertyId(); const int nFrameCount = getPropertyFrameCount(sPropertyId); const int nCurrentFrame = getCurrentFrameIndex(); if(!isPropertyTimeDependent(sPropertyId) || nFrameCount <= 1 || nCurrentFrame < 0 || nCurrentFrame >= nFrameCount) { finishFailedOperation(playbackText( "Result playback is unavailable.")); return; } int nTargetFrame = nCurrentFrame + 1; if(nTargetFrame >= nFrameCount) { if(!m_bLoopEnabled) { stopTimerWithoutSignal(); emit stateChanged(); return; } nTargetFrame = 0; } QString sError; if(!m_pSceneController->applyPropertyFrame( sPropertyId, nTargetFrame, &sError)) { finishFailedOperation(sError); return; } if(isPressureProperty(sPropertyId)) { m_nLastPressureFrameIndex = nTargetFrame; } if(!m_bLoopEnabled && nTargetFrame == nFrameCount - 1) { // 末帧已经成功显示,立即切换为暂停图标并保留该帧。 stopTimerWithoutSignal(); } emit stateChanged(); } bool nmPebiResultPlaybackController::stopTimerWithoutSignal() { const bool bWasPlaying = m_bPlaying || (m_pTimer != nullptr && m_pTimer->isActive()); if(m_pTimer != nullptr) { m_pTimer->stop(); } m_bPlaying = false; m_nPlaybackBindingRevision = 0; return bWasPlaying; } bool nmPebiResultPlaybackController::synchronizeSceneIdentity() { if(m_pSceneController == nullptr || !m_pSceneController->hasSnapshot()) { stopTimerWithoutSignal(); m_sObservedSnapshotId.clear(); m_nObservedBindingRevision = 0; m_nLastPressureFrameIndex = 0; return false; } const QString sSnapshotId = m_pSceneController->getCurrentSnapshotId(); const quint64 nBindingRevision = m_pSceneController->getSnapshotBindingRevision(); if(sSnapshotId.isEmpty() || nBindingRevision == 0) { stopTimerWithoutSignal(); m_sObservedSnapshotId.clear(); m_nObservedBindingRevision = 0; m_nLastPressureFrameIndex = 0; return false; } if(m_nObservedBindingRevision != nBindingRevision || m_sObservedSnapshotId != sSnapshotId) { stopTimerWithoutSignal(); m_sObservedSnapshotId = sSnapshotId; m_nObservedBindingRevision = nBindingRevision; // 新快照由 SceneController 固定提交压力第 0 帧;无压力分支仍从 0 开始。 m_nLastPressureFrameIndex = isPressureProperty(getCurrentPropertyId()) && getCurrentFrameIndex() >= 0 ? getCurrentFrameIndex() : 0; } return true; } bool nmPebiResultPlaybackController::finishFailedOperation( const QString& sError) { stopTimerWithoutSignal(); // Scene 极端回滚失败时可能已经自清空,此处先同步为不可用状态。 synchronizeSceneIdentity(); emit stateChanged(); emit operationFailed(sError.isEmpty() ? playbackText("Result playback is unavailable.") : sError); return false; } int nmPebiResultPlaybackController::calculatePlaybackInterval() const { return qMax(1, qRound(1000.0 / static_cast(m_nStepsPerSecond))); }