diff --git a/Bin/Config/Lang/cn/nmNum_cn.qm b/Bin/Config/Lang/cn/nmNum_cn.qm index 10b3a18..66f8a76 100644 Binary files a/Bin/Config/Lang/cn/nmNum_cn.qm and b/Bin/Config/Lang/cn/nmNum_cn.qm differ diff --git a/Bin/Config/Lang/cn/nmNum_cn.ts b/Bin/Config/Lang/cn/nmNum_cn.ts index 0b008ba..0c52fa8 100644 --- a/Bin/Config/Lang/cn/nmNum_cn.ts +++ b/Bin/Config/Lang/cn/nmNum_cn.ts @@ -2854,6 +2854,22 @@ Reason: %1 Solver completed 求解完成 + + Solver result discarded + 求解结果已丢弃 + + + The input changed while the solver was preparing or running. The outdated result was discarded. + 输入数据在求解准备或运行期间发生了变化,已丢弃本次过期结果。 + + + Result display error + 结果显示错误 + + + Solver completed and the results were committed, but the result window could not be opened. + 求解已完成且结果已提交,但无法打开结果窗口。 + Grid count: %1 Solve time: %2 ms diff --git a/Include/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.h b/Include/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.h index dcb0b3b..fac7291 100644 --- a/Include/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.h +++ b/Include/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.h @@ -53,6 +53,8 @@ class NMCALCULATION_EXPORT nmCalculationDllPebiSolverTask : public QThread { void requestCancel(); /** @brief 返回本次任务是否因手工停止而结束。 */ bool wasCancelled() const; + /** @brief 返回任务启动后网格或求解输入是否已经变化。 */ + bool hasInputChanged(nmDataAnalyzeManager* pDataManager) const; /** * @brief 在 DataManager 所属线程分批捕获手工求解输入。 * @param nMaxWellCount 本次最多捕获的真实井数量。 diff --git a/Include/nmNum/nmSubWnd/nmSubWndMain.h b/Include/nmNum/nmSubWnd/nmSubWndMain.h index 087bfad..65d3cd5 100644 --- a/Include/nmNum/nmSubWnd/nmSubWndMain.h +++ b/Include/nmNum/nmSubWnd/nmSubWndMain.h @@ -137,7 +137,7 @@ class NM_SUB_WND_EXPORT nmSubWndMain : public iSubWndBaseFit { virtual bool slotSaveAll(); // 求解完成 - void on_calculationFinished(NM_Calculation_Result); + bool on_calculationFinished(NM_Calculation_Result); // DLL线程只返回成功/失败,这里转换回原来的计算结果处理流程。 void on_solverTaskFinished(bool isSuccessed); /** @brief 完成回调退出后隐藏求解进度框。 */ @@ -146,6 +146,8 @@ class NM_SUB_WND_EXPORT nmSubWndMain : public iSubWndBaseFit { void completePendingSolverResult(); /** @brief 首次结果载体创建完成后,在下一轮事件中填充三维和参数页面。 */ void populatePendingResultWidgets(); + /** @brief 首次结果页面填充调用栈退出后显示最终求解提示。 */ + void showPendingSolverResultCompletion(); /** @brief 进度框显示后再创建任务,避免输入捕获发生在首次绘制之前。 */ void startSolverTask(); /** @brief 每轮事件只捕获少量井输入,保持准备阶段界面和停止按钮可响应。 */ @@ -233,16 +235,21 @@ class NM_SUB_WND_EXPORT nmSubWndMain : public iSubWndBaseFit { // 从菜单点击绘制,除非ToolBar上的点击 void triggerToolBarAction(int index); // 合并数据到流量端分析窗口 - void mergeAnaResultToFitting(); + bool mergeAnaResultToFitting(); // 填充已经存在的数值三维和结果参数页面。 - void populateResultWidgets(iSubWndFitting* pSubWndFit, + bool populateResultWidgets(iSubWndFitting* pSubWndFit, nmDataAnalyzeManager* pDataManager); - // 将首次结果载体的页面填充排队到当前模态调用结束之后。 + // 将首次结果载体的页面填充排队到当前创建事件结束之后。 void schedulePendingResultWidgets(); + // 根据结果页面是否准备成功显示本次求解的最终提示。 + void showSolverResultCompletion(bool bResultWindowReady, + int nPebiCount, + int nSolveTimeMs); // 先隐藏求解进度框,再排队处理结果界面。 void scheduleSolverResultCompletion(bool bSucceeded, int nPebiCount, - int nSolveTimeMs); + int nSolveTimeMs, + bool bInputChanged = false); // 半对数曲线数据 bool inithalfLog(QVector> & vvecLogPreData, QVector& vecDescs,bool isPreLoadData); // 双对数曲线数据 @@ -290,8 +297,13 @@ private: /** @brief 首次结果载体创建后,延迟页面填充所需的拟合窗口。 */ QPointer m_pPendingResultFitting; bool m_bPendingResultWidgetRefresh; ///< 正在等待下一轮事件填充首次结果页面。 + bool m_bPendingResultCompletionMessage; ///< 首次页面填充后再显示求解结果提示。 + bool m_bPendingResultWindowReady; ///< 延迟填充是否成功创建全部结果页面。 + int m_nPendingResultPebiCount; ///< 延迟提示所需的网格数量。 + int m_nPendingResultSolveTimeMs; ///< 延迟提示所需的求解耗时。 bool m_bSolverResultCompletionPending; ///< 等待进度框隐藏后处理求解结果。 bool m_bPendingSolverSucceeded; ///< 待处理求解结果是否成功。 + bool m_bPendingSolverInputChanged; ///< 输入变化导致本次旧结果被丢弃。 int m_nPendingSolverPebiCount; ///< 待显示的网格数量。 int m_nPendingSolverTimeMs; ///< 待显示的求解耗时。 bool m_bSolverStartPending; ///< 进度框已显示,输入正分批捕获或任务等待启动。 diff --git a/Src/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.cpp b/Src/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.cpp index 6ce6b41..5ab0d50 100644 --- a/Src/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.cpp +++ b/Src/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.cpp @@ -494,6 +494,21 @@ bool nmCalculationDllPebiSolverTask::wasCancelled() const isCancelRequested(); } +bool nmCalculationDllPebiSolverTask::hasInputChanged( + nmDataAnalyzeManager* pDataManager) const +{ + if(pDataManager == nullptr || pDataManager != m_pDataManager || + QThread::currentThread() != pDataManager->thread()) { + return false; + } + + const nmDataNumericalAnalysisCase* pAnalysisCase = + pDataManager->getNumericalAnalysisCase(); + return pAnalysisCase != nullptr && + (pAnalysisCase->getGridInputRevision() != m_nGridInputRevision || + pAnalysisCase->getResultInputRevision() != m_nResultInputRevision); +} + bool nmCalculationDllPebiSolverTask::captureManualInputStep( int nMaxWellCount, bool& bFinished) diff --git a/Src/nmNum/nmSubWnd/nmSubWndMain.cpp b/Src/nmNum/nmSubWnd/nmSubWndMain.cpp index 39f2860..e8767ce 100644 --- a/Src/nmNum/nmSubWnd/nmSubWndMain.cpp +++ b/Src/nmNum/nmSubWnd/nmSubWndMain.cpp @@ -133,8 +133,13 @@ nmSubWndMain::nmSubWndMain(QWidget *parent, QString sExt) : m_pPendingResultDataManager = nullptr; m_pPendingResultFitting = nullptr; m_bPendingResultWidgetRefresh = false; + m_bPendingResultCompletionMessage = false; + m_bPendingResultWindowReady = false; + m_nPendingResultPebiCount = -1; + m_nPendingResultSolveTimeMs = -1; m_bSolverResultCompletionPending = false; m_bPendingSolverSucceeded = false; + m_bPendingSolverInputChanged = false; m_nPendingSolverPebiCount = -1; m_nPendingSolverTimeMs = -1; m_bSolverStartPending = false; @@ -1742,6 +1747,8 @@ void nmSubWndMain::continueSolverInputCapture() if(!bSucceeded) { nmCalculationDllPebiSolverTask* pTask = m_pSolverTask; const bool bCancelled = pTask->wasCancelled(); + const bool bInputChanged = + pTask->hasInputChanged(m_pSolverDataManager.data()); m_pSolverTask = nullptr; if(pTask == s_pRunningSolverTask) { s_pRunningSolverTask = nullptr; @@ -1761,7 +1768,7 @@ void nmSubWndMain::continueSolverInputCapture() m_pSolverDataManager = nullptr; m_pSolverFitting = nullptr; } else { - scheduleSolverResultCompletion(false, -1, -1); + scheduleSolverResultCompletion(false, -1, -1, bInputChanged); } m_bSolverCancelRequested = false; return; @@ -1923,7 +1930,7 @@ void nmSubWndMain::triggerToolBarAction(int index) } } -void nmSubWndMain::mergeAnaResultToFitting() +bool nmSubWndMain::mergeAnaResultToFitting() { //QMessageBox::information(this, tr("solver success"), tr("solver succeed!")); @@ -1938,14 +1945,14 @@ void nmSubWndMain::mergeAnaResultToFitting() ZxMainWindow* pMainWnd1 = const_cast(m_pMainWindow); if(nullptr == pMainWnd1) { - return; + return false; } iSubWndFitting* pSubWndFit = !m_pSolverFitting.isNull() ? m_pSolverFitting.data() : nmDataAnalyzeManager::getCurrentFitting(); Q_ASSERT(pSubWndFit != nullptr); if(pSubWndFit == nullptr) { - return; + return false; } // 历史压力数据 @@ -1959,18 +1966,22 @@ void nmSubWndMain::mergeAnaResultToFitting() ? m_pSolverDataManager.data() : nmDataAnalyzeManager::getCurrentInstance(); if(pInstance == nullptr) { - return; + return false; } // 捕获的拟合窗口和数据管理器必须仍保持原来的映射关系。 if(nmDataAnalyzeManager::findManagerByFitting(pSubWndFit) != pInstance) { - return; + return false; } // 新一轮主动刷新替代尚未执行的旧请求;已经投递的旧槽会检测标记并直接返回。 m_bPendingResultWidgetRefresh = false; m_pPendingResultDataManager = nullptr; m_pPendingResultFitting = nullptr; + m_bPendingResultCompletionMessage = false; + m_bPendingResultWindowReady = false; + m_nPendingResultPebiCount = -1; + m_nPendingResultSolveTimeMs = -1; QVector vecWellsOrder = pInstance->getSolverWellOrder(); QString sCurrentResultWellCode = pInstance->getCurrentResultWellCode(); @@ -2006,7 +2017,7 @@ void nmSubWndMain::mergeAnaResultToFitting() if(pInstance->getGridType() == NM_Grid_PEBI) { pWellData = pInstance->findWellByCode(sCurrentResultWellCode); if(pWellData == NULL) { - return; + return false; } pInstance->setCurrentResultWellCode(sCurrentResultWellCode); // 获取该井下存储的三条数据 @@ -2040,7 +2051,7 @@ void nmSubWndMain::mergeAnaResultToFitting() pSubWndFit->adjustFitSubPlotBy(tag, vecHistory, true, &errorMessage4); if(pInstance->getGridType() != NM_Grid_PEBI) { - return; + return true; } // getFitSubRstWxOf(..., true) 会同步创建 iWxResult、挂入 MDI 并切换当前页。 @@ -2051,33 +2062,33 @@ void nmSubWndMain::mergeAnaResultToFitting() p3DWidget = pSubWndFit->getFitSubRstWxOf( FSRT_Nm3D, true, &errorMessage); if(p3DWidget == nullptr) { - return; + return false; } m_pPendingResultDataManager = pInstance; m_pPendingResultFitting = pSubWndFit; m_bPendingResultWidgetRefresh = true; - return; + return true; } - populateResultWidgets(pSubWndFit, pInstance); + return populateResultWidgets(pSubWndFit, pInstance); } -void nmSubWndMain::populateResultWidgets( +bool nmSubWndMain::populateResultWidgets( iSubWndFitting* pSubWndFit, nmDataAnalyzeManager* pDataManager) { if(pSubWndFit == nullptr || pDataManager == nullptr || pDataManager->getGridType() != NM_Grid_PEBI || nmDataAnalyzeManager::findManagerByFitting(pSubWndFit) != pDataManager) { - return; + return false; } QString sErrorMessage; QWidget* p3DWidget = pSubWndFit->getFitSubRstWxOf( FSRT_Nm3D, true, &sErrorMessage); if(p3DWidget == nullptr) { - return; + return false; } nmWxPostprocessingAnimationWidget* pVtkWidget = @@ -2106,7 +2117,7 @@ void nmSubWndMain::populateResultWidgets( QWidget* pResultPage = pSubWndFit->getFitSubRstWxOf( FSRT_NmRst, true, &sErrorMessage); if(pResultPage == nullptr) { - return; + return false; } nmWxParaPropertyPebi* pResultWidget = @@ -2132,11 +2143,12 @@ void nmSubWndMain::populateResultWidgets( } } pResultLayout->addWidget(pResultWidget); - return; + return true; } pResultWidget->setDataManager(pDataManager); pResultWidget->rebuildResultParas(); + return true; } void nmSubWndMain::schedulePendingResultWidgets() @@ -2158,7 +2170,33 @@ void nmSubWndMain::populatePendingResultWidgets() m_pPendingResultDataManager = nullptr; m_pPendingResultFitting = nullptr; - populateResultWidgets(pSubWndFit, pDataManager); + const bool bResultWindowReady = + populateResultWidgets(pSubWndFit, pDataManager); + if(m_bPendingResultCompletionMessage) { + m_bPendingResultWindowReady = bResultWindowReady; + QTimer::singleShot( + 0, + this, + SLOT(showPendingSolverResultCompletion())); + } +} + +void nmSubWndMain::showPendingSolverResultCompletion() +{ + if(!m_bPendingResultCompletionMessage) { + return; + } + + const bool bResultWindowReady = m_bPendingResultWindowReady; + const int nPebiCount = m_nPendingResultPebiCount; + const int nSolveTimeMs = m_nPendingResultSolveTimeMs; + m_bPendingResultCompletionMessage = false; + m_bPendingResultWindowReady = false; + m_nPendingResultPebiCount = -1; + m_nPendingResultSolveTimeMs = -1; + showSolverResultCompletion(bResultWindowReady, + nPebiCount, + nSolveTimeMs); } bool nmSubWndMain::inithalfLog(QVector> & vvecLogPreData, QVector& vecDescs, bool isHistoryData) @@ -2373,24 +2411,27 @@ void nmSubWndMain::geoLayering() } } -void nmSubWndMain::on_calculationFinished(NM_Calculation_Result result) +bool nmSubWndMain::on_calculationFinished(NM_Calculation_Result result) { if(result == NM_Calculation_Result_Success) { - this->mergeAnaResultToFitting(); + return this->mergeAnaResultToFitting(); } else { // 如果失败,进度条其实没必要拉满了,直接提示错误 QMessageBox::warning(this, tr("solver error"), tr("solver failed!")); + return false; } } void nmSubWndMain::scheduleSolverResultCompletion( bool bSucceeded, int nPebiCount, - int nSolveTimeMs) + int nSolveTimeMs, + bool bInputChanged) { s_pPendingSolverWindow = this; m_bSolverResultCompletionPending = true; m_bPendingSolverSucceeded = bSucceeded; + m_bPendingSolverInputChanged = bInputChanged; m_nPendingSolverPebiCount = nPebiCount; m_nPendingSolverTimeMs = nSolveTimeMs; @@ -2413,36 +2454,71 @@ void nmSubWndMain::completePendingSolverResult() } const bool bSucceeded = m_bPendingSolverSucceeded; + const bool bInputChanged = m_bPendingSolverInputChanged; const int nPebiCount = m_nPendingSolverPebiCount; const int nSolveTimeMs = m_nPendingSolverTimeMs; m_bSolverResultCompletionPending = false; m_bPendingSolverSucceeded = false; + m_bPendingSolverInputChanged = false; m_nPendingSolverPebiCount = -1; m_nPendingSolverTimeMs = -1; - on_calculationFinished(bSucceeded - ? NM_Calculation_Result_Success - : NM_Calculation_Result_Fail); + bool bResultWindowReady = false; + if(bInputChanged) { + QMessageBox::warning( + this, + tr("Solver result discarded"), + tr("The input changed while the solver was preparing or running. " + "The outdated result was discarded.")); + } else { + bResultWindowReady = on_calculationFinished(bSucceeded + ? NM_Calculation_Result_Success + : NM_Calculation_Result_Fail); + } // mergeAnaResultToFitting() 已使用完启动时捕获的上下文。 m_pSolverDataManager = nullptr; m_pSolverFitting = nullptr; m_bSolverCancelRequested = false; - if(bSucceeded && nPebiCount >= 0 && nSolveTimeMs >= 0) { - QMessageBox::information(this, tr("Solver completed"), - tr("Grid count: %1\nSolve time: %2 ms") - .arg(nPebiCount) - .arg(nSolveTimeMs)); + if(bSucceeded && bResultWindowReady && + m_bPendingResultWidgetRefresh) { + // 首次结果页尚未填充,最终提示必须等页面真实创建结果返回后再显示。 + m_bPendingResultCompletionMessage = true; + m_nPendingResultPebiCount = nPebiCount; + m_nPendingResultSolveTimeMs = nSolveTimeMs; + } else if(bSucceeded) { + showSolverResultCompletion(bResultWindowReady, + nPebiCount, + nSolveTimeMs); } - // 首次结果载体的填充必须在成功提示的模态事件循环完全退出后执行。 + // 首次结果载体创建后再跨一个事件循环填充,避免 Qt 4.8 布局重入。 schedulePendingResultWidgets(); if(s_pPendingSolverWindow == this) { s_pPendingSolverWindow = nullptr; } } +void nmSubWndMain::showSolverResultCompletion( + bool bResultWindowReady, + int nPebiCount, + int nSolveTimeMs) +{ + if(bResultWindowReady && nPebiCount >= 0 && nSolveTimeMs >= 0) { + QMessageBox::information(this, tr("Solver completed"), + tr("Grid count: %1\nSolve time: %2 ms") + .arg(nPebiCount) + .arg(nSolveTimeMs)); + } else if(!bResultWindowReady) { + QMessageBox::warning( + this, + tr("Result display error"), + tr("Solver completed and the results were committed, but the " + "result window could not be opened.")); + } +} + void nmSubWndMain::on_solverTaskFinished(bool isSuccessed) { // 第一步:只接受当前窗口实际启动的任务,忽略已经失效的旧回调。 @@ -2454,14 +2530,18 @@ void nmSubWndMain::on_solverTaskFinished(bool isSuccessed) int nPebiCount = pTask != nullptr ? pTask->getPebiCount() : -1; int nSolveTimeMs = pTask != nullptr ? pTask->getSolveTimeMs() : -1; const bool bCancelled = pTask->wasCancelled(); + bool bInputChanged = false; // 后台线程只生成局部结果快照。回到主线程后先验证所属 DataManager 和 - // 输入版本,再整体替换旧成果;提交失败与求解失败使用同一条错误路径。 + // 输入版本,再整体替换旧成果;输入变化与真正的求解失败分别提示。 if(isSuccessed && !bCancelled) { if(m_pProgressDlg != nullptr) { m_pProgressDlg->setLabelText(tr("Committing results...")); m_pProgressDlg->setRange(0, 0); } + bInputChanged = + pTask->hasInputChanged(m_pSolverDataManager.data()); isSuccessed = pTask->commitResult(m_pSolverDataManager.data()); + bInputChanged = !isSuccessed && bInputChanged; } else if(bCancelled) { isSuccessed = false; } @@ -2483,7 +2563,8 @@ void nmSubWndMain::on_solverTaskFinished(bool isSuccessed) } else { scheduleSolverResultCompletion(isSuccessed, nPebiCount, - nSolveTimeMs); + nSolveTimeMs, + bInputChanged); } }