Adds methods to permanently disable/enable strategies by name. Disabled strategies remain registered but are excluded from the default pipeline. SetEnabled (used for remnant fills) takes precedence over the disabled set, so explicit overrides still work. Pipeline test now checks against active strategy count dynamically. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
123 lines
4.1 KiB
C#
123 lines
4.1 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
|
|
namespace OpenNest.Engine.Strategies
|
|
{
|
|
public static class FillStrategyRegistry
|
|
{
|
|
private static readonly List<IFillStrategy> strategies = new();
|
|
private static List<IFillStrategy> sorted;
|
|
private static HashSet<string> enabledFilter;
|
|
private static readonly HashSet<string> disabled = new(StringComparer.OrdinalIgnoreCase);
|
|
|
|
static FillStrategyRegistry()
|
|
{
|
|
LoadFrom(typeof(FillStrategyRegistry).Assembly);
|
|
}
|
|
|
|
public static IReadOnlyList<IFillStrategy> Strategies =>
|
|
sorted ??= FilterStrategies();
|
|
|
|
private static List<IFillStrategy> FilterStrategies()
|
|
{
|
|
var source = enabledFilter != null
|
|
? strategies.Where(s => enabledFilter.Contains(s.Name))
|
|
: strategies.Where(s => !disabled.Contains(s.Name));
|
|
return source.OrderBy(s => s.Order).ToList();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Permanently disables strategies by name. They remain registered
|
|
/// but are excluded from the default pipeline.
|
|
/// </summary>
|
|
public static void Disable(params string[] names)
|
|
{
|
|
foreach (var name in names)
|
|
disabled.Add(name);
|
|
sorted = null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Re-enables a previously disabled strategy.
|
|
/// </summary>
|
|
public static void Enable(params string[] names)
|
|
{
|
|
foreach (var name in names)
|
|
disabled.Remove(name);
|
|
sorted = null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Restricts the active strategies to only those whose names are listed.
|
|
/// Pass null to restore all strategies.
|
|
/// </summary>
|
|
public static void SetEnabled(params string[] names)
|
|
{
|
|
enabledFilter = names != null && names.Length > 0
|
|
? new HashSet<string>(names, StringComparer.OrdinalIgnoreCase)
|
|
: null;
|
|
sorted = null;
|
|
}
|
|
|
|
public static void LoadFrom(Assembly assembly)
|
|
{
|
|
foreach (var type in assembly.GetTypes())
|
|
{
|
|
if (type.IsAbstract || type.IsInterface || !typeof(IFillStrategy).IsAssignableFrom(type))
|
|
continue;
|
|
|
|
var ctor = type.GetConstructor(Type.EmptyTypes);
|
|
if (ctor == null)
|
|
{
|
|
Debug.WriteLine($"[FillStrategyRegistry] Skipping {type.Name}: no parameterless constructor");
|
|
continue;
|
|
}
|
|
|
|
try
|
|
{
|
|
var instance = (IFillStrategy)ctor.Invoke(null);
|
|
|
|
if (strategies.Any(s => s.Name.Equals(instance.Name, StringComparison.OrdinalIgnoreCase)))
|
|
{
|
|
Debug.WriteLine($"[FillStrategyRegistry] Duplicate strategy '{instance.Name}' skipped");
|
|
continue;
|
|
}
|
|
|
|
strategies.Add(instance);
|
|
Debug.WriteLine($"[FillStrategyRegistry] Registered: {instance.Name} (Order={instance.Order})");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.WriteLine($"[FillStrategyRegistry] Failed to instantiate {type.Name}: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
sorted = null;
|
|
}
|
|
|
|
public static void LoadPlugins(string directory)
|
|
{
|
|
if (!Directory.Exists(directory))
|
|
return;
|
|
|
|
foreach (var dll in Directory.GetFiles(directory, "*.dll"))
|
|
{
|
|
try
|
|
{
|
|
var assembly = Assembly.LoadFrom(dll);
|
|
LoadFrom(assembly);
|
|
Debug.WriteLine($"[FillStrategyRegistry] Loaded plugin assembly: {Path.GetFileName(dll)}");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.WriteLine($"[FillStrategyRegistry] Failed to load {Path.GetFileName(dll)}: {ex.Message}");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|