Skip to content

DynamoModel startup time improvements #16236

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

Open
wants to merge 2 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
55 changes: 41 additions & 14 deletions src/DynamoApplications/StartupUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -426,7 +426,7 @@ public static string SetLocale(CommandLineArguments cmdLineArgs)
/// <summary>
/// The white list of dependencies to be ignored.
/// </summary>
private static readonly String[] assemblyNamesToIgnore = { "Newtonsoft.Json", "RevitAPI.dll", "RevitAPIUI.dll" };
private static readonly String[] assemblyNamesToIgnore = { "Newtonsoft.Json", "RevitAPI", "RevitAPIUI" };

/// <summary>
/// Checks that an assembly does not have any dependencies that have already been loaded into the
Expand All @@ -439,6 +439,9 @@ public static List<Exception> CheckAssemblyForVersionMismatches(Assembly assembl
return GetVersionMismatchedReferencesInAppDomain(assembly, assemblyNamesToIgnore);
}

private static Dictionary<string, Dictionary<string, (Assembly, AssemblyName)>> LoadedAssembliesCache
= new Dictionary<string, Dictionary<string, (Assembly, AssemblyName)>>();

/// <summary>
/// Handler for an assembly load event into a host's appdomain - we need to make sure
/// that another addin or package has not loaded another version of a .dll that we require.
Expand All @@ -454,38 +457,62 @@ private static List<Exception> GetVersionMismatchedReferencesInAppDomain(Assembl
// educated guess and assume they will be loaded in the same ALC as the main input assembly.
// Ignore some assemblies(Revit assemblies) that we know work and have changed their version number format or do not align
// with semantic versioning.
var loadedAssemblies = AssemblyLoadContext.GetLoadContext(assembly).Assemblies
.Where(a => !assemblyNamesToIgnore.Contains(a.GetName().Name))
.ToList();
var loadContext = AssemblyLoadContext.GetLoadContext(assembly);

// populate the cache
if (!LoadedAssembliesCache.TryGetValue(loadContext.Name, out var loadedAssemblies))
{
loadedAssemblies = new Dictionary<string, (Assembly, AssemblyName)>();
LoadedAssembliesCache[loadContext.Name] = loadedAssemblies;

foreach (var asm in loadContext.Assemblies)
{
var asmData = asm.GetName();
var name = asmData.Name;
if (assemblyNamesToIgnore.Contains(name) || loadedAssemblies.ContainsKey(name))
{
continue;
}

loadedAssemblies[name] = (asm, asmData);
}
}

// update the cache with the newly loaded assembly
var asmName = assembly.GetName();
if (!loadedAssemblies.ContainsKey(asmName.Name))
{
loadedAssemblies[asmName.Name] = (assembly, asmName);
}

var output = new List<Exception>();
var loadedAssemblyDict = loadedAssemblies.GroupBy(a => a.GetName().Name).ToDictionary(g => g.Key, g => g.FirstOrDefault());

foreach (var currentReferencedAssembly in assembly.GetReferencedAssemblies())
{
if (loadedAssemblyDict.TryGetValue(currentReferencedAssembly.Name, out Assembly loadedAssembly))
if (loadedAssemblies.TryGetValue(currentReferencedAssembly.Name, out var assemblyData))
{
//if the dll is already loaded, then check lthat our required version is not greater than the currently loaded one.
if (currentReferencedAssembly.Version.Major > loadedAssembly.GetName().Version.Major)
var (_, asmInfo) = assemblyData;
//if the dll is already loaded, then check that our required version is not greater than the currently loaded one.
if (currentReferencedAssembly.Version.Major > asmInfo.Version.Major)
{
//there must exist a loaded assembly which references the newer version of the assembly which we require - lets find it:
var referencingNewerVersions = new List<AssemblyName>();
foreach (var originalLoadedAssembly in loadedAssemblies)
var referencingNewerVersions = new HashSet<string>();
foreach (var originalLoadedAssemblyData in loadedAssemblies)
{
foreach (var refedAssembly in originalLoadedAssembly.GetReferencedAssemblies())
foreach (var refedAssembly in originalLoadedAssemblyData.Value.Item1.GetReferencedAssemblies())
{
//if the version matches then this is one our guys
if (refedAssembly.Version == loadedAssembly.GetName().Version)
if (refedAssembly.Version == asmInfo.Version)
{
referencingNewerVersions.Add(originalLoadedAssembly.GetName());
referencingNewerVersions.Add(originalLoadedAssemblyData.Key);
}
}
}

output.Add(new FileLoadException(
string.Format(Resources.MismatchedAssemblyVersion, assembly.FullName, currentReferencedAssembly.FullName)
+ Environment.NewLine + Resources.MismatchedAssemblyList + Environment.NewLine +
string.Join(", ", referencingNewerVersions.Select(x => x.Name).Distinct())));
string.Join(", ", referencingNewerVersions)));
}
}
}
Expand Down
24 changes: 9 additions & 15 deletions src/DynamoCore/Library/FunctionDescriptor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -364,18 +364,16 @@ public string Category
//get NodeCategoryAttribute for this function if it was defined
var nodeCat = type.GetMethods().Where(x => x.Name == FunctionName)
.Select(x => x.GetCustomAttribute(typeof(NodeCategoryAttribute)))
.Where(x => x != null)
.Cast<NodeCategoryAttribute>()
.Select(x => x.ElementCategory)
.FirstOrDefault();
.OfType<NodeCategoryAttribute>()
.FirstOrDefault()?.ElementCategory;

//if attribute is found compose node category string with last part from attribute
if (!string.IsNullOrEmpty(nodeCat) && (
nodeCat == LibraryServices.Categories.Constructors
|| nodeCat == LibraryServices.Categories.Properties
|| nodeCat == LibraryServices.Categories.MemberFunctions))
{
categoryBuf.Append("." + UnqualifedClassName + "." + nodeCat);
categoryBuf.Append($".{UnqualifedClassName}.{nodeCat}");
category = categoryBuf.ToString();
return category;
}
Expand All @@ -391,20 +389,17 @@ public string Category
switch (Type)
{
case FunctionType.Constructor:
categoryBuf.Append(
"." + UnqualifedClassName + "." + LibraryServices.Categories.Constructors);
categoryBuf.Append($".{UnqualifedClassName}.{LibraryServices.Categories.Constructors}");
break;

case FunctionType.StaticMethod:
case FunctionType.InstanceMethod:
categoryBuf.Append(
"." + UnqualifedClassName + "." + LibraryServices.Categories.MemberFunctions);
categoryBuf.Append($".{UnqualifedClassName}.{LibraryServices.Categories.MemberFunctions}");
break;

case FunctionType.StaticProperty:
case FunctionType.InstanceProperty:
categoryBuf.Append(
"." + UnqualifedClassName + "." + LibraryServices.Categories.Properties);
categoryBuf.Append($".{UnqualifedClassName}.{LibraryServices.Categories.Properties}");
break;
}
return categoryBuf.ToString();
Expand All @@ -420,7 +415,7 @@ public string QualifiedName
{
return FunctionType.GenericFunction == Type
? UserFriendlyName
: ClassName + "." + UserFriendlyName;
: $"{ClassName}.{UserFriendlyName}";
}
}

Expand All @@ -432,9 +427,8 @@ public string MangledName
{
get
{
return Parameters != null && Parameters.Any()
? QualifiedName + "@" + string.Join(",", Parameters.Select(p => p.Type))
: QualifiedName;
return Parameters?.Any() != true ? QualifiedName :
$"{QualifiedName}@{string.Join(",", Parameters.Select(p => p.Type))}";
}
}

Expand Down
6 changes: 3 additions & 3 deletions src/DynamoCore/Library/LibraryCustomization.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
Expand Down Expand Up @@ -71,9 +71,9 @@ public LibraryCustomization GetLibraryCustomization(string assemblyPath)
/// found, or null otherwise.</returns>
public static LibraryCustomization GetForAssembly(string assemblyPath, IPathManager pathManager, bool useAdditionalPaths = true)
{
if (triedPaths.ContainsKey(assemblyPath))
if (triedPaths.TryGetValue(assemblyPath, out var isCached))
{
return triedPaths[assemblyPath] ? cache[assemblyPath] : null;
return isCached ? cache[assemblyPath] : null;
}

var customizationPath = "";
Expand Down
37 changes: 18 additions & 19 deletions src/DynamoCore/Library/XmlDocumentationExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
Expand Down Expand Up @@ -130,23 +130,19 @@ private static MemberDocumentNode GetMemberDocumentNode(
XmlReader xml )
{
//customNodeDefinitions typedParameters don't have functionDescriptors
if (function == null)
if (function == null || string.IsNullOrEmpty(function.Assembly))
{
return null;
}
var assemblyName = function.Assembly;

if (string.IsNullOrEmpty(assemblyName))
return null;

var fullyQualifiedName = MemberDocumentNode.MakeFullyQualifiedName
(assemblyName, GetMemberElementName(function));
var fullyQualifiedName = MemberDocumentNode.MakeFullyQualifiedName(
function.Assembly, GetMemberElementName(function));

if (!documentNodes.ContainsKey(fullyQualifiedName))
{
if (xml == null)
xml = DocumentationServices.GetForAssembly(function.Assembly, function.PathManager);
LoadDataFromXml(xml, assemblyName);
LoadDataFromXml(xml, function.Assembly);
}

MemberDocumentNode documentNode = null;
Expand All @@ -156,13 +152,13 @@ private static MemberDocumentNode GetMemberDocumentNode(
{
// Note that the following may take the incorrect overload.
// Unfortunately we can't map back to the exact .NET parameter types from the DS function descriptor.
var overloadedName = documentNodes.Keys.
Where(key => key.Contains(function.ClassName + "." + function.FunctionName)).FirstOrDefault();
var searchName = $"{function.ClassName}.{function.FunctionName}";
var overloadedName = documentNodes.Keys.FirstOrDefault(key => key.Contains(searchName));

if (overloadedName == null)
return null;
if (documentNodes.ContainsKey(overloadedName))
documentNode = documentNodes[overloadedName];
if (documentNodes.TryGetValue(overloadedName, out documentNode))
return documentNode;
}

return documentNode;
Expand Down Expand Up @@ -242,7 +238,7 @@ private static string GetMemberElementName(FunctionDescriptor member)

string memberName = member.FunctionName;
if (!string.IsNullOrEmpty(member.ClassName))
memberName = member.ClassName + "." + member.FunctionName;
memberName = $"{member.ClassName}.{member.FunctionName}";

switch (member.Type)
{
Expand All @@ -266,10 +262,13 @@ private static string GetMemberElementName(FunctionDescriptor member)
// parameters are listed according to their type, not their name
string paramTypesList = String.Join(
",",
member.Parameters.Select(x => x.Type.ToString()).Select(PrimitiveMap).ToArray()
member.Parameters.Select(x => x.Type.ToString()).Select(PrimitiveMap)
);

if (!String.IsNullOrEmpty(paramTypesList)) memberName += "(" + paramTypesList + ")";

if (!String.IsNullOrEmpty(paramTypesList))
{
memberName = $"{memberName}({paramTypesList})";
}
break;

case FunctionType.StaticMethod:
Expand All @@ -289,7 +288,7 @@ private static string GetMemberElementName(FunctionDescriptor member)
}

// elements are of the form "M:Namespace.Class.Method"
return String.Format("{0}:{1}", prefixCode, memberName);
return $"{prefixCode}:{memberName}";
}

private enum XmlTagType
Expand Down Expand Up @@ -421,4 +420,4 @@ private static void LoadDataFromXml(XmlReader reader, string assemblyName)
#endregion
}

}
}
27 changes: 15 additions & 12 deletions src/DynamoCore/Models/DynamoModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1506,7 +1506,7 @@ private void InitializeCustomNodeManager()
CustomNodeManager.InfoUpdated += info =>
{
//just bail in service mode.
if (IsServiceMode)
if (IsServiceMode || SearchModel == null)
{
return;
}
Expand All @@ -1515,17 +1515,21 @@ private void InitializeCustomNodeManager()
|| !info.IsVisibleInDynamoLibrary)
return;


var elements = SearchModel?.Entries.OfType<CustomNodeSearchElement>().
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();
// this is a hot path that will get hit for every single node definition; avoid linq
var elements = new List<CustomNodeSearchElement>();
foreach (var entry in SearchModel.Entries)
{
if (entry is not CustomNodeSearchElement cnSearch ||
string.IsNullOrWhiteSpace(cnSearch?.Path) ||
string.Compare(cnSearch.Path, info.Path, StringComparison.OrdinalIgnoreCase) != 0)
{
continue;
}

elements.Add(cnSearch);
}

if (elements.Any())
if (elements.Count != 0)
{
foreach (var element in elements)
{
Expand All @@ -1535,7 +1539,6 @@ private void InitializeCustomNodeManager()
return;
}


customNodeSearchRegistry.Add(info.FunctionId);
var searchElement = new CustomNodeSearchElement(CustomNodeManager, info);
SearchModel.Add(searchElement);
Expand Down
Loading