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

Cache FunctionDescriptor Category property #15834

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
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
87 changes: 77 additions & 10 deletions src/DynamoCore/Library/FunctionDescriptor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using Dynamo.Configuration;
using Dynamo.Graph.Nodes;
using Dynamo.Interfaces;
Expand Down Expand Up @@ -159,6 +160,16 @@ public FunctionDescriptorParams()
/// </summary>
public class FunctionDescriptor : IFunctionDescriptor
{
/// <summary>
/// A dictionary of loaded assemblies by assembly name to speed up Dynamo loading
/// </summary>
private static Dictionary<string, Assembly> assembliesByName;

/// <summary>
/// Ensure the assembly cache is kept around until all callers have finished using it
/// </summary>
private static int assemblyCachingRequests = 0;

/// <summary>
/// A comment describing the Function
/// </summary>
Expand Down Expand Up @@ -318,29 +329,32 @@ public IEnumerable<Tuple<string, string>> InputParameters
private set;
}

private string category;

/// <summary>
/// The category of this function.
/// </summary>
public string Category
{
get
{
if (category != null)
{
return category;
}

var categoryBuf = new StringBuilder();
categoryBuf.Append(GetRootCategory());

//if this is not BuiltIn function search NodeCategoryAttribute for it
if (ClassName != null)
{
//get function assembly
var asm = AppDomain.CurrentDomain.GetAssemblies()
.Where(x => x.GetName().Name == Path.GetFileNameWithoutExtension(Assembly))
.ToArray();
var asmName = Path.GetFileNameWithoutExtension(Assembly);

if (asm.Any() && asm.First().GetType(ClassName) != null)
//get class type of function
if (TryGetAssembly(asmName, out var asm) && asm.GetType(ClassName) is System.Type type)
{
//get class type of function
var type = asm.First().GetType(ClassName);

//get NodeCategoryAttribute for this function if it was been defined
var nodeCat = type.GetMethods().Where(x => x.Name == FunctionName)
.Select(x => x.GetCustomAttribute(typeof(NodeCategoryAttribute)))
Expand All @@ -356,7 +370,8 @@ public string Category
|| nodeCat == LibraryServices.Categories.MemberFunctions))
{
categoryBuf.Append("." + UnqualifedClassName + "." + nodeCat);
return categoryBuf.ToString();
category = categoryBuf.ToString();
return category;
}
}
}
Expand All @@ -380,7 +395,9 @@ public string Category
"." + UnqualifedClassName + "." + LibraryServices.Categories.Properties);
break;
}
return categoryBuf.ToString();

category = categoryBuf.ToString();
return category;
}
}

Expand Down Expand Up @@ -584,6 +601,56 @@ private bool CheckIfFunctionIsMarkedExperimentalByPrefs(FunctionDescriptor fd)
return false;
}
internal bool IsExperimental { get;}
}

/// <summary>
/// Try to get an <see cref="System.Reflection.Assembly"/> by name
/// </summary>
/// <param name="assemblyName">Name of the assembly to load</param>
/// <param name="assembly">The assembly</param>
/// <returns><see langword="true"/> if a matching assembly was found</returns>
private static bool TryGetAssembly(string assemblyName, out Assembly assembly)
{
// Use the lookup dictionary if it exists, to avoid doing .GetName calls.
if (assembliesByName != null)
{
return assembliesByName.TryGetValue(assemblyName, out assembly);
}

assembly = AppDomain.CurrentDomain.GetAssemblies()
.FirstOrDefault(x => x.GetName().Name == assemblyName);

return assembly != null;
}


/// <summary>
/// Load all assemblies by name in the current domain for faster <see cref="Category"/> lookup.
/// </summary>
/// <returns>An <see cref="IDisposable"/> that removes the assembly dictionary on <see cref="IDisposable.Dispose"/></returns>
internal static IDisposable CacheAssemblyNamesForZeroTouchNodeSearch()
{
return Scheduler.Disposable.Create(() => {
// If in a nested call, the assembliesByName cache should already exist,
// and 'assemblyCachingRequests' should be higher than 0
if (Interlocked.Increment(ref assemblyCachingRequests) == 1)
{
assembliesByName = new();
foreach(var asm in AppDomain.CurrentDomain.GetAssemblies())
{
// Only add the first occurence of an assembly name, to match the
// functionality of TryGetAssembly
assembliesByName.TryAdd(asm.GetName().Name, asm);
}
}
},
() => {
// If in a nested call, the count should be larger than 1, and the outer
// caller should be responsible for deleting the cache
if (Interlocked.Decrement(ref assemblyCachingRequests) == 0)
{
assembliesByName = null;
}
});
}
}
}
6 changes: 6 additions & 0 deletions src/DynamoCore/Models/DynamoModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1342,6 +1342,9 @@ public virtual void PostTraceReconciliation(Dictionary<Guid, List<string>> orpha
/// <param name="e"></param>
private void LibraryLoaded(object sender, LibraryServices.LibraryLoadedEventArgs e)
{
if (!e.LibraryPaths.Any()) return;

using (var cachedAssemblies = FunctionDescriptor.CacheAssemblyNamesForZeroTouchNodeSearch())
foreach (var newLibrary in e.LibraryPaths)
{
// Load all functions defined in that library.
Expand Down Expand Up @@ -3478,6 +3481,9 @@ internal void AddZeroTouchNodesToSearch(IEnumerable<FunctionGroup> functionGroup
{
var iDoc = LuceneUtility.InitializeIndexDocumentForNodes();
List<NodeSearchElement> nodes = new();

// Preload the assembly names for faster FunctionDescriptor.Category resolution
using (var cachedAssemblies = FunctionDescriptor.CacheAssemblyNamesForZeroTouchNodeSearch())
foreach (var funcGroup in functionGroups)
{
foreach (var functionDescriptor in funcGroup.Functions)
Expand Down
Loading