diff --git a/src/CalcViewModel/GraphingCalculator/VariableViewModel.h b/src/CalcViewModel/GraphingCalculator/VariableViewModel.h index d4dd699e..4d9240e8 100644 --- a/src/CalcViewModel/GraphingCalculator/VariableViewModel.h +++ b/src/CalcViewModel/GraphingCalculator/VariableViewModel.h @@ -47,11 +47,13 @@ public { if (value < Min) { - value = Min; + Min = value; + RaisePropertyChanged(L"Min"); } else if (value > Max) { - value = Max; + Max = value; + RaisePropertyChanged(L"Max"); } if (Value != value) diff --git a/src/Calculator/Controls/EquationTextBox.cpp b/src/Calculator/Controls/EquationTextBox.cpp index 718c9101..a5d6d69b 100644 --- a/src/Calculator/Controls/EquationTextBox.cpp +++ b/src/Calculator/Controls/EquationTextBox.cpp @@ -319,26 +319,6 @@ void EquationTextBox::OnHasErrorPropertyChanged(bool, bool) UpdateCommonVisualState(); } -Platform::String ^ EquationTextBox::GetEquationText() -{ - String ^ text; - if (m_richEditBox != nullptr) - { - // Clear formatting since the graph control doesn't work with bold/underlines - ITextRange ^ range = m_richEditBox->TextDocument->GetRange(0, m_richEditBox->TextDocument->Selection->EndPosition); - - if (range != nullptr) - { - range->CharacterFormat->Bold = FormatEffect::Off; - range->CharacterFormat->Underline = UnderlineType::None; - } - - text = m_richEditBox->MathText; - } - - return text; -} - void EquationTextBox::SetEquationText(Platform::String ^ equationText) { if (m_richEditBox != nullptr) diff --git a/src/Calculator/Controls/EquationTextBox.h b/src/Calculator/Controls/EquationTextBox.h index d2451ad0..15759ec6 100644 --- a/src/Calculator/Controls/EquationTextBox.h +++ b/src/Calculator/Controls/EquationTextBox.h @@ -33,7 +33,6 @@ namespace CalculatorApp event Windows::Foundation::EventHandler ^ EquationFormatRequested; event Windows::UI::Xaml::RoutedEventHandler ^ EquationButtonClicked; - Platform::String ^ GetEquationText(); void SetEquationText(Platform::String ^ equationText); void FocusTextBox(); diff --git a/src/Calculator/Controls/MathRichEditBox.cpp b/src/Calculator/Controls/MathRichEditBox.cpp index e97d3822..b607ff8b 100644 --- a/src/Calculator/Controls/MathRichEditBox.cpp +++ b/src/Calculator/Controls/MathRichEditBox.cpp @@ -12,6 +12,7 @@ using namespace std; using namespace Windows::ApplicationModel; using namespace Windows::UI::Xaml; using namespace Windows::UI::Xaml::Controls; +using namespace Windows::UI::Text; using namespace Windows::Foundation::Collections; using namespace Windows::System; @@ -178,6 +179,15 @@ void MathRichEditBox::BackSpace() void MathRichEditBox::SubmitEquation(EquationSubmissionSource source) { + // Clear formatting since the graph control doesn't work with bold/underlines + auto range = this->TextDocument->GetRange(0, this->TextDocument->Selection->EndPosition); + + if (range != nullptr) + { + range->CharacterFormat->Bold = FormatEffect::Off; + range->CharacterFormat->Underline = UnderlineType::None; + } + auto newVal = GetMathTextProperty(); if (MathText != newVal) { diff --git a/src/Calculator/Views/GraphingCalculator/EquationInputArea.xaml.cpp b/src/Calculator/Views/GraphingCalculator/EquationInputArea.xaml.cpp index f5ee56cf..8dd897b9 100644 --- a/src/Calculator/Views/GraphingCalculator/EquationInputArea.xaml.cpp +++ b/src/Calculator/Views/GraphingCalculator/EquationInputArea.xaml.cpp @@ -330,9 +330,6 @@ void EquationInputArea::SubmitTextbox(TextBox ^ sender) { val = validateDouble(sender->Text, variableViewModel->Value); variableViewModel->Value = val; - - // Assign back to val in case it gets changed due to min/max - val = variableViewModel->Value; } else if (sender->Name == "MinTextBox") { diff --git a/src/GraphControl/Control/Grapher.cpp b/src/GraphControl/Control/Grapher.cpp index 20e59a65..3a770808 100644 --- a/src/GraphControl/Control/Grapher.cpp +++ b/src/GraphControl/Control/Grapher.cpp @@ -12,11 +12,13 @@ using namespace GraphControl::DX; using namespace Platform; using namespace Platform::Collections; using namespace std; +using namespace Concurrency; using namespace Windows::Devices::Input; using namespace Windows::Foundation; using namespace Windows::Foundation::Collections; using namespace Windows::Storage::Streams; using namespace Windows::System; +using namespace Windows::System::Threading; using namespace Windows::UI; using namespace Windows::UI::Core; using namespace Windows::UI::Input; @@ -228,9 +230,9 @@ namespace GraphControl TryPlotGraph(keepCurrentView, false); } - void Grapher::TryPlotGraph(bool keepCurrentView, bool shouldRetry) + task Grapher::TryPlotGraph(bool keepCurrentView, bool shouldRetry) { - if (TryUpdateGraph(keepCurrentView)) + if (co_await TryUpdateGraph(keepCurrentView)) { SetEquationsAsValid(); } @@ -241,12 +243,12 @@ namespace GraphControl // If we failed to plot the graph, try again after the bad equations are flagged. if (shouldRetry) { - TryUpdateGraph(keepCurrentView); + co_await TryUpdateGraph(keepCurrentView); } } } - bool Grapher::TryUpdateGraph(bool keepCurrentView) + task Grapher::TryUpdateGraph(bool keepCurrentView) { optional>> initResult = nullopt; bool successful = false; @@ -282,7 +284,7 @@ namespace GraphControl // If the equation request failed, then fail graphing. if (equationRequest == nullptr) { - return false; + co_return false; } request += equationRequest; @@ -303,7 +305,8 @@ namespace GraphControl m_renderMain->Graph = m_graph; // It is possible that the render fails, in that case fall through to explicit empty initialization - if (m_renderMain->RunRenderPass()) + co_await m_renderMain->RunRenderPassAsync(false); + if (m_renderMain->IsRenderPassSuccesful()) { UpdateVariables(); successful = true; @@ -329,7 +332,7 @@ namespace GraphControl SetGraphArgs(); m_renderMain->Graph = m_graph; - m_renderMain->RunRenderPass(); + co_await m_renderMain->RunRenderPassAsync(); UpdateVariables(); @@ -341,7 +344,7 @@ namespace GraphControl } // Return true if we were able to graph and render all graphable equations - return successful; + co_return successful; } void Grapher::SetEquationsAsValid() @@ -365,8 +368,10 @@ namespace GraphControl void Grapher::SetGraphArgs() { - if (m_graph) + if (m_graph != nullptr && m_renderMain != nullptr) { + critical_section::scoped_lock lock(m_renderMain->GetCriticalSection()); + for (auto variable : Variables) { m_graph->SetArgValue(variable->Key->Data(), variable->Value); @@ -436,14 +441,19 @@ namespace GraphControl Variables->Insert(variableName, newValue); - if (m_graph) + if (m_graph != nullptr && m_renderMain != nullptr) { - m_graph->SetArgValue(variableName->Data(), newValue); - if (m_renderMain) - { - m_renderMain->RunRenderPass(); - } + auto workItemHandler = ref new WorkItemHandler([this, variableName, newValue](IAsyncAction ^ action) { + m_renderMain->GetCriticalSection().lock(); + m_graph->SetArgValue(variableName->Data(), newValue); + m_renderMain->GetCriticalSection().unlock(); + + m_renderMain->RunRenderPassAsync(); + }); + + ThreadPool::RunAsync(workItemHandler, WorkItemPriority::High, WorkItemOptions::None); + } } diff --git a/src/GraphControl/Control/Grapher.h b/src/GraphControl/Control/Grapher.h index 4f902c6a..9b00acc2 100644 --- a/src/GraphControl/Control/Grapher.h +++ b/src/GraphControl/Control/Grapher.h @@ -272,8 +272,8 @@ public void OnEquationChanged(Equation ^ equation); void OnEquationStyleChanged(Equation ^ equation); void OnEquationLineEnabledChanged(Equation ^ equation); - bool TryUpdateGraph(bool keepCurrentView); - void TryPlotGraph(bool keepCurrentView, bool shouldRetry); + concurrency::task TryUpdateGraph(bool keepCurrentView); + concurrency::task TryPlotGraph(bool keepCurrentView, bool shouldRetry); void UpdateGraphOptions(Graphing::IGraphingOptions& options, const std::vector& validEqs); std::vector GetGraphableEquations(); void SetGraphArgs(); diff --git a/src/GraphControl/DirectX/RenderMain.cpp b/src/GraphControl/DirectX/RenderMain.cpp index b06effb3..169af46b 100644 --- a/src/GraphControl/DirectX/RenderMain.cpp +++ b/src/GraphControl/DirectX/RenderMain.cpp @@ -63,7 +63,6 @@ namespace GraphControl::DX renderer->SetDpi(dpi, dpi); renderer->SetGraphSize(static_cast(m_swapChainPanel->ActualWidth), static_cast(m_swapChainPanel->ActualHeight)); - } } } @@ -130,6 +129,54 @@ namespace GraphControl::DX bool RenderMain::RunRenderPass() { + // Non async render passes cancel if they can't obtain the lock immediatly + if (!m_criticalSection.try_lock()) + { + return false; + } + + m_criticalSection.unlock(); + + critical_section::scoped_lock lock(m_criticalSection); + + return RunRenderPassInternal(); + } + + IAsyncAction ^ RenderMain::RunRenderPassAsync(bool allowCancel) + { + // Try to cancel the renderPass that is in progress + if (m_renderPass != nullptr && m_renderPass->Status == ::AsyncStatus::Started) + { + m_renderPass->Cancel(); + } + + auto device = m_deviceResources; + auto workItemHandler = ref new WorkItemHandler([this, allowCancel](IAsyncAction ^ action) { + critical_section::scoped_lock lock(m_criticalSection); + + // allowCancel is passed as false when the grapher relies on the render pass to validate that an equation can be succesfully rendered. + // Passing false garauntees that another render pass doesn't cancel this one. + if (allowCancel && action->Status == ::AsyncStatus::Canceled) + { + return; + } + + RunRenderPassInternal(); + }); + + m_renderPass = ThreadPool::RunAsync(workItemHandler, WorkItemPriority::High, WorkItemOptions::None); + + return m_renderPass; + } + + bool RenderMain::RunRenderPassInternal() + { + // We are accessing Direct3D resources directly without Direct2D's knowledge, so we + // must manually acquire and apply the Direct2D factory lock. + ID2D1Multithread* m_D2DMultithread; + m_deviceResources.GetD2DFactory()->QueryInterface(IID_PPV_ARGS(&m_D2DMultithread)); + m_D2DMultithread->Enter(); + bool succesful = Render(); if (succesful) @@ -137,7 +184,12 @@ namespace GraphControl::DX m_deviceResources.Present(); } - return succesful; + // It is absolutely critical that the factory lock be released upon + // exiting this function, or else any consequent Direct2D calls will be blocked. + m_D2DMultithread->Leave(); + + m_isRenderPassSuccesful = succesful; + return m_isRenderPassSuccesful; } // Renders the current frame according to the current application state. @@ -204,11 +256,11 @@ namespace GraphControl::DX { auto lineColors = m_graph->GetOptions().GetGraphColors(); - if (formulaId >= 0 && static_cast(formulaId) < lineColors.size()) - { - auto dotColor = lineColors[formulaId]; - m_nearestPointRenderer.SetColor(D2D1::ColorF(dotColor.R * 65536 + dotColor.G * 256 + dotColor.B, 1.0)); - } + if (formulaId >= 0 && static_cast(formulaId) < lineColors.size()) + { + auto dotColor = lineColors[formulaId]; + m_nearestPointRenderer.SetColor(D2D1::ColorF(dotColor.R * 65536 + dotColor.G * 256 + dotColor.B, 1.0)); + } m_TraceLocation = Point(nearestPointLocationX, nearestPointLocationY); m_nearestPointRenderer.Render(m_TraceLocation); diff --git a/src/GraphControl/DirectX/RenderMain.h b/src/GraphControl/DirectX/RenderMain.h index 7524e2ec..f4647113 100644 --- a/src/GraphControl/DirectX/RenderMain.h +++ b/src/GraphControl/DirectX/RenderMain.h @@ -47,6 +47,18 @@ namespace GraphControl::DX bool RunRenderPass(); + Windows::Foundation::IAsyncAction ^ RunRenderPassAsync(bool allowCancel = true); + + Concurrency::critical_section& GetCriticalSection() + { + return m_criticalSection; + } + + bool IsRenderPassSuccesful() + { + return m_isRenderPassSuccesful; + } + // Indicates if we are in active tracing mode (the tracing box is being used and controlled through keyboard input) property bool ActiveTracing { @@ -99,6 +111,8 @@ namespace GraphControl::DX private: bool Render(); + bool RunRenderPassInternal(); + // Loaded/Unloaded void OnLoaded(Platform::Object ^ sender, Windows::UI::Xaml::RoutedEventArgs ^ e); @@ -162,5 +176,11 @@ namespace GraphControl::DX // Are we currently showing the tracing value bool m_Tracing; + + Concurrency::critical_section m_criticalSection; + + Windows::Foundation::IAsyncAction ^ m_renderPass = nullptr; + + bool m_isRenderPassSuccesful; }; }