diff --git a/src/DynamoApplications/StartupUtils.cs b/src/DynamoApplications/StartupUtils.cs index 103b83d14df..66a0cf2562b 100644 --- a/src/DynamoApplications/StartupUtils.cs +++ b/src/DynamoApplications/StartupUtils.cs @@ -194,7 +194,8 @@ private static DynamoModel PrepareModel( bool cliMode = true, string userDataFolder = "", string commonDataFolder = "", - bool serviceMode = false) + bool serviceMode = false, + bool useCustomNodeCache = false) { var normalizedCLILocale = string.IsNullOrEmpty(cliLocale) ? null : cliLocale; IPathResolver pathResolver = CreatePathResolver(false, string.Empty, string.Empty, string.Empty); @@ -214,7 +215,8 @@ private static DynamoModel PrepareModel( noNetworkMode: noNetworkMode, info: analyticsInfo, isServiceMode: serviceMode, - cliLocale: normalizedCLILocale + cliLocale: normalizedCLILocale, + useCustomNodeCache ); model.IsASMLoaded = isASMloaded; return model; @@ -285,7 +287,8 @@ public static DynamoModel MakeModel(bool CLImode, string asmPath = "", string ho /// Path to directory containing geometry library binaries /// Host analytics info specifying Dynamo launching host related information. /// - public static DynamoModel MakeModel(bool CLImode, string CLIlocale, bool noNetworkMode, string asmPath = "", HostAnalyticsInfo info = new HostAnalyticsInfo()) + public static DynamoModel MakeModel(bool CLImode, string CLIlocale, bool noNetworkMode, string asmPath = "", + HostAnalyticsInfo info = new HostAnalyticsInfo()) { var model = PrepareModel( cliLocale: CLIlocale, @@ -296,6 +299,22 @@ public static DynamoModel MakeModel(bool CLImode, string asmPath = "", string ho return model; } + internal static DynamoModel MakeModel(bool CLImode, string CLIlocale, bool noNetworkMode, bool useCustomNodeCache, + string asmPath = "", HostAnalyticsInfo info = new HostAnalyticsInfo()) + { + var model = PrepareModel( + cliLocale: CLIlocale, + asmPath: asmPath, + noNetworkMode: noNetworkMode, + analyticsInfo: info, + cliMode: CLImode, + userDataFolder: "", + commonDataFolder: "", + serviceMode: false, + useCustomNodeCache); + return model; + } + /// /// It returns an IPathResolver based on the mode and some locations /// @@ -371,7 +390,8 @@ private static DynamoModel StartDynamoWithDefaultConfig(bool CLImode, bool noNetworkMode, HostAnalyticsInfo info = new HostAnalyticsInfo(), bool isServiceMode = false, - string cliLocale = null) + string cliLocale = null, + bool useCustomNodeCache = false) { var config = new DynamoModel.DefaultStartConfiguration @@ -387,6 +407,7 @@ private static DynamoModel StartDynamoWithDefaultConfig(bool CLImode, Preferences = PreferenceSettings.Instance, NoNetworkMode = noNetworkMode, CLILocale = cliLocale, + UseCustomNodeCache = useCustomNodeCache, //Breaks all Lucene calls. TI enable this would require a lot of refactoring around Lucene usage in Dynamo. //IsHeadless = CLImode }; diff --git a/src/DynamoCore/Core/CustomNodeDefinition.cs b/src/DynamoCore/Core/CustomNodeDefinition.cs index b9a574d15c1..b2ceecd0275 100644 --- a/src/DynamoCore/Core/CustomNodeDefinition.cs +++ b/src/DynamoCore/Core/CustomNodeDefinition.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using Dynamo.Engine; using Dynamo.Engine.CodeGeneration; @@ -7,6 +8,8 @@ using Dynamo.Graph.Nodes.CustomNodes; using Dynamo.Graph.Workspaces; using Dynamo.Library; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using ProtoCore; using ProtoCore.AST.AssociativeAST; @@ -302,6 +305,9 @@ public CustomNodeInfo(Guid functionId, string name, string category, string desc if (String.IsNullOrWhiteSpace(Category)) Category = Dynamo.Properties.Resources.DefaultCustomNodeCategory; } + + [JsonConstructor] public CustomNodeInfo() { } + /// /// Returns custom node unique ID /// @@ -315,7 +321,7 @@ public CustomNodeInfo(Guid functionId, string name, string category, string desc /// /// Returns custom node category /// - public string Category { get; set; } + public string Category { get; set; } = string.Empty; /// /// Returns custom node description @@ -337,7 +343,7 @@ public CustomNodeInfo(Guid functionId, string name, string category, string desc /// Indicates if custom node is part of the library search. /// If true, then custom node is part of library search. /// - public bool IsVisibleInDynamoLibrary { get; private set; } + public bool IsVisibleInDynamoLibrary { get; set; } /// /// Only valid if IsPackageMember is true. @@ -345,5 +351,73 @@ public CustomNodeInfo(Guid functionId, string name, string category, string desc /// requested this CustomNode to load. /// public PackageInfo PackageInfo { get; internal set; } + + + private static readonly string[] topLevelJsonKeys = { "Uuid", "Category", "Description", "Name" }; + private static readonly Dictionary propertyLookup = new Dictionary(); + private static object isVisibleInDynamoLibraryProp = null; + private static DefaultJsonNameTable propertyTable = null; + + internal static bool GetFromJsonDocument(string path, out CustomNodeInfo info, out Exception ex) + { + if (propertyTable == null) + { + propertyTable = new DefaultJsonNameTable(); + foreach (var pn in topLevelJsonKeys) + { + propertyLookup[pn] = propertyTable.Add(pn); + } + isVisibleInDynamoLibraryProp = propertyTable.Add(nameof(IsVisibleInDynamoLibrary)); + } + + try + { + var data = new JObject(); + // JsonTextRead will automatically dispose of the stream reader + using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read)) + using (var jr = new JsonTextReader(new StreamReader(fs)) { PropertyNameTable = propertyTable }) + { + while (jr.Read()) + { + if (data.Count == topLevelJsonKeys.Length + 1) + { + break; // we have all of the + } + + if (jr.TokenType == JsonToken.PropertyName) + { + if (jr.Depth == 1) + { + foreach (var prop in propertyLookup) + { + if (jr.Value == prop.Value) + { + data[prop.Key] = jr.ReadAsString() ?? ""; + break; + } + } + } + else if (jr.Value == isVisibleInDynamoLibraryProp) + { + data[nameof(IsVisibleInDynamoLibrary)] = jr.ReadAsBoolean(); + } + } + } + } + + ex = null; + data["FunctionId"] = data.GetValue("Uuid"); + data["Path"] = path; + + info = data.ToObject(); + return true; + } + catch (Exception e) + { + ex = e; + info = null; + return false; + } + } } } diff --git a/src/DynamoCore/Core/CustomNodeManager.cs b/src/DynamoCore/Core/CustomNodeManager.cs index 8dabe1ec45b..a6c3a38dd8f 100644 --- a/src/DynamoCore/Core/CustomNodeManager.cs +++ b/src/DynamoCore/Core/CustomNodeManager.cs @@ -1,8 +1,11 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; using System.Xml; using Dynamo.Engine; using Dynamo.Engine.NodeToCode; @@ -26,6 +29,8 @@ using Dynamo.Utilities; using ProtoCore.AST.AssociativeAST; using Symbol = Dynamo.Graph.Nodes.CustomNodes.Symbol; +using System.Diagnostics; + namespace Dynamo.Core { @@ -36,6 +41,138 @@ namespace Dynamo.Core /// public class CustomNodeManager : LogSourceBase, ICustomNodeSource, ICustomNodeManager, IDisposable { + private class JsonCache + { + private readonly string cacheFilePath; + private readonly ConcurrentDictionary cache = new(); + private volatile bool dirty; + + public JsonCache(string filePath) + { + cacheFilePath = filePath; + Deserialize(); + } + + public void Set(string key, T value) + { + cache.AddOrUpdate(key, value, (k, v) => value); + dirty = true; + } + + public bool TryGet(string key, out T value) => cache.TryGetValue(key, out value); + + private void Remove(string key) + { + if (cache.TryRemove(key, out _)) + dirty = true; + } + + /// + /// Removes stale entries that don't correspond to existing files + /// + public void RemoveStaleEntries() + { + // Create a snapshot of keys to avoid modification during enumeration + var keysSnapshot = cache.Keys.ToList(); + + foreach (var key in keysSnapshot) + { + try + { + // Extract file path from cache key (path + lastWriteTime + length) + var filePath = ExtractFilePathFromCacheKey(key); + if (string.IsNullOrEmpty(filePath) || !File.Exists(filePath)) + { + Remove(key); + } + else + { + // Check if the file's write time and length match the cached key + var fileInfo = new FileInfo(filePath); + var lastWriteTime = fileInfo.LastWriteTimeUtc; + var length = fileInfo.Length; + var newKey = filePath + lastWriteTime + length; + if (newKey != key) + { + // File has been updated, remove from cache + Remove(key); + } + } + } + catch (Exception ex) + { + // If we can't check the file (permissions, IO error, etc.), remove the entry + // This prevents keeping potentially stale entries due to access issues + Remove(key); + Debug.WriteLine($"Error checking cache entry {key}: {ex.Message}"); + } + } + } + + /// + /// Extracts file path from cache key + /// + private string ExtractFilePathFromCacheKey(string key) + { + try + { + // Cache key format: path + lastWriteTime + length + int idx = key.IndexOf(".dyf", StringComparison.OrdinalIgnoreCase); + return idx >= 0 ? key.Substring(0, idx + 4) : string.Empty; + } + catch + { + // If extraction fails, return empty string + } + return string.Empty; + } + + private void Deserialize() + { + try + { + if (File.Exists(cacheFilePath)) + { + var json = File.ReadAllText(cacheFilePath); + var loaded = JsonSerializer.Deserialize>(json); + if (loaded != null) + { + foreach (var kv in loaded) + { + cache.TryAdd(kv.Key, kv.Value); + } + } + } + } + catch (Exception ex) + { + // If deserialization fails, continue with empty cache + // This prevents crashes due to corrupted cache files + Debug.WriteLine($"Failed to deserialize cache: {ex.Message}"); + cache.Clear(); + } + } + + public void Serialize() + { + if (!dirty) return; + + try + { + // Create a snapshot to avoid serialization issues if cache is modified during serialization + var snapshot = new Dictionary(cache); + var json = JsonSerializer.Serialize(snapshot, new JsonSerializerOptions { WriteIndented = true }); + File.WriteAllText(cacheFilePath, json); + dirty = false; + } + catch (Exception ex) + { + // Log error but don't throw - serialization failure shouldn't crash the app + Debug.WriteLine($"Failed to serialize cache: {ex.Message}"); + } + } + } + /// /// This function creates CustomNodeManager /// @@ -49,17 +186,37 @@ public CustomNodeManager(NodeFactory nodeFactory, MigrationManager migrationMana this.libraryServices = libraryServices; } + internal CustomNodeManager(NodeFactory nodeFactory, MigrationManager migrationManager, LibraryServices libraryServices, + bool useCustomNodeCache) : this(nodeFactory, migrationManager, libraryServices) + { + this.useCustomNodeCache = useCustomNodeCache; + + if (!useCustomNodeCache) return; + + + // Use a user-specific cache path + string cacheDir = Path.Combine(libraryServices.PathManager.GetUserDataFolder(), "Cache"); + Directory.CreateDirectory(cacheDir); + string cacheFilePath = Path.Combine(cacheDir, "CustomNodeInfoCache.temp"); + + customNodeInfoCache = new JsonCache(cacheFilePath); + + // Cleanup stale entries on startup in background + _ = Task.Run(() => customNodeInfoCache.RemoveStaleEntries()); + } + #region Fields and properties - private LibraryServices libraryServices; + private readonly LibraryServices libraryServices; + private readonly JsonCache customNodeInfoCache; + + private readonly bool useCustomNodeCache; - private readonly OrderedSet loadOrder = new OrderedSet(); + private readonly OrderedSet loadOrder = new(); - private readonly Dictionary loadedCustomNodes = - new Dictionary(); + private readonly Dictionary loadedCustomNodes = new(); - private readonly Dictionary loadedWorkspaceModels = - new Dictionary(); + private readonly Dictionary loadedWorkspaceModels = new(); private readonly NodeFactory nodeFactory; private readonly MigrationManager migrationManager; @@ -174,7 +331,7 @@ public Function CreateCustomNodeInstance( /// the given id could not be found. /// /// - /// Flag specifying whether or not this should operate in "test mode". + /// Flag specifying whether this should operate in "test mode". /// /// /// Custom node definition data @@ -394,7 +551,7 @@ internal bool Uninitialize(Guid guid) /// /// Path on disk to scan for custom nodes. /// - /// Flag specifying whether or not this should operate in "test mode". + /// Flag specifying whether this should operate in "test mode". /// /// /// Indicates whether custom node comes from package or not. @@ -464,7 +621,6 @@ private IEnumerable ScanNodeHeadersInDirectory(string dir, bool Log(e); yield break; } - foreach (var file in dyfs) { CustomNodeInfo info; @@ -480,23 +636,11 @@ private IEnumerable ScanNodeHeadersInDirectory(string dir, bool /// private void SetNodeInfo(CustomNodeInfo newInfo) { - var guids = NodeInfos.Where(x => - { - return !string.IsNullOrEmpty(x.Value.Path) && - string.Compare(x.Value.Path, newInfo.Path, StringComparison.OrdinalIgnoreCase) == 0; - }).Select(x => x.Key).ToList(); - - - foreach (var guid in guids) - { - NodeInfos.Remove(guid); - } - - // we need to check with the packageManager that this node if this node is in a package or not - + // we need to check with the packageManager if this node is in a package or not - // currently the package data is lost when the customNode workspace is loaded. // we'll only do this check for customNode infos which don't have a package currently to verify if this // is correct. - if(newInfo.IsPackageMember == false) + if (newInfo.IsPackageMember == false) { var owningPackage = this.OnRequestCustomNodeOwner(newInfo.FunctionId); @@ -509,18 +653,17 @@ private void SetNodeInfo(CustomNodeInfo newInfo) } - CustomNodeInfo info; // if the custom node is part of a package make sure it does not overwrite another node - if (newInfo.IsPackageMember && NodeInfos.TryGetValue(newInfo.FunctionId, out info)) + if (newInfo.IsPackageMember && NodeInfos.TryGetValue(newInfo.FunctionId, out CustomNodeInfo info)) { var newInfoPath = String.IsNullOrEmpty(newInfo.Path) ? string.Empty : Path.GetDirectoryName(newInfo.Path); var infoPath = String.IsNullOrEmpty(info.Path) ? string.Empty : Path.GetDirectoryName(info.Path); var message = string.Format(Resources.MessageCustomNodePackageFailedToLoad, infoPath, newInfoPath); - + //only try to compare package info if both customNodeInfos have package info. - if(info.IsPackageMember && info.PackageInfo != null) + if (info.IsPackageMember && info.PackageInfo != null) { // if these are different packages raise an error. // TODO (for now we don't raise an error for different @@ -538,7 +681,7 @@ private void SetNodeInfo(CustomNodeInfo newInfo) } else //(newInfo has owning Package, oldInfo does not) { - + // This represents the case where a previous info was not from a package, but the current info // has an owning package. var looseCustomNodeToPackageMessage = String.Format(Properties.Resources.FunctionDefinitionOverwrittenMessage, newInfo.Name, newInfo.PackageInfo, info.Name); @@ -668,49 +811,69 @@ internal bool IsInitialized(Guid guid) } /// - /// Returns a boolean indicating if successfully get a CustomNodeInfo object from a workspace path + /// Returns a boolean indicating if a CustomNodeInfo object from a workspace path is successfully obtained /// /// The path from which to get the guid /// - /// Flag specifying whether or not this should operate in "test mode". + /// Flag specifying whether this should operate in "test mode" or not. /// /// /// The custom node info object - null if we failed internal bool TryGetInfoFromPath(string path, bool isTestMode, out CustomNodeInfo info) { - WorkspaceInfo header = null; - XmlDocument xmlDoc; - string jsonDoc; - Exception ex; try { - if (DynamoUtilities.PathHelper.isValidJson(path, out jsonDoc, out ex)) + var fileInfo = new FileInfo(path); + var lastWriteTime = fileInfo.LastWriteTimeUtc; + var length = fileInfo.Length; + + //create a fast check for checking if the file has been loaded before. Could also do a file hash but we don't care too much about false negatives + var key = path + lastWriteTime + length; + + if (useCustomNodeCache && customNodeInfoCache.TryGet(key, out info)) { - if (!WorkspaceInfo.FromJsonDocument(jsonDoc, path, isTestMode, false, AsLogger(), out header)) + return true; + } + + if (CustomNodeInfo.GetFromJsonDocument(path, out info, out var ex)) + { + if (useCustomNodeCache) { - Log(String.Format(Properties.Resources.FailedToLoadHeader, path)); - info = null; - return false; + customNodeInfoCache.Set(key, info); } + + return true; } - else if (DynamoUtilities.PathHelper.isValidXML(path, out xmlDoc, out ex)) + + if (DynamoUtilities.PathHelper.isValidXML(path, out var xmlDoc, out ex)) { - if (!WorkspaceInfo.FromXmlDocument(xmlDoc, path, isTestMode, false, AsLogger(), out header)) + if (!WorkspaceInfo.FromXmlDocument(xmlDoc, path, isTestMode, false, AsLogger(), out var header)) { Log(String.Format(Properties.Resources.FailedToLoadHeader, path)); info = null; return false; } + + info = new CustomNodeInfo( + Guid.Parse(header.ID), + header.Name, + header.Category, + header.Description, + path, + header.IsVisibleInDynamoLibrary); + + if (useCustomNodeCache) + { + customNodeInfoCache.Set(key, info); + } + + return true; } - else throw ex; - info = new CustomNodeInfo( - Guid.Parse(header.ID), - header.Name, - header.Category, - header.Description, - path, - header.IsVisibleInDynamoLibrary); - return true; + if (ex == null) + { + throw new InvalidOperationException(Resources.UnknownErrorProcessingFile); + } + throw ex; } catch (Exception e) { @@ -1416,7 +1579,17 @@ internal IEnumerable GetAllDependenciesGuids(CustomNodeDefinition def) /// public void Dispose() { - this.loadedCustomNodes.ToList().ForEach(x => Uninitialize(x.Value.FunctionId)); + // Unsubscribe event handlers + InfoUpdated = null; + CustomNodeRemoved = null; + DefinitionUpdated = null; + + loadedCustomNodes.ToList().ForEach(x => Uninitialize(x.Value.FunctionId)); + + if (useCustomNodeCache) + { + customNodeInfoCache?.Serialize(); + } } } } diff --git a/src/DynamoCore/Library/LibraryServices.cs b/src/DynamoCore/Library/LibraryServices.cs index a19de90d230..788cf956fd6 100644 --- a/src/DynamoCore/Library/LibraryServices.cs +++ b/src/DynamoCore/Library/LibraryServices.cs @@ -3,6 +3,7 @@ using System.IO; using System.Linq; using System.Reflection; +using System.Reflection.Metadata.Ecma335; using System.Xml; using Dynamo.Configuration; using Dynamo.Core; @@ -134,7 +135,9 @@ public void Dispose() AppDomain.CurrentDomain.AssemblyResolve -= ResolveAssembly; } - + + internal PathManager PathManager => pathManager as PathManager; + /// /// Returns a list of imported libraries. /// @@ -1080,7 +1083,7 @@ private void ImportProcedure(string library, ProcedureNode proc) ObsoleteMsg = obsoleteMessage, CanUpdatePeriodically = canUpdatePeriodically, IsBuiltIn = pathManager.PreloadedLibraries.Contains(library) - || library.StartsWith(PathManager.BuiltinPackagesDirectory), + || library.StartsWith(Dynamo.Core.PathManager.BuiltinPackagesDirectory), IsPackageMember = packagedLibraries.Contains(library), IsLacingDisabled = isLacingDisabled, IsExperimental = (methodAttribute?.IsExperimental).GetValueOrDefault()|| (classAttribute?.IsExperimental).GetValueOrDefault() diff --git a/src/DynamoCore/Models/DynamoModel.cs b/src/DynamoCore/Models/DynamoModel.cs index 2ff460b5cee..8db82f671df 100644 --- a/src/DynamoCore/Models/DynamoModel.cs +++ b/src/DynamoCore/Models/DynamoModel.cs @@ -599,6 +599,12 @@ public struct DefaultStartConfiguration : IStartConfiguration /// public bool CLIMode { get; set; } public string CLILocale { get; set; } + + /// + /// If true, a custom node cache stores custom node info objects + /// in a dictionary, so they don't have to be reloaded from the DYF. + /// + internal bool UseCustomNodeCache { get; set; } } /// @@ -638,6 +644,7 @@ protected DynamoModel(IStartConfiguration config) { DynamoModel.IsCrashing = false; + bool useCustomNodeCache = false; if (config is DefaultStartConfiguration defaultStartConfig) { // This is not exposed in IStartConfiguration to avoid a breaking change. @@ -646,6 +653,7 @@ protected DynamoModel(IStartConfiguration config) CLIMode = defaultStartConfig.CLIMode; IsServiceMode = defaultStartConfig.IsServiceMode; CLILocale = defaultStartConfig.CLILocale; + useCustomNodeCache = defaultStartConfig.UseCustomNodeCache; } if (config is IStartConfigCrashReporter cerConfig) @@ -916,7 +924,7 @@ protected DynamoModel(IStartConfiguration config) LibraryServices.MessageLogged += LogMessage; LibraryServices.LibraryLoaded += LibraryLoaded; - CustomNodeManager = new CustomNodeManager(NodeFactory, MigrationManager, LibraryServices); + CustomNodeManager = new CustomNodeManager(NodeFactory, MigrationManager, LibraryServices, useCustomNodeCache); LuceneSearch.LuceneUtilityNodeSearch = new LuceneSearchUtility(this, LuceneSearchUtility.DefaultNodeIndexStartConfig); @@ -1513,28 +1521,9 @@ private void InitializeCustomNodeManager() if (customNodeSearchRegistry.Contains(info.FunctionId) || !info.IsVisibleInDynamoLibrary) - return; - - - var elements = SearchModel?.Entries.OfType(). - Where(x => - { - // Search for common paths and get rid of empty paths. - // It can be empty just in case it's just created node. - return String.Compare(x.Path, info.Path, StringComparison.OrdinalIgnoreCase) == 0 && - !String.IsNullOrEmpty(x.Path); - }).ToList(); - - if (elements.Any()) { - foreach (var element in elements) - { - element.SyncWithCustomNodeInfo(info); - SearchModel.Update(element); - } return; } - customNodeSearchRegistry.Add(info.FunctionId); var searchElement = new CustomNodeSearchElement(CustomNodeManager, info); @@ -1542,13 +1531,9 @@ private void InitializeCustomNodeManager() //Indexing node packages installed using PackageManagerSearch var iDoc = LuceneUtility.InitializeIndexDocumentForNodes(); - if (searchElement != null) - { - LuceneUtility.AddNodeTypeToSearchIndex(searchElement, iDoc); - } + LuceneUtility.AddNodeTypeToSearchIndex(searchElement, iDoc); - Action infoUpdatedHandler = null; - infoUpdatedHandler = newInfo => + Action infoUpdatedHandler = newInfo => { if (info.FunctionId == newInfo.FunctionId) { diff --git a/src/DynamoCore/Properties/Resources.Designer.cs b/src/DynamoCore/Properties/Resources.Designer.cs index 3dda5d9db63..78c8e49ea00 100644 --- a/src/DynamoCore/Properties/Resources.Designer.cs +++ b/src/DynamoCore/Properties/Resources.Designer.cs @@ -89,7 +89,7 @@ public static string Autocomplete { } /// - /// Looks up a localized string similar to No confident suggestions available. The model is continuously improving - check back soon. + /// Looks up a localized string similar to No confident suggestions available. The model is continuously improving - check back soon.. /// public static string AutocompleteLowConfidenceMessage { get { @@ -2181,6 +2181,15 @@ public static string UnhandledExceptionTitle { } } + /// + /// Looks up a localized string similar to An unknown error occurred while processing the file.. + /// + public static string UnknownErrorProcessingFile { + get { + return ResourceManager.GetString("UnknownErrorProcessingFile", resourceCulture); + } + } + /// /// Looks up a localized string similar to Unknown. /// diff --git a/src/DynamoCore/Properties/Resources.en-US.resx b/src/DynamoCore/Properties/Resources.en-US.resx index d68870e6ce2..ecd5e8e6964 100644 --- a/src/DynamoCore/Properties/Resources.en-US.resx +++ b/src/DynamoCore/Properties/Resources.en-US.resx @@ -926,4 +926,7 @@ This package likely contains an assembly that is blocked. You will need to load Click on the sparkle icon next to the port to activate Node Autocomplete. + + An unknown error occurred while processing the file. + diff --git a/src/DynamoCore/Properties/Resources.resx b/src/DynamoCore/Properties/Resources.resx index 36f1edefe5b..ae8027d9486 100644 --- a/src/DynamoCore/Properties/Resources.resx +++ b/src/DynamoCore/Properties/Resources.resx @@ -929,4 +929,7 @@ This package likely contains an assembly that is blocked. You will need to load Click on the sparkle icon next to the port to activate Node Autocomplete. + + An unknown error occurred while processing the file. + diff --git a/src/DynamoCore/PublicAPI.Unshipped.txt b/src/DynamoCore/PublicAPI.Unshipped.txt index 09837e29546..9d2357a29c5 100644 --- a/src/DynamoCore/PublicAPI.Unshipped.txt +++ b/src/DynamoCore/PublicAPI.Unshipped.txt @@ -400,6 +400,7 @@ Dynamo.CustomNodeDefinition.ReturnKeys.get -> System.Collections.Generic.IEnumer Dynamo.CustomNodeDefinition.Returns.get -> System.Collections.Generic.IEnumerable> Dynamo.CustomNodeDefinition.ReturnType.get -> ProtoCore.Type Dynamo.CustomNodeInfo +Dynamo.CustomNodeInfo.CustomNodeInfo() -> void Dynamo.CustomNodeInfo.Category.get -> string Dynamo.CustomNodeInfo.Category.set -> void Dynamo.CustomNodeInfo.CustomNodeInfo(System.Guid functionId, string name, string category, string description, string path, bool isVisibleInDynamoLibrary = true) -> void @@ -410,6 +411,7 @@ Dynamo.CustomNodeInfo.FunctionId.set -> void Dynamo.CustomNodeInfo.IsPackageMember.get -> bool Dynamo.CustomNodeInfo.IsPackageMember.set -> void Dynamo.CustomNodeInfo.IsVisibleInDynamoLibrary.get -> bool +Dynamo.CustomNodeInfo.IsVisibleInDynamoLibrary.set -> void Dynamo.CustomNodeInfo.Name.get -> string Dynamo.CustomNodeInfo.Name.set -> void Dynamo.CustomNodeInfo.PackageInfo.get -> Dynamo.Graph.Workspaces.PackageInfo @@ -2998,6 +3000,7 @@ static Dynamo.Properties.Resources.WarningInvalidOutput.get -> string static Dynamo.Properties.Resources.WatermarkLabelText.get -> string static Dynamo.Properties.Resources.WelcomeMessage.get -> string static Dynamo.Properties.Resources.WorkbenchNotOpen.get -> string +static Dynamo.Properties.Resources.UnknownErrorProcessingFile.get -> string static Dynamo.Scheduler.AsyncTaskExtensions.AllComplete(this System.Collections.Generic.IEnumerable tasks, System.Action> action) -> System.IDisposable static Dynamo.Scheduler.AsyncTaskExtensions.Then(this Dynamo.Scheduler.AsyncTask task, Dynamo.Scheduler.AsyncTaskCompletedHandler action) -> System.IDisposable static Dynamo.Scheduler.AsyncTaskExtensions.ThenPost(this Dynamo.Scheduler.AsyncTask task, Dynamo.Scheduler.AsyncTaskCompletedHandler action, System.Threading.SynchronizationContext context = null) -> System.IDisposable @@ -3161,3 +3164,5 @@ virtual Dynamo.Search.SearchElements.NodeSearchElement.GenerateInputParameters() virtual Dynamo.Search.SearchElements.NodeSearchElement.GenerateOutputParameters() -> System.Collections.Generic.IEnumerable virtual Dynamo.Search.SearchElements.NodeSearchElement.OnItemProduced(Dynamo.Graph.Nodes.NodeModel obj) -> void virtual Dynamo.Search.SearchElements.SearchElementBase.CreationName.get -> string + + diff --git a/src/DynamoSandbox/DynamoCoreSetup.cs b/src/DynamoSandbox/DynamoCoreSetup.cs index 0d5fc122962..96e38a67445 100644 --- a/src/DynamoSandbox/DynamoCoreSetup.cs +++ b/src/DynamoSandbox/DynamoCoreSetup.cs @@ -142,7 +142,7 @@ public void RunApplication(Application app) private void LoadDynamoView() { DynamoModel model; - model = StartupUtils.MakeModel(false, CLILocale, noNetworkMode, ASMPath ?? string.Empty, analyticsInfo); + model = StartupUtils.MakeModel(false, CLILocale, noNetworkMode, useCustomNodeCache: true, ASMPath ?? string.Empty, analyticsInfo); model.CERLocation = CERLocation; viewModel = DynamoViewModel.Start(