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(