You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
nmWTAI-Platform/Src/nmNum/nmSubWxs/nmPebiResultPlaybackControl...

488 lines
15 KiB
C++

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#include "nmPebiResultPlaybackController.h"
#include "nmPebiResultSceneController.h"
#include <QCoreApplication>
#include <QTimer>
#include <QtGlobal>
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 a positive integer.")
};
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) {
// 公开接口不能依赖设置对话框兜底;非法参数也执行统一失败收尾。
return finishFailedOperation(playbackText(
"Playback speed must be a positive integer."));
}
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<double>(m_nStepsPerSecond)));
}