diff --git a/.ci/code/BHoM_Adapter_Tests/Objects/StructuralAdapter.cs b/.ci/code/BHoM_Adapter_Tests/Objects/StructuralAdapter.cs index 0528c6b7..57aa329e 100644 --- a/.ci/code/BHoM_Adapter_Tests/Objects/StructuralAdapter.cs +++ b/.ci/code/BHoM_Adapter_Tests/Objects/StructuralAdapter.cs @@ -43,6 +43,7 @@ using System.Text; using System.Threading.Tasks; using System.Reflection; +using BH.Engine.Adapter; namespace BH.Tests.Adapter { @@ -124,10 +125,7 @@ protected override IEnumerable IRead(Type type, IList ids, ActionCo List modelObjects = Created.Where(x => x.Item1.IsAssignableFrom(type)).SelectMany(x => x.Item2).ToList(); - MethodInfo method = typeof(Engine.Adapter.Query).GetMethod(nameof(Engine.Adapter.Query.GetDependencyTypes)); - method = method.MakeGenericMethod(type); - - List dependencyTypes = method.Invoke(null, new object[] { this }) as List; + List dependencyTypes = this.GetDependencyTypes(type); MethodInfo readCached = typeof(BHoMAdapter).GetMethods(BindingFlags.NonPublic | BindingFlags.Instance).Where(x => x.GetGenericArguments().Length == 1).FirstOrDefault(x => x.Name == nameof(GetCachedOrRead)); diff --git a/.ci/code/BHoM_Adapter_Tests/PushTests.cs b/.ci/code/BHoM_Adapter_Tests/PushTests.cs index a294e284..e0479f2b 100644 --- a/.ci/code/BHoM_Adapter_Tests/PushTests.cs +++ b/.ci/code/BHoM_Adapter_Tests/PushTests.cs @@ -68,14 +68,18 @@ public void DependencyOrder_BarLoads() sa.Push(inputObjects); - string correctOrder = "BH.oM.Structure.Constraints.Constraint6DOF, BH.oM.Structure.MaterialFragments.IMaterialFragment, " + - "BH.oM.Structure.SectionProperties.ISectionProperty, BH.oM.Structure.Elements.Node, " + - "BH.oM.Structure.Constraints.BarRelease, BH.oM.Structure.Offsets.Offset, " + - "BH.oM.Structure.Elements.Bar, BH.oM.Structure.Loads.Loadcase, " + - "BH.oM.Structure.Loads.BarUniformlyDistributedLoad"; + string correctOrder = "BH.oM.Structure.MaterialFragments.IMaterialFragment, " + + "BH.oM.Structure.Constraints.Constraint6DOF, " + + "BH.oM.Structure.SectionProperties.ISectionProperty, " + + "BH.oM.Structure.Elements.Node, " + + "BH.oM.Structure.Constraints.BarRelease, " + + "BH.oM.Structure.Offsets.Offset, " + + "BH.oM.Structure.Elements.Bar, " + + "BH.oM.Structure.Loads.Loadcase, " + + "BH.oM.Structure.Loads.BarUniformlyDistributedLoad"; string createdOrder = string.Join(", ", sa.Created.Select(c => c.Item1.FullName)); - Assert.IsTrue(createdOrder == correctOrder); + Assert.AreEqual(correctOrder, createdOrder); } @@ -95,12 +99,21 @@ public void DependencyOrder_MostStructuralObjects() sa.Push(inputObjects); - string correctOrder = "BH.oM.Structure.Constraints.Constraint4DOF, BH.oM.Structure.MaterialFragments.IMaterialFragment, " + - "BH.oM.Structure.Constraints.Constraint6DOF, BH.oM.Structure.SectionProperties.ISectionProperty, " + - "BH.oM.Structure.Elements.Node, BH.oM.Structure.Constraints.BarRelease, BH.oM.Structure.Offsets.Offset, " + - "BH.oM.Structure.Elements.Bar, BH.oM.Structure.Loads.Loadcase, BH.oM.Structure.SurfaceProperties.ISurfaceProperty, " + - "BH.oM.Structure.Elements.Opening, BH.oM.Structure.Elements.Edge, BH.oM.Structure.Loads.BarUniformlyDistributedLoad, " + - "BH.oM.Structure.Elements.FEMesh, BH.oM.Structure.Elements.Panel"; + string correctOrder = "BH.oM.Structure.Constraints.Constraint6DOF, " + + "BH.oM.Structure.MaterialFragments.IMaterialFragment, " + + "BH.oM.Structure.Constraints.Constraint4DOF, " + + "BH.oM.Structure.Elements.Node, " + + "BH.oM.Structure.Elements.Edge, " + + "BH.oM.Structure.SectionProperties.ISectionProperty, " + + "BH.oM.Structure.Constraints.BarRelease, " + + "BH.oM.Structure.Offsets.Offset, " + + "BH.oM.Structure.SurfaceProperties.ISurfaceProperty, " + + "BH.oM.Structure.Elements.Bar, " + + "BH.oM.Structure.Loads.Loadcase, " + + "BH.oM.Structure.Elements.Opening, " + + "BH.oM.Structure.Loads.BarUniformlyDistributedLoad, " + + "BH.oM.Structure.Elements.FEMesh, " + + "BH.oM.Structure.Elements.Panel"; string createdOrder = string.Join(", ", sa.Created.Select(c => c.Item1.FullName)); Assert.AreEqual(correctOrder, createdOrder); @@ -117,9 +130,12 @@ public void Dependecies_UpdateOnly() sa.Push(inputObjects, "", BH.oM.Adapter.PushType.UpdateOnly); - string correctOrderCreated = "BH.oM.Structure.MaterialFragments.IMaterialFragment, BH.oM.Structure.Constraints.Constraint6DOF, " + - "BH.oM.Structure.SectionProperties.ISectionProperty, BH.oM.Structure.Elements.Node, " + - "BH.oM.Structure.Constraints.BarRelease, BH.oM.Structure.Offsets.Offset"; + string correctOrderCreated = "BH.oM.Structure.Constraints.Constraint6DOF, " + + "BH.oM.Structure.MaterialFragments.IMaterialFragment, " + + "BH.oM.Structure.SectionProperties.ISectionProperty, " + + "BH.oM.Structure.Elements.Node, " + + "BH.oM.Structure.Constraints.BarRelease, " + + "BH.oM.Structure.Offsets.Offset"; string correctOrderUpdated = "BH.oM.Structure.Elements.Node, BH.oM.Structure.SectionProperties.ISectionProperty, BH.oM.Structure.Elements.Bar"; string createdOrder = string.Join(", ", sa.Created.Select(c => c.Item1.FullName)); @@ -178,6 +194,27 @@ public void DependencyOrder_UpdateAndFullPush() "For Node objects, UpdateOnly should have come before FullPush."); } + [Test] + public void DependencyOrder_CreateLoadNoObjectsWithIds() + { + List loads = Create.RandomObjects(3); + + sa.Push(loads); + + string correctOrder = "BH.oM.Structure.MaterialFragments.IMaterialFragment, " + + "BH.oM.Structure.Constraints.Constraint6DOF, " + + "BH.oM.Structure.SectionProperties.ISectionProperty, " + + "BH.oM.Structure.Elements.Node, " + + "BH.oM.Structure.Constraints.BarRelease, " + + "BH.oM.Structure.Offsets.Offset, " + + "BH.oM.Structure.Elements.Bar, " + + "BH.oM.Structure.Loads.Loadcase, " + + "BH.oM.Structure.Loads.BarUniformlyDistributedLoad"; + string createdOrder = string.Join(", ", sa.Created.Select(c => c.Item1.FullName)); + + Assert.AreEqual(correctOrder, createdOrder); + } + [Test] public void DependencyOrder_CreateLoadAllObjectsWithIds() { diff --git a/Adapter_Engine/Query/GetDependencySortedObjects.cs b/Adapter_Engine/Query/GetDependencySortedObjects.cs index e04d42be..188a11fc 100644 --- a/Adapter_Engine/Query/GetDependencySortedObjects.cs +++ b/Adapter_Engine/Query/GetDependencySortedObjects.cs @@ -30,6 +30,7 @@ using BH.oM.Adapter; using BH.oM.Base.Attributes; using BH.Engine.Reflection; +using System.Reflection; namespace BH.Engine.Adapter { @@ -56,68 +57,14 @@ public static List>> GetDependencySort Dictionary, List> allObjectsPerType = GetObjectsAndRecursiveDependencies(objects, pushType, bHoMAdapter); // Sort the groups by dependency order, so they can be pushed in the correct order. - List>> orderedObjects = new List>>(); - - List> handledGroups = new List>(); - foreach (var typeGroup in allObjectsPerType) - { - if (handledGroups.Contains(typeGroup.Key)) - continue; - - // We can scan the rest of the input objects to see if they include dependencies of this current object type. - List dependenciesToLookFor = new List(); - - // Add direct dependencies of this current object type. - if (bHoMAdapter.DependencyTypes.TryGetValue(typeGroup.Key.Item1, out List typeDeps)) - dependenciesToLookFor.AddRange(typeDeps); - - // Check if the current object type has basetypes (interfaces) for which dependencies may have been specified. - // E.g. for a CrossSection object we can get ISectionProperty which it implements, - // which in turn is a type that commonly specifies additional dependencies (generally, IMaterialFragment). - foreach (var baseType in typeGroup.Key.Item1.BaseTypes()) - { - if (bHoMAdapter.DependencyTypes.TryGetValue(baseType, out List baseTypeDeps)) - dependenciesToLookFor.AddRange(baseTypeDeps); - } - - // If this current object type does not have dependencies, add it at the start of the list and continue. - if (!dependenciesToLookFor.Any()) - { - orderedObjects.Insert(0, new Tuple>(typeGroup.Key.Item1, typeGroup.Key.Item2, typeGroup.Value.OfType())); - handledGroups.Add(typeGroup.Key); - continue; - } - - // Scan the rest of the input objects to see they include dependencies, - // and add them to the orderedObjects list in order to prioritize them. - foreach (var otherTypeGroup in allObjectsPerType) - { - if (handledGroups.Contains(otherTypeGroup.Key) || otherTypeGroup.Key == typeGroup.Key) - continue; - - if (dependenciesToLookFor.Contains(otherTypeGroup.Key.Item1) || dependenciesToLookFor.Any(d => d.IsAssignableFromIncludeGenericsAndRefTypes(otherTypeGroup.Key.Item1))) - { - orderedObjects.Add(new Tuple>(otherTypeGroup.Key.Item1, otherTypeGroup.Key.Item2, otherTypeGroup.Value.OfType())); - handledGroups.Add(otherTypeGroup.Key); - } - } - } - - // The non-handled groups can be added at the end in any order, because they don't have any dependency ordering. - foreach (var typeGroup in allObjectsPerType) - { - if (handledGroups.Contains(typeGroup.Key)) - continue; - - orderedObjects.Add(new Tuple>(typeGroup.Key.Item1, typeGroup.Key.Item2, typeGroup.Value.OfType())); - } + List>> baseTypeGroupObjects = allObjectsPerType.Select(x => new Tuple> (x.Key.Item1, x.Key.Item2, x.Value )).ToList(); // Group per base type extracted from dependencies. // This is useful to reduce the number of CRUD calls. var allTypesInDependencies = bHoMAdapter.DependencyTypes.Values.SelectMany(v => v).Distinct(); - for (int i = 0; i < orderedObjects.Count; i++) + for (int i = 0; i < baseTypeGroupObjects.Count; i++) { - var kv = orderedObjects.ElementAt(i); + var kv = baseTypeGroupObjects.ElementAt(i); foreach (Type baseType in kv.Item1.BaseTypes()) { @@ -128,32 +75,32 @@ public static List>> GetDependencySort continue; //Find matching item in the ordered obejct, matching the base type and push type. - var matchingItem = orderedObjects.FirstOrDefault(o => o.Item1 == baseType && o.Item2 == kv.Item2); + var matchingItem = baseTypeGroupObjects.FirstOrDefault(o => o.Item1 == baseType && o.Item2 == kv.Item2); int matchingIndex; //Get index of matching object if match is not null. if (matchingItem != null) - matchingIndex = orderedObjects.IndexOf(matchingItem); + matchingIndex = baseTypeGroupObjects.IndexOf(matchingItem); else matchingIndex = -1; if (matchingIndex == -1) { //Nothing found. Replace the current item with base type instead of concrete type. - orderedObjects[i] = new Tuple>(baseType, kv.Item2, kv.Item3); + baseTypeGroupObjects[i] = new Tuple>(baseType, kv.Item2, kv.Item3); } else { //If matching base type is found, concatenate the to sets together, to be CRUD together. //For example, if the pushtype for both is the same, SteelSections and AluminiumSections will be grouped under ISectionProperty if ISectionProperty is in DependencyTypes. - var toAdd = new Tuple>(baseType, orderedObjects[matchingIndex].Item2, orderedObjects[matchingIndex].Item3.Concat(kv.Item3)); + var toAdd = new Tuple>(baseType, baseTypeGroupObjects[matchingIndex].Item2, baseTypeGroupObjects[matchingIndex].Item3.Concat(kv.Item3)); int minIndex = Math.Min(i, matchingIndex); int maxIndex = Math.Max(i, matchingIndex); //Replace on minimum of the two indecies found and remove the other. - orderedObjects[minIndex] = toAdd; - orderedObjects.RemoveAt(maxIndex); + baseTypeGroupObjects[minIndex] = toAdd; + baseTypeGroupObjects.RemoveAt(maxIndex); i--; //Decrement i as item has been removed from the list. } found = true; @@ -166,6 +113,17 @@ public static List>> GetDependencySort } } + //Dictionary to store the dependency depth of each type + //The dependency depth indicates how many objects being pushed that depend on them + //This means that the types with the highest depth count should be pushed first + Dictionary dependecyDepth = new Dictionary(); + + //Method runs through all types, and recursively calls the dependecy types, and increments the depth of each type for every time it is found + EvaluateDependencyDepths(bHoMAdapter, baseTypeGroupObjects.Select(x => x.Item1).Distinct(), dependecyDepth); + + //Sorts the types by highest to lowest depth count + List>> orderedObjects = baseTypeGroupObjects.OrderByDescending(x => dependecyDepth[x.Item1]).ToList(); + // If two types are subject to two different CRUD operations (e.g. UpdateOnly and FullCRUD), // make sure the order of CRUD operations is appropriate (e.g. UpdateOnly must happen before FullCRUD to avoid duplicates). // For example, this happens when both Nodes and Bars are sent via UpdateOnly during a same Push operation, @@ -188,6 +146,25 @@ public static List>> GetDependencySort return orderedObjects; } + + /***************************************************/ + + [Description("Looping through all types and their dependencies and incrementally increases the depth counter for every time a type is found.")] + private static void EvaluateDependencyDepths(this IBHoMAdapter bHoMAdapter, IEnumerable types, Dictionary depths) + { + foreach (Type type in types) + { + if (!depths.ContainsKey(type)) + depths[type] = 0; //First appearance of the type, either as a standalone object or as a dependecy + else + depths[type]++; //Increment depth counter for every time the dependecy is found, either as the standalone type, or as a dependency object + + //Recursive call to make sure all dependecies are incremented + EvaluateDependencyDepths(bHoMAdapter, bHoMAdapter.GetDependencyTypes(type), depths); + } + } + + /***************************************************/ } } diff --git a/Adapter_Engine/Query/GetDependencyTypes.cs b/Adapter_Engine/Query/GetDependencyTypes.cs index 5d5bd331..2f53a741 100644 --- a/Adapter_Engine/Query/GetDependencyTypes.cs +++ b/Adapter_Engine/Query/GetDependencyTypes.cs @@ -41,7 +41,12 @@ public static partial class Query [Description("Returns the dependency types for a certain object type.")] public static List GetDependencyTypes(this IBHoMAdapter bhomAdapter) { - Type type = typeof(T); + return GetDependencyTypes(bhomAdapter, typeof(T)); + } + + [Description("Returns the dependency types for a certain object type.")] + public static List GetDependencyTypes(this IBHoMAdapter bhomAdapter, Type type) + { List dependencyTypes = new List(); if (bhomAdapter.DependencyTypes.ContainsKey(type))