From faab394fc4d8affc90f597652824a998c689619d Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Sun, 14 Aug 2022 19:38:58 +0200 Subject: [PATCH] Implement topological sorting using feature dependencies --- .../EnumerableTopologicalSortExtensions.cs | 33 +++++++++++ .../Elsa.Features/Implementations/Module.cs | 57 ++++--------------- 2 files changed, 45 insertions(+), 45 deletions(-) create mode 100644 src/common/Elsa.Features/Extensions/EnumerableTopologicalSortExtensions.cs diff --git a/src/common/Elsa.Features/Extensions/EnumerableTopologicalSortExtensions.cs b/src/common/Elsa.Features/Extensions/EnumerableTopologicalSortExtensions.cs new file mode 100644 index 000000000..032e884f4 --- /dev/null +++ b/src/common/Elsa.Features/Extensions/EnumerableTopologicalSortExtensions.cs @@ -0,0 +1,33 @@ +namespace Elsa.Features.Extensions; + +public static class EnumerableTopologicalSortExtensions +{ + public static IEnumerable TSort(this IEnumerable source, Func> dependencies, bool throwOnCycle = false) + { + var sorted = new List(); + var visited = new HashSet(); + + foreach (var item in source) + Visit(item, visited, sorted, dependencies, throwOnCycle); + + return sorted; + } + + private static void Visit(T item, ISet visited, ICollection sorted, Func> dependencies, bool throwOnCycle) + { + if (!visited.Contains(item)) + { + visited.Add(item); + + foreach (var dep in dependencies(item)) + Visit(dep, visited, sorted, dependencies, throwOnCycle); + + sorted.Add(item); + } + else + { + if (throwOnCycle && !sorted.Contains(item)) + throw new Exception("Cyclic dependency found"); + } + } +} \ No newline at end of file diff --git a/src/common/Elsa.Features/Implementations/Module.cs b/src/common/Elsa.Features/Implementations/Module.cs index c2819fb8d..fc156b236 100644 --- a/src/common/Elsa.Features/Implementations/Module.cs +++ b/src/common/Elsa.Features/Implementations/Module.cs @@ -1,5 +1,6 @@ using System.Reflection; using Elsa.Features.Attributes; +using Elsa.Features.Extensions; using Elsa.Features.Services; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; @@ -21,18 +22,18 @@ public class Module : IModule public IServiceCollection Services { get; } - public T Configure(Action? configure = default) where T : class, IFeature => Configure(serviceConfiguration => (T)Activator.CreateInstance(typeof(T), serviceConfiguration)!, configure); + public T Configure(Action? configure = default) where T : class, IFeature => Configure(module => (T)Activator.CreateInstance(typeof(T), module)!, configure); public T Configure(Func factory, Action? configure = default) where T : class, IFeature { - if (_features.FirstOrDefault(x => x is T) is not T configurator) + if (_features.FirstOrDefault(x => x is T) is not T feature) { - configurator = factory(this); - _features.Add(configurator); + feature = factory(this); + _features.Add(feature); } - configure?.Invoke(configurator); - return configurator; + configure?.Invoke(feature); + return feature; } public IModule ConfigureHostedService(int priority = 0) @@ -43,9 +44,10 @@ public class Module : IModule public void Apply() { - ResolveDependencies(); + var featureTypes = _features.Select(x => x.GetType()).TSort(x => x.GetCustomAttributes().Select(dependsOn => dependsOn.Type)).ToList(); + var features = featureTypes.Select(featureType => _features.FirstOrDefault(x => x.GetType() == featureType) ?? (IFeature)Activator.CreateInstance(featureType, this)!).ToList(); - foreach (var feature in _features) + foreach (var feature in features) { feature.Configure(); feature.ConfigureHostedServices(); @@ -53,43 +55,8 @@ public class Module : IModule foreach (var hostedServiceDescriptor in _hostedServiceDescriptors.OrderBy(x => x.Order)) Services.TryAddEnumerable(ServiceDescriptor.Singleton(typeof(IHostedService), hostedServiceDescriptor.HostedServiceType)); - - foreach (var feature in _features) - { + + foreach (var feature in features) feature.Apply(); - } - } - - private void ResolveDependencies() - { - var resolvedDependencyTypes = new HashSet(); - - foreach (var configurator in _features.ToList()) - ResolveDependencies(configurator, resolvedDependencyTypes); - } - - private void ResolveDependencies(IFeature feature, ISet resolvedDependencyTypes) - { - var dependencyTypes = feature.GetType().GetCustomAttributes().Select(x => x.Type).ToHashSet(); - dependencyTypes = dependencyTypes.Except(resolvedDependencyTypes).ToHashSet(); - - foreach (var type in dependencyTypes) - { - var dependencyConfigurator = AddConfigurator(type); - resolvedDependencyTypes.Add(type); - ResolveDependencies(dependencyConfigurator, resolvedDependencyTypes); - } - } - - private IFeature AddConfigurator(Type type) - { - var configurator = _features.FirstOrDefault(x => x.GetType() == type); - - if (configurator != null) - return configurator; - - configurator = (IFeature)Activator.CreateInstance(type, this)!; - _features.Add(configurator); - return configurator; } } \ No newline at end of file