Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle memory outage on render (cherry pick) #10569

Merged
merged 2 commits into from
Apr 14, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions src/DynamoCore/Models/DynamoModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

/// <summary>
/// Remove a workspace from the dynamo model.
/// </summary>
Expand Down
2 changes: 1 addition & 1 deletion src/DynamoCore/Models/DynamoModelEvents.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
9 changes: 9 additions & 0 deletions src/DynamoCore/Properties/Resources.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions src/DynamoCore/Properties/Resources.en-US.resx
Original file line number Diff line number Diff line change
Expand Up @@ -709,4 +709,7 @@ Restart Dynamo to complete the uninstall.</value>
<data name="FunctionDefinitionOverwrittenMessage" xml:space="preserve">
<value>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.</value>
</data>
<data name="Preview3DOutageTitle" xml:space="preserve">
<value>3D preview has been deactivated</value>
</data>
</root>
3 changes: 3 additions & 0 deletions src/DynamoCore/Properties/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -709,4 +709,7 @@ Restart Dynamo to complete the uninstall.</value>
<data name="FunctionDefinitionOverwrittenMessage" xml:space="preserve">
<value>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.</value>
</data>
<data name="Preview3DOutageTitle" xml:space="preserve">
<value>3D preview has been deactivated</value>
</data>
</root>
18 changes: 18 additions & 0 deletions src/DynamoCoreWpf/Properties/Resources.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions src/DynamoCoreWpf/Properties/Resources.en-US.resx
Original file line number Diff line number Diff line change
Expand Up @@ -2242,4 +2242,10 @@ Uninstall the following packages: {0}?</value>
<data name="NodeTooltipOriginalName" xml:space="preserve">
<value>Original node name: </value>
</data>
<data name="RenderingMemoryOutageDescription" xml:space="preserve">
<value>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.</value>
</data>
<data name="RenderingMemoryOutageSummary" xml:space="preserve">
<value>Dynamo has run out of memory trying to render your geometry. The geometry preview has been disabled.</value>
</data>
</root>
6 changes: 6 additions & 0 deletions src/DynamoCoreWpf/Properties/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -2242,4 +2242,10 @@ Uninstall the following packages: {0}?</value>
<data name="NodeTooltipOriginalName" xml:space="preserve">
<value>Original node name: </value>
</data>
<data name="RenderingMemoryOutageDescription" xml:space="preserve">
<value>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.</value>
</data>
<data name="RenderingMemoryOutageSummary" xml:space="preserve">
<value>Dynamo has run out of memory trying to render your geometry. The geometry preview has been disabled.</value>
</data>
</root>
10 changes: 10 additions & 0 deletions src/DynamoCoreWpf/ViewModels/Core/DynamoViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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;
Expand Down
16 changes: 14 additions & 2 deletions src/DynamoCoreWpf/ViewModels/Watch3D/HelixWatch3DViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -859,7 +861,17 @@ public override void GenerateViewGeometryFromRenderPackagesAndRequestUpdate(Rend
var meshPackages = packages.Cast<HelixRenderPackage>().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));
Expand Down Expand Up @@ -1635,7 +1647,7 @@ private void ToggleTreeViewItemHighlighting(string path, bool isSelected)
/// attaches them to the visual scene.
/// </summary>
/// <param name="packages">An <see cref="IEnumerable"/> of <see cref="HelixRenderPackage"/>.</param>
private void AggregateRenderPackages(IEnumerable<HelixRenderPackage> packages)
protected virtual void AggregateRenderPackages(IEnumerable<HelixRenderPackage> packages)
Copy link
Member

@mjkkirschner mjkkirschner Apr 14, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has the effect of exposing this method to the API in any derived class, as this is currently a public class - (In the current API) Of course we'd like to do a refactor and change our API policies as we have been discussing but for now I'm a bit hesitant.

If this method was made internal could it be mocked?

If it has to become protected then we can use an interface as the argument type and check the type inside the method.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this method was made internal could it be mocked?

Apparently this could be done https://stackoverflow.com/a/2823554. Let me give that a try.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mjkkirschner That worked! Would you take one last look?

{
IEnumerable<string> customNodeIdents = null;
if (InCustomNode())
Expand Down
54 changes: 35 additions & 19 deletions src/VisualizationTests/HelixWatch3DViewModelTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -116,6 +109,29 @@ protected override void StartDynamo(TestSessionConfiguration testConfig)
SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());
}

/// <summary>
/// Derived test classes can override this method to provide a customized Dynamo model.
/// </summary>
protected virtual DynamoModel CreateModel(DynamoModel.IStartConfiguration configuration)
{
return DynamoModel.Start(configuration);
}

/// <summary>
/// Derived test classes can override this method to provide a customized view model configuration.
/// </summary>
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();
Expand Down
75 changes: 75 additions & 0 deletions src/VisualizationTests/Preview3DMemoryOutageTest.cs
Original file line number Diff line number Diff line change
@@ -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<DynamoModel> modelMock;

/// <summary>
/// Creates a mock DynamoModel that does not show a task dialog but instead sets its call
/// as a verifiable call.
/// </summary>
/// <param name="configuration">Default DynamoModel configuration</param>
/// <returns>Mock DynamoModel</returns>
protected override DynamoModel CreateModel(DynamoModel.IStartConfiguration configuration)
{
modelMock = new Mock<DynamoModel>(configuration)
{
CallBase = true
};
modelMock.Setup(m => m.OnRequestTaskDialog(It.IsAny<object>(), It.IsAny<TaskDialogEventArgs>()))
.Callback(() => { }) // Prevent dialog from blocking the test
.Verifiable();

return modelMock.Object;
}

/// <summary>
/// Sets up a mock HelixWatch3DViewModel which will throw OutOfMemoryException when rendering.
/// </summary>
/// <returns>DynamoViewModel configuration referencing the mock 3D preview</returns>
protected override DynamoViewModel.StartConfiguration CreateViewModelStartConfiguration()
{
var watch3DMock = new Mock<HelixWatch3DViewModel>(null, new Watch3DViewModelStartupParams(Model))
{
CallBase = true
};
watch3DMock.Protected().Setup("AggregateRenderPackages", ItExpr.IsAny<IEnumerable<HelixRenderPackage>>()).Throws<OutOfMemoryException>();
return new DynamoViewModel.StartConfiguration()
{
Watch3DViewModel = watch3DMock.Object
};
}

/// <summary>
/// Opens any file that produces geometry and checks that the 3D preview is disabled after the error
/// and also that a dialog is shown.
/// </summary>
[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();
}
}
}
4 changes: 4 additions & 0 deletions src/VisualizationTests/WpfVisualizationTests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\extern\prism\Microsoft.Practices.Prism.dll</HintPath>
</Reference>
<Reference Include="Moq, Version=4.2.1507.118, Culture=neutral, PublicKeyToken=69f491c39445e920, processorArchitecture=MSIL">
<HintPath>..\packages\Moq.4.2.1507.0118\lib\net40\Moq.dll</HintPath>
</Reference>
<Reference Include="nunit.framework, Version=2.6.3.13283, Culture=neutral, PublicKeyToken=96d09a1eb7f44a77, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\extern\NUnit\nunit.framework.dll</HintPath>
Expand Down Expand Up @@ -105,6 +108,7 @@
<Compile Include="HelixWatch3dViewModelPrimitiveTests.cs" />
<Compile Include="HelixWatch3DViewModelTests.cs" />
<Compile Include="HelixWatch3DViewModelUnitTests.cs" />
<Compile Include="Preview3DMemoryOutageTest.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Watch3DViewModelTests.cs" />
</ItemGroup>
Expand Down
1 change: 1 addition & 0 deletions src/VisualizationTests/packages.config
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<package id="HelixToolkit" version="2.9.0" targetFramework="net47" />
<package id="HelixToolkit.Wpf" version="2.9.0" targetFramework="net47" />
<package id="HelixToolkit.Wpf.SharpDX" version="2.9.0" targetFramework="net47" />
<package id="Moq" version="4.2.1507.0118" targetFramework="net47" />
<package id="SharpDX" version="4.2.0" targetFramework="net47" />
<package id="SharpDX.D3DCompiler" version="4.2.0" targetFramework="net47" />
<package id="SharpDX.Direct2D1" version="4.2.0" targetFramework="net47" />
Expand Down