Skip to content

Commit

Permalink
fix bug where loading package does not re-execute graph. (#4)
Browse files Browse the repository at this point in the history
* appdomain for deps

* load engine ourselves

* review comments

* when extension is late loaded run the graph to resolve the new engine.

* test issues
  • Loading branch information
mjkkirschner authored Jan 9, 2024
1 parent d8dc59b commit c0caa15
Show file tree
Hide file tree
Showing 6 changed files with 107 additions and 28 deletions.
6 changes: 6 additions & 0 deletions DSIronPython.sln
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IronPythonExtension", "Iron
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IronPythonTests", "IronPythonTests\IronPythonTests.csproj", "{E6DF2FBD-7D4D-4465-94DC-D576D737E985}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DSIronPythonEmpty", "DSironPythonEmpty\DSIronPythonEmpty.csproj", "{4B853519-34A0-494D-8A16-3E79D1C5829B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -27,6 +29,10 @@ Global
{E6DF2FBD-7D4D-4465-94DC-D576D737E985}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E6DF2FBD-7D4D-4465-94DC-D576D737E985}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E6DF2FBD-7D4D-4465-94DC-D576D737E985}.Release|Any CPU.Build.0 = Release|Any CPU
{4B853519-34A0-494D-8A16-3E79D1C5829B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4B853519-34A0-494D-8A16-3E79D1C5829B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4B853519-34A0-494D-8A16-3E79D1C5829B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4B853519-34A0-494D-8A16-3E79D1C5829B}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
12 changes: 0 additions & 12 deletions DSIronPython/DSIronPython.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,6 @@
</AssemblyAttribute>
</ItemGroup>

<!--<Target Name="Move python libs to extra" AfterTargets="Build">
<ItemGroup>
<MySourceFiles Include="$(OutputPath)\*lib\**;" />
</ItemGroup>
<Move SourceFiles="@(MySourceFiles)" DestinationFiles="$(OutputPath)..\extra\%(RecursiveDir)%(Filename)%(Extension)" />
</Target>
<Target Name="Remove Lib" AfterTargets="Move python libs to extra">
<RemoveDir Directories="$(OutputPath)\lib" />
</Target>-->

<Target Name="copypkgjson" AfterTargets="Build">
<Copy SourceFiles="pkg.json" DestinationFolder="$(OutputPath)..\"/>
</Target>
Expand Down
2 changes: 1 addition & 1 deletion DSIronPython/pkg.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@
"site_url": "https://dynamobim.org/",
"repository_url": "https://github.com/DynamoDS/Dynamo",
"contains_binaries": true,
"node_libraries": []
"node_libraries": [ "DSIronPythonEmpty, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" ]
}
11 changes: 11 additions & 0 deletions DSironPythonEmpty/Class1.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace DSironPythonEmpty
{
/// <summary>
/// This class/dll exists so that Dynamo knows this package is loaded,
/// even though it really only contains an extension.
/// </summary>
internal class Class1
{

}
}
12 changes: 12 additions & 0 deletions DSironPythonEmpty/DSIronPythonEmpty.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<ImportGroup Label="PropertySheets">
<Import Project="$(SolutionDir)Config\shared.props" />
</ImportGroup>
<PropertyGroup>
<OutputPath>$(SolutionDir)\package_output\DSIronPython\bin\</OutputPath>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

</Project>
92 changes: 77 additions & 15 deletions IronPythonExtension/IronPythonExtension.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
using System;
using Dynamo.Extensions;
using Dynamo.Graph.Workspaces;
using Dynamo.Logging;
using Dynamo.PythonServices;
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
using Dynamo.Extensions;
using Dynamo.Logging;
using Dynamo.PythonServices;

namespace IronPythonExtension
{
Expand Down Expand Up @@ -59,27 +61,87 @@ public void Startup(StartupParams sp)
/// Action to be invoked when the Dynamo has started up and is ready
/// for user interaction.
/// </summary>
/// <param name="sp"></param>
public void Ready(ReadyParams sp)
/// <param name="rp"></param>
public void Ready(ReadyParams rp)
{
var extraPath = Path.Combine(new FileInfo(Assembly.GetAssembly(typeof(IronPythonExtension)).Location).Directory.Parent.FullName, "extra");
var alc = new IsolatedPythoContext(Path.Combine(extraPath,"DSIronPython.dll"));
alc.LoadFromAssemblyName(new AssemblyName("DSIronPython"));
}
var extraPath = Path.Combine(new FileInfo(Assembly.GetExecutingAssembly().Location).Directory.Parent.FullName, "extra");
var alc = new IsolatedPythonContext(Path.Combine(extraPath,$"{PythonEvaluatorAssembly}.dll"));
var dsIronAssem = alc.LoadFromAssemblyName(new AssemblyName(PythonEvaluatorAssembly));

/// <summary>
/// Action to be invoked when shutdown has begun.
/// </summary>
//load the engine into Dynamo ourselves.
LoadPythonEngine(dsIronAssem);

//we used to do this:
//but it's not neccesary to load anything into the VM.
//instead we skip all the extra work and trigger the side effect we want
//which is re executing the graph after the dsIronPython evaluator is loaded into the PythonEngineManager.
//rp.StartupParams.LibraryLoader.LoadNodeLibrary(dsIronAssem);

if(rp.CurrentWorkspaceModel is HomeWorkspaceModel hwm)
{
foreach (var n in hwm.Nodes)
{
n.MarkNodeAsModified(true);
}
hwm.Run();
}
}
public void Shutdown()
{
// Do nothing for now
}

private static void LoadPythonEngine(Assembly assembly)
{
if (assembly == null)
{
return;
}

// Currently we are using try-catch to validate loaded assembly and Singleton Instance method exist
// but we can optimize by checking all loaded types against evaluators interface later
try
{
Type eType = null;
PropertyInfo instanceProp = null;
try
{
eType = assembly.GetTypes().FirstOrDefault(x => typeof(PythonEngine).IsAssignableFrom(x) && !x.IsInterface && !x.IsAbstract);
if (eType == null) return;

instanceProp = eType?.GetProperty("Instance", BindingFlags.NonPublic | BindingFlags.Static);
if (instanceProp == null) return;
}
catch
{
// Ignore exceptions from iterating assembly types.
return;
}

PythonEngine engine = (PythonEngine)instanceProp.GetValue(null);
if (engine == null)
{
throw new Exception($"Could not get a valid PythonEngine instance by calling the {eType.Name}.Instance method");
}

if (PythonEngineManager.Instance.AvailableEngines.All(x => x.Name != engine.Name))
{
PythonEngineManager.Instance.AvailableEngines.Add(engine);
}
}
catch (Exception ex)
{
throw new Exception($"Failed to add a Python engine from assembly {assembly.GetName().Name}.dll with error: {ex.Message}");
}
}


}
internal class IsolatedPythoContext : AssemblyLoadContext
internal class IsolatedPythonContext : AssemblyLoadContext
{
private AssemblyDependencyResolver resolver;

public IsolatedPythoContext(string libPath)
public IsolatedPythonContext(string libPath)
{
resolver = new AssemblyDependencyResolver(libPath);
}
Expand Down

0 comments on commit c0caa15

Please sign in to comment.