From c0caa152cc84c8d2cb5c45b6ad122b868e536447 Mon Sep 17 00:00:00 2001 From: Michael Kirschner Date: Mon, 8 Jan 2024 22:58:38 -0500 Subject: [PATCH] fix bug where loading package does not re-execute graph. (#4) * appdomain for deps * load engine ourselves * review comments * when extension is late loaded run the graph to resolve the new engine. * test issues --- DSIronPython.sln | 6 ++ DSIronPython/DSIronPython.csproj | 12 --- DSIronPython/pkg.json | 2 +- DSironPythonEmpty/Class1.cs | 11 +++ DSironPythonEmpty/DSIronPythonEmpty.csproj | 12 +++ IronPythonExtension/IronPythonExtension.cs | 92 ++++++++++++++++++---- 6 files changed, 107 insertions(+), 28 deletions(-) create mode 100644 DSironPythonEmpty/Class1.cs create mode 100644 DSironPythonEmpty/DSIronPythonEmpty.csproj diff --git a/DSIronPython.sln b/DSIronPython.sln index 787f0ef..678eb1f 100644 --- a/DSIronPython.sln +++ b/DSIronPython.sln @@ -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 @@ -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 diff --git a/DSIronPython/DSIronPython.csproj b/DSIronPython/DSIronPython.csproj index 05c7e7f..cf73c07 100644 --- a/DSIronPython/DSIronPython.csproj +++ b/DSIronPython/DSIronPython.csproj @@ -29,18 +29,6 @@ - - diff --git a/DSIronPython/pkg.json b/DSIronPython/pkg.json index 8ade97b..a63abe8 100644 --- a/DSIronPython/pkg.json +++ b/DSIronPython/pkg.json @@ -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" ] } \ No newline at end of file diff --git a/DSironPythonEmpty/Class1.cs b/DSironPythonEmpty/Class1.cs new file mode 100644 index 0000000..cb88ec0 --- /dev/null +++ b/DSironPythonEmpty/Class1.cs @@ -0,0 +1,11 @@ +namespace DSironPythonEmpty +{ + /// + /// This class/dll exists so that Dynamo knows this package is loaded, + /// even though it really only contains an extension. + /// + internal class Class1 + { + + } +} diff --git a/DSironPythonEmpty/DSIronPythonEmpty.csproj b/DSironPythonEmpty/DSIronPythonEmpty.csproj new file mode 100644 index 0000000..063c28e --- /dev/null +++ b/DSironPythonEmpty/DSIronPythonEmpty.csproj @@ -0,0 +1,12 @@ + + + + + + $(SolutionDir)\package_output\DSIronPython\bin\ + net6.0 + enable + enable + + + diff --git a/IronPythonExtension/IronPythonExtension.cs b/IronPythonExtension/IronPythonExtension.cs index 89be644..6c08bf4 100644 --- a/IronPythonExtension/IronPythonExtension.cs +++ b/IronPythonExtension/IronPythonExtension.cs @@ -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 { @@ -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. /// - /// - public void Ready(ReadyParams sp) + /// + 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)); - /// - /// Action to be invoked when shutdown has begun. - /// + //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); }