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...

489 lines
15 KiB
C++

#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 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<double>(m_nStepsPerSecond)));
}