From b6100156b14caeb6ea60385c90dfe84c2091f1d3 Mon Sep 17 00:00:00 2001 From: Martin Misol Monzo Date: Tue, 14 Apr 2020 11:42:01 -0400 Subject: [PATCH 1/2] Handle memory outage on render (cherry pick) This brings the fix from master to the helix-upgrade branch. The only difference is that the test now mocks the AggregateRenderPackage function directly, because the function mocked in master no longer exists. The visibility was changes to protected virtual in order to be able to mock it. --- src/DynamoCore/Models/DynamoModel.cs | 21 ++++++ src/DynamoCore/Models/DynamoModelEvents.cs | 2 +- .../Properties/Resources.Designer.cs | 9 +++ .../Properties/Resources.en-US.resx | 3 + src/DynamoCore/Properties/Resources.resx | 3 + .../Properties/Resources.Designer.cs | 18 +++++ .../Properties/Resources.en-US.resx | 6 ++ src/DynamoCoreWpf/Properties/Resources.resx | 6 ++ .../ViewModels/Core/DynamoViewModel.cs | 10 +++ .../Watch3D/HelixWatch3DViewModel.cs | 16 +++- .../HelixWatch3DViewModelTests.cs | 54 ++++++++----- .../Preview3DMemoryOutageTest.cs | 75 +++++++++++++++++++ .../WpfVisualizationTests.csproj | 4 + src/VisualizationTests/packages.config | 1 + 14 files changed, 206 insertions(+), 22 deletions(-) create mode 100644 src/VisualizationTests/Preview3DMemoryOutageTest.cs diff --git a/src/DynamoCore/Models/DynamoModel.cs b/src/DynamoCore/Models/DynamoModel.cs index 50efa49d4aa..159e1111a77 100644 --- a/src/DynamoCore/Models/DynamoModel.cs +++ b/src/DynamoCore/Models/DynamoModel.cs @@ -2109,6 +2109,27 @@ private void DisplayInvalidInputSymbolWarning() OnRequestTaskDialog(null, args); } + internal event VoidHandler Preview3DOutage; + private void OnPreview3DOutage() + { + if (Preview3DOutage != null) + { + Preview3DOutage(); + } + } + + internal void Report3DPreviewOutage(string summary, string description) + { + OnPreview3DOutage(); + + const string imageUri = "/DynamoCoreWpf;component/UI/Images/task_dialog_future_file.png"; + var args = new TaskDialogEventArgs( + new Uri(imageUri, UriKind.Relative), + Resources.Preview3DOutageTitle, summary, description); + + OnRequestTaskDialog(null, args); + } + /// /// Remove a workspace from the dynamo model. /// diff --git a/src/DynamoCore/Models/DynamoModelEvents.cs b/src/DynamoCore/Models/DynamoModelEvents.cs index 2a381d3cbe9..f65cd0c6a5f 100644 --- a/src/DynamoCore/Models/DynamoModelEvents.cs +++ b/src/DynamoCore/Models/DynamoModelEvents.cs @@ -283,7 +283,7 @@ public void OnRequestsCrashPrompt(object sender, CrashPromptArgs args) internal delegate void TaskDialogHandler(object sender, TaskDialogEventArgs e); internal event TaskDialogHandler RequestTaskDialog; - internal void OnRequestTaskDialog(object sender, TaskDialogEventArgs args) + internal virtual void OnRequestTaskDialog(object sender, TaskDialogEventArgs args) { if (RequestTaskDialog != null) RequestTaskDialog(sender, args); diff --git a/src/DynamoCore/Properties/Resources.Designer.cs b/src/DynamoCore/Properties/Resources.Designer.cs index 3c31307df9b..fe5af09064b 100644 --- a/src/DynamoCore/Properties/Resources.Designer.cs +++ b/src/DynamoCore/Properties/Resources.Designer.cs @@ -1162,6 +1162,15 @@ public static string PortsNameDescriptionDoNotEqualWarningMessage { } } + /// + /// Looks up a localized string similar to 3D preview has been deactivated. + /// + public static string Preview3DOutageTitle { + get { + return ResourceManager.GetString("Preview3DOutageTitle", resourceCulture); + } + } + /// /// Looks up a localized string similar to Proceed anyway. /// diff --git a/src/DynamoCore/Properties/Resources.en-US.resx b/src/DynamoCore/Properties/Resources.en-US.resx index bf707718a4e..a251ef48feb 100644 --- a/src/DynamoCore/Properties/Resources.en-US.resx +++ b/src/DynamoCore/Properties/Resources.en-US.resx @@ -709,4 +709,7 @@ Restart Dynamo to complete the uninstall. Attempting to load customNode {0} loaded by package {1}, but a previous definition named {2} exists with no associated package. The new customNode definition has been loaded, but Dynamo may be in an unstable state, please avoid loading multiple custom nodes with the id. + + 3D preview has been deactivated + \ No newline at end of file diff --git a/src/DynamoCore/Properties/Resources.resx b/src/DynamoCore/Properties/Resources.resx index 10c1e95bc9d..88efd0b39c9 100644 --- a/src/DynamoCore/Properties/Resources.resx +++ b/src/DynamoCore/Properties/Resources.resx @@ -709,4 +709,7 @@ Restart Dynamo to complete the uninstall. Attempting to load customNode {0} loaded by package {1}, but a previous definition named {2} exists with no associated package. The new customNode definition has been loaded, but Dynamo may be in an unstable state, please avoid loading multiple custom nodes with the id. + + 3D preview has been deactivated + \ No newline at end of file diff --git a/src/DynamoCoreWpf/Properties/Resources.Designer.cs b/src/DynamoCoreWpf/Properties/Resources.Designer.cs index 4b2a477ed1e..7d347886554 100644 --- a/src/DynamoCoreWpf/Properties/Resources.Designer.cs +++ b/src/DynamoCoreWpf/Properties/Resources.Designer.cs @@ -4652,6 +4652,24 @@ public static string QueryMember { } } + /// + /// Looks up a localized string similar to Please check if you intended to render this amount of geometry, and consider turning off the preview of other nodes within your graph, lowering the amount of Geometry you wish to render, or turning down the render precision.. + /// + public static string RenderingMemoryOutageDescription { + get { + return ResourceManager.GetString("RenderingMemoryOutageDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Dynamo has run out of memory trying to render your geometry. The geometry preview has been disabled.. + /// + public static string RenderingMemoryOutageSummary { + get { + return ResourceManager.GetString("RenderingMemoryOutageSummary", resourceCulture); + } + } + /// /// Looks up a localized string similar to Apply Changes. /// diff --git a/src/DynamoCoreWpf/Properties/Resources.en-US.resx b/src/DynamoCoreWpf/Properties/Resources.en-US.resx index 79ea4fb7937..d75cb608a60 100644 --- a/src/DynamoCoreWpf/Properties/Resources.en-US.resx +++ b/src/DynamoCoreWpf/Properties/Resources.en-US.resx @@ -2242,4 +2242,10 @@ Uninstall the following packages: {0}? Original node name: + + Please check if you intended to render this amount of geometry, and consider turning off the preview of other nodes within your graph, lowering the amount of Geometry you wish to render, or turning down the render precision. + + + Dynamo has run out of memory trying to render your geometry. The geometry preview has been disabled. + \ No newline at end of file diff --git a/src/DynamoCoreWpf/Properties/Resources.resx b/src/DynamoCoreWpf/Properties/Resources.resx index 6b3de229122..db64e30d894 100644 --- a/src/DynamoCoreWpf/Properties/Resources.resx +++ b/src/DynamoCoreWpf/Properties/Resources.resx @@ -2242,4 +2242,10 @@ Uninstall the following packages: {0}? Original node name: + + Please check if you intended to render this amount of geometry, and consider turning off the preview of other nodes within your graph, lowering the amount of Geometry you wish to render, or turning down the render precision. + + + Dynamo has run out of memory trying to render your geometry. The geometry preview has been disabled. + \ No newline at end of file diff --git a/src/DynamoCoreWpf/ViewModels/Core/DynamoViewModel.cs b/src/DynamoCoreWpf/ViewModels/Core/DynamoViewModel.cs index 69b9099ccec..aac83b78455 100644 --- a/src/DynamoCoreWpf/ViewModels/Core/DynamoViewModel.cs +++ b/src/DynamoCoreWpf/ViewModels/Core/DynamoViewModel.cs @@ -724,12 +724,14 @@ private void SubscribeModelUiEvents() { model.RequestBugReport += ReportABug; model.RequestDownloadDynamo += DownloadDynamo; + model.Preview3DOutage += Disable3DPreview; } private void UnsubscribeModelUiEvents() { model.RequestBugReport -= ReportABug; model.RequestDownloadDynamo -= DownloadDynamo; + model.Preview3DOutage -= Disable3DPreview; } private void SubscribeModelCleaningUpEvent() @@ -868,6 +870,14 @@ internal static void DownloadDynamo() Process.Start(new ProcessStartInfo("explorer.exe", Configurations.DynamoDownloadLink)); } + private void Disable3DPreview() + { + foreach (var item in Watch3DViewModels) + { + item.Active = false; + } + } + internal bool CanReportABug(object parameter) { return true; diff --git a/src/DynamoCoreWpf/ViewModels/Watch3D/HelixWatch3DViewModel.cs b/src/DynamoCoreWpf/ViewModels/Watch3D/HelixWatch3DViewModel.cs index abd9acfbe65..fce44f2096d 100644 --- a/src/DynamoCoreWpf/ViewModels/Watch3D/HelixWatch3DViewModel.cs +++ b/src/DynamoCoreWpf/ViewModels/Watch3D/HelixWatch3DViewModel.cs @@ -21,7 +21,9 @@ using Dynamo.Graph.Nodes.CustomNodes; using Dynamo.Graph.Workspaces; using Dynamo.Logging; +using Dynamo.Models; using Dynamo.Selection; +using Dynamo.UI.Prompts; using Dynamo.Utilities; using Dynamo.ViewModels; using Dynamo.Visualization; @@ -859,7 +861,17 @@ public override void GenerateViewGeometryFromRenderPackagesAndRequestUpdate(Rend var meshPackages = packages.Cast().Where(rp => rp.MeshVertexCount % 3 == 0); RemoveGeometryForUpdatedPackages(meshPackages); - AggregateRenderPackages(meshPackages); + try + { + AggregateRenderPackages(meshPackages); + } + catch (OutOfMemoryException) + { + // This can happen when the amount of packages to render is too large + string summary = Resources.RenderingMemoryOutageSummary; + var description = Resources.RenderingMemoryOutageDescription; + (dynamoModel as DynamoModel).Report3DPreviewOutage(summary, description); + } #if DEBUG renderTimer.Stop(); Debug.WriteLine(string.Format("RENDER: {0} ellapsed for compiling assets for rendering.", renderTimer.Elapsed)); @@ -1635,7 +1647,7 @@ private void ToggleTreeViewItemHighlighting(string path, bool isSelected) /// attaches them to the visual scene. /// /// An of . - private void AggregateRenderPackages(IEnumerable packages) + protected virtual void AggregateRenderPackages(IEnumerable packages) { IEnumerable customNodeIdents = null; if (InCustomNode()) diff --git a/src/VisualizationTests/HelixWatch3DViewModelTests.cs b/src/VisualizationTests/HelixWatch3DViewModelTests.cs index 2ec98fa2836..df5ec9e4f26 100644 --- a/src/VisualizationTests/HelixWatch3DViewModelTests.cs +++ b/src/VisualizationTests/HelixWatch3DViewModelTests.cs @@ -86,28 +86,21 @@ protected override void StartDynamo(TestSessionConfiguration testConfig) } } - Model = DynamoModel.Start( - new DynamoModel.DefaultStartConfiguration() - { - StartInTestMode = true, - PathResolver = pathResolver, - GeometryFactoryPath = preloader.GeometryFactoryPath, - UpdateManager = this.UpdateManager, - ProcessMode = TaskProcessMode.Synchronous - }); + Model = CreateModel(new DynamoModel.DefaultStartConfiguration() + { + StartInTestMode = true, + PathResolver = pathResolver, + GeometryFactoryPath = preloader.GeometryFactoryPath, + UpdateManager = this.UpdateManager, + ProcessMode = TaskProcessMode.Synchronous + }); Model.EvaluationCompleted += Model_EvaluationCompleted; - ViewModel = DynamoViewModel.Start( - new DynamoViewModel.StartConfiguration() - { - DynamoModel = Model, - Watch3DViewModel = - HelixWatch3DViewModel.TryCreateHelixWatch3DViewModel( - null, - new Watch3DViewModelStartupParams(Model), - Model.Logger) - }); + var vmConfig = CreateViewModelStartConfiguration(); + vmConfig.DynamoModel = Model; + + ViewModel = DynamoViewModel.Start(vmConfig); //create the view View = new DynamoView(ViewModel); @@ -116,6 +109,29 @@ protected override void StartDynamo(TestSessionConfiguration testConfig) SynchronizationContext.SetSynchronizationContext(new SynchronizationContext()); } + /// + /// Derived test classes can override this method to provide a customized Dynamo model. + /// + protected virtual DynamoModel CreateModel(DynamoModel.IStartConfiguration configuration) + { + return DynamoModel.Start(configuration); + } + + /// + /// Derived test classes can override this method to provide a customized view model configuration. + /// + protected virtual DynamoViewModel.StartConfiguration CreateViewModelStartConfiguration() + { + return new DynamoViewModel.StartConfiguration() + { + DynamoModel = Model, + Watch3DViewModel = HelixWatch3DViewModel.TryCreateHelixWatch3DViewModel( + null, + new Watch3DViewModelStartupParams(Model), + Model.Logger) + }; + } + private async void Model_EvaluationCompleted(object sender, EvaluationCompletedEventArgs e) { DispatcherUtil.DoEvents(); diff --git a/src/VisualizationTests/Preview3DMemoryOutageTest.cs b/src/VisualizationTests/Preview3DMemoryOutageTest.cs new file mode 100644 index 00000000000..67225e14329 --- /dev/null +++ b/src/VisualizationTests/Preview3DMemoryOutageTest.cs @@ -0,0 +1,75 @@ +using Dynamo.Models; +using Dynamo.ViewModels; +using Dynamo.Wpf.Rendering; +using Dynamo.Wpf.ViewModels.Watch3D; +using DynamoCoreWpfTests.Utility; +using HelixToolkit.Wpf.SharpDX; +using Moq; +using Moq.Protected; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace WpfVisualizationTests +{ + [TestFixture] + public class Preview3DMemoryOutageTest : VisualizationTest + { + private Mock modelMock; + + /// + /// Creates a mock DynamoModel that does not show a task dialog but instead sets its call + /// as a verifiable call. + /// + /// Default DynamoModel configuration + /// Mock DynamoModel + protected override DynamoModel CreateModel(DynamoModel.IStartConfiguration configuration) + { + modelMock = new Mock(configuration) + { + CallBase = true + }; + modelMock.Setup(m => m.OnRequestTaskDialog(It.IsAny(), It.IsAny())) + .Callback(() => { }) // Prevent dialog from blocking the test + .Verifiable(); + + return modelMock.Object; + } + + /// + /// Sets up a mock HelixWatch3DViewModel which will throw OutOfMemoryException when rendering. + /// + /// DynamoViewModel configuration referencing the mock 3D preview + protected override DynamoViewModel.StartConfiguration CreateViewModelStartConfiguration() + { + var watch3DMock = new Mock(null, new Watch3DViewModelStartupParams(Model)) + { + CallBase = true + }; + watch3DMock.Protected().Setup("AggregateRenderPackages", ItExpr.IsAny>()).Throws(); + return new DynamoViewModel.StartConfiguration() + { + Watch3DViewModel = watch3DMock.Object + }; + } + + /// + /// Opens any file that produces geometry and checks that the 3D preview is disabled after the error + /// and also that a dialog is shown. + /// + [Test] + public void HandlesRenderMemoryOutageGracefully() + { + Assert.Greater(ViewModel.Watch3DViewModels.Count(), 0); + Assert.True(ViewModel.Watch3DViewModels.All(w => w.Active)); + + OpenVisualizationTest("ASM_cuboid.dyn"); + + Assert.True(ViewModel.Watch3DViewModels.All(w => !w.Active)); + modelMock.Verify(); + } + } +} diff --git a/src/VisualizationTests/WpfVisualizationTests.csproj b/src/VisualizationTests/WpfVisualizationTests.csproj index 656e1085a33..010c012afd4 100644 --- a/src/VisualizationTests/WpfVisualizationTests.csproj +++ b/src/VisualizationTests/WpfVisualizationTests.csproj @@ -53,6 +53,9 @@ False ..\..\extern\prism\Microsoft.Practices.Prism.dll + + ..\packages\Moq.4.2.1507.0118\lib\net40\Moq.dll + False ..\..\extern\NUnit\nunit.framework.dll @@ -105,6 +108,7 @@ + diff --git a/src/VisualizationTests/packages.config b/src/VisualizationTests/packages.config index 8a3352aaa1c..333983cb1a9 100644 --- a/src/VisualizationTests/packages.config +++ b/src/VisualizationTests/packages.config @@ -4,6 +4,7 @@ + From 72573e0567d82842c92b2ce045da28fe0e58fd60 Mon Sep 17 00:00:00 2001 From: Martin Misol Monzo Date: Tue, 14 Apr 2020 13:07:08 -0400 Subject: [PATCH 2/2] Change visibility to internal --- src/DynamoCoreWpf/Properties/AssemblyInfo.cs | 1 + src/DynamoCoreWpf/ViewModels/Watch3D/HelixWatch3DViewModel.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/DynamoCoreWpf/Properties/AssemblyInfo.cs b/src/DynamoCoreWpf/Properties/AssemblyInfo.cs index f2fa7c99232..64da742f531 100644 --- a/src/DynamoCoreWpf/Properties/AssemblyInfo.cs +++ b/src/DynamoCoreWpf/Properties/AssemblyInfo.cs @@ -40,3 +40,4 @@ [assembly: InternalsVisibleTo("WorkspaceDependencyViewExtension")] [assembly: InternalsVisibleTo("DocumentationBrowserViewExtension")] [assembly: InternalsVisibleTo("SystemTestServices")] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] diff --git a/src/DynamoCoreWpf/ViewModels/Watch3D/HelixWatch3DViewModel.cs b/src/DynamoCoreWpf/ViewModels/Watch3D/HelixWatch3DViewModel.cs index fce44f2096d..7c0422d6fff 100644 --- a/src/DynamoCoreWpf/ViewModels/Watch3D/HelixWatch3DViewModel.cs +++ b/src/DynamoCoreWpf/ViewModels/Watch3D/HelixWatch3DViewModel.cs @@ -1647,7 +1647,7 @@ private void ToggleTreeViewItemHighlighting(string path, bool isSelected) /// attaches them to the visual scene. /// /// An of . - protected virtual void AggregateRenderPackages(IEnumerable packages) + internal virtual void AggregateRenderPackages(IEnumerable packages) { IEnumerable customNodeIdents = null; if (InCustomNode())