diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..fc549fa --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ +# Auto detect text files and perform LF normalization +* text eol=crlf +*.png binary \ No newline at end of file diff --git a/Benchmark/Program.cs b/Benchmark/Program.cs index f5acf7b..d3717ca 100644 --- a/Benchmark/Program.cs +++ b/Benchmark/Program.cs @@ -7,7 +7,7 @@ namespace Craftimizer.Benchmark; internal static class Program { - private static void Main() + private static async Task Main() { //var summary = BenchmarkRunner.Run(); //return; @@ -77,12 +77,12 @@ internal static class Program Console.WriteLine($"{state.Quality} {state.CP} {state.Progress} {state.Durability}"); //return; - var (_, s) = config.Invoke(state, a => Console.WriteLine(a))!.Value; + var solver = new Solver.Solver(config, state); + solver.OnLog += s => Console.WriteLine(s); + solver.OnNewAction += s => Console.WriteLine(s); + solver.Start(); + var (_, s) = await solver.GetTask().ConfigureAwait(false); Console.WriteLine($"Qual: {s.Quality}/{s.Input.Recipe.MaxQuality}"); - return; - - config.Invoke(new(input)); - //Benchmark(() => ); } private static void Benchmark(Func search) diff --git a/Craftimizer/Windows/Craft.cs b/Craftimizer/Windows/Craft.cs index 0ff783e..29422e1 100644 --- a/Craftimizer/Windows/Craft.cs +++ b/Craftimizer/Windows/Craft.cs @@ -215,7 +215,8 @@ public sealed unsafe partial class Craft : Window, IDisposable public void Dispose() { StopSolve(); - SolverTask?.Wait(); + SolverTaskToken?.Cancel(); + SolverTask?.TryWait(); SolverTask?.Dispose(); SolverTaskToken?.Dispose(); diff --git a/Craftimizer/Windows/CraftSolver.cs b/Craftimizer/Windows/CraftSolver.cs index 6b70fd3..9149981 100644 --- a/Craftimizer/Windows/CraftSolver.cs +++ b/Craftimizer/Windows/CraftSolver.cs @@ -1,6 +1,7 @@ using Craftimizer.Simulator; using Craftimizer.Simulator.Actions; using Dalamud.Interface.Windowing; +using Dalamud.Logging; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -12,7 +13,7 @@ namespace Craftimizer.Plugin.Windows; public sealed unsafe partial class Craft : Window, IDisposable { private SimulationState? SolverState { get; set; } - private Task? SolverTask { get; set; } + private Solver.Solver? SolverTask { get; set; } private CancellationTokenSource? SolverTaskToken { get; set; } private ConcurrentQueue SolverActionQueue { get; } = new(); @@ -48,7 +49,10 @@ public sealed unsafe partial class Craft : Window, IDisposable SolverSim = new(state); SolverTaskToken = new(); - SolverTask = Task.Run(() => Config.SynthHelperSolverConfig.Invoke(state, SolverActionQueue.Enqueue, SolverTaskToken.Token)); + SolverTask = new(Config.SynthHelperSolverConfig, state) { Token = SolverTaskToken.Token }; + SolverTask.OnLog += s => PluginLog.Debug(s); + SolverTask.OnNewAction += SolverActionQueue.Enqueue; + SolverTask.Start(); } private void SolveTick() diff --git a/Craftimizer/Windows/Settings.cs b/Craftimizer/Windows/Settings.cs index 5fa226e..23bdd9c 100644 --- a/Craftimizer/Windows/Settings.cs +++ b/Craftimizer/Windows/Settings.cs @@ -312,32 +312,32 @@ public class Settings : Window DrawOption( "Progress", "Amount of weight to give to the craft's progress.", - config.ScoreProgressBonus, - v => config = config with { ScoreProgressBonus = v }, + config.ScoreProgress, + v => config = config with { ScoreProgress = v }, ref isDirty ); DrawOption( "Quality", "Amount of weight to give to the craft's quality.", - config.ScoreQualityBonus, - v => config = config with { ScoreQualityBonus = v }, + config.ScoreQuality, + v => config = config with { ScoreQuality = v }, ref isDirty ); DrawOption( "Durability", "Amount of weight to give to the craft's remaining durability.", - config.ScoreDurabilityBonus, - v => config = config with { ScoreDurabilityBonus = v }, + config.ScoreDurability, + v => config = config with { ScoreDurability = v }, ref isDirty ); DrawOption( "CP", "Amount of weight to give to the craft's remaining CP.", - config.ScoreCPBonus, - v => config = config with { ScoreCPBonus = v }, + config.ScoreCP, + v => config = config with { ScoreCP = v }, ref isDirty ); @@ -345,25 +345,25 @@ public class Settings : Window "Steps", "Amount of weight to give to the craft's number of steps. The lower\n" + "the step count, the higher the score.", - config.ScoreFewerStepsBonus, - v => config = config with { ScoreFewerStepsBonus = v }, + config.ScoreSteps, + v => config = config with { ScoreSteps = v }, ref isDirty ); if (ImGui.Button("Normalize Weights", OptionButtonSize)) { - var total = config.ScoreProgressBonus + - config.ScoreQualityBonus + - config.ScoreDurabilityBonus + - config.ScoreCPBonus + - config.ScoreFewerStepsBonus; + var total = config.ScoreProgress + + config.ScoreQuality + + config.ScoreDurability + + config.ScoreCP + + config.ScoreSteps; config = config with { - ScoreProgressBonus = config.ScoreProgressBonus / total, - ScoreQualityBonus = config.ScoreQualityBonus / total, - ScoreDurabilityBonus = config.ScoreDurabilityBonus / total, - ScoreCPBonus = config.ScoreCPBonus / total, - ScoreFewerStepsBonus = config.ScoreFewerStepsBonus / total + ScoreProgress = config.ScoreProgress / total, + ScoreQuality = config.ScoreQuality / total, + ScoreDurability = config.ScoreDurability / total, + ScoreCP = config.ScoreCP / total, + ScoreSteps = config.ScoreSteps / total }; isDirty = true; } diff --git a/Craftimizer/Windows/SimulatorSolver.cs b/Craftimizer/Windows/SimulatorSolver.cs index d4df22c..de3eaf6 100644 --- a/Craftimizer/Windows/SimulatorSolver.cs +++ b/Craftimizer/Windows/SimulatorSolver.cs @@ -1,6 +1,7 @@ using Craftimizer.Simulator; using Craftimizer.Simulator.Actions; using Dalamud.Interface.Windowing; +using Dalamud.Logging; using System; using System.Collections.Concurrent; using System.Threading; @@ -10,7 +11,7 @@ namespace Craftimizer.Plugin.Windows; public sealed partial class Simulator : Window, IDisposable { - private Task? SolverTask { get; set; } + private Solver.Solver? SolverTask { get; set; } private CancellationTokenSource? SolverTaskToken { get; set; } private ConcurrentQueue SolverActionQueue { get; } = new(); private int SolverInitialActionCount { get; set; } @@ -83,13 +84,17 @@ public sealed partial class Simulator : Window, IDisposable SolverInitialActionCount = Actions.Count; SolverTaskToken = new(); - SolverTask = Task.Run(() => Config.SimulatorSolverConfig.Invoke(solverState, SolverActionQueue.Enqueue, SolverTaskToken.Token)); + SolverTask = new(Config.SimulatorSolverConfig, solverState) { Token = SolverTaskToken.Token }; + SolverTask.OnLog += s => PluginLog.Debug(s); + SolverTask.OnNewAction += SolverActionQueue.Enqueue; + SolverTask.Start(); } public void Dispose() { StopSolveMacro(); - SolverTask?.Wait(); + SolverTaskToken?.Cancel(); + SolverTask?.TryWait(); SolverTask?.Dispose(); SolverTaskToken?.Dispose(); } diff --git a/Solver/MCTSConfig.cs b/Solver/MCTSConfig.cs index 71ae268..c0d057d 100644 --- a/Solver/MCTSConfig.cs +++ b/Solver/MCTSConfig.cs @@ -21,7 +21,7 @@ public readonly record struct MCTSConfig public MCTSConfig(SolverConfig config) { - MaxStepCount= config.MaxStepCount; + MaxStepCount = config.MaxStepCount; MaxRolloutStepCount = config.MaxRolloutStepCount; StrictActions = config.StrictActions; @@ -29,10 +29,10 @@ public readonly record struct MCTSConfig ExplorationConstant = config.ExplorationConstant; ScoreStorageThreshold = config.ScoreStorageThreshold; - ScoreProgress = config.ScoreProgressBonus; - ScoreQuality = config.ScoreQualityBonus; - ScoreDurability = config.ScoreDurabilityBonus; - ScoreCP = config.ScoreCPBonus; - ScoreSteps = config.ScoreFewerStepsBonus; + ScoreProgress = config.ScoreProgress; + ScoreQuality = config.ScoreQuality; + ScoreDurability = config.ScoreDurability; + ScoreCP = config.ScoreCP; + ScoreSteps = config.ScoreSteps; } } diff --git a/Solver/Solver.cs b/Solver/Solver.cs index d4b324f..a34abe9 100644 --- a/Solver/Solver.cs +++ b/Solver/Solver.cs @@ -4,57 +4,165 @@ using System.Diagnostics; namespace Craftimizer.Solver; -public static class Solver +public sealed class Solver : IDisposable { - private static SolverSolution SearchStepwiseFurcated(SolverConfig config, SimulationState state, Action? actionCallback, CancellationToken token) + public SolverConfig Config { get; } + public SimulationState State { get; } + public CancellationToken Token { get; init; } + public SolverSolution? Solution { get; private set; } + + public bool IsStarted => CompletionTask != null; + public bool IsCompletedSuccessfully => Solution != null; + public bool IsCompleted => CompletionTask?.IsCompleted ?? false; + + private Func> SearchFunc { get; } + private MCTSConfig MCTSConfig => new(Config); + private Task? CompletionTask { get; set; } + + public delegate void LogDelegate(string text); + public delegate void WorkerProgressDelegate(SolverSolution solution, float score); + public delegate void NewActionDelegate(ActionType action); + public delegate void SolutionDelegate(SolverSolution solution); + + // Print to console or plugin log. + public event LogDelegate? OnLog; + + // Isn't always called. This is just meant to show as an indicator to the user. + // Solution contains the best terminal state, and its actions to get there exclude the ones provided by OnNewAction. + // For example, to get to the terminal state, execute all OnNewAction actions, then execute all Solution actions. + public event WorkerProgressDelegate? OnWorkerProgress; + + // Always called when a new step is generated. + public event NewActionDelegate? OnNewAction; + + // Always called when the solver is fully complete. + public event SolutionDelegate? OnSolution; + + public Solver(SolverConfig config, SimulationState state) + { + Config = config; + State = state; + + SearchFunc = Config.Algorithm switch + { + SolverAlgorithm.Oneshot => SearchOneshot, + SolverAlgorithm.OneshotForked => SearchOneshotForked, + SolverAlgorithm.Stepwise => SearchStepwise, + SolverAlgorithm.StepwiseForked => SearchStepwiseForked, + SolverAlgorithm.StepwiseFurcated => SearchStepwiseFurcated, + _ => throw new ArgumentOutOfRangeException(nameof(config), config, $"Invalid algorithm: {config.Algorithm}") + }; + } + + public void Start() + { + if (IsStarted) + throw new InvalidOperationException("Solver has already started."); + + CompletionTask = RunTask(); + } + + private async Task RunTask() + { + if (Token.IsCancellationRequested) + return; + + Solution = await SearchFunc().ConfigureAwait(false); + } + + public async Task GetTask() + { + if (!IsStarted) + throw new InvalidOperationException("Solver has not started."); + + await CompletionTask!.ConfigureAwait(false); + + return Solution!.Value; + } + + public async Task GetSafeTask() + { + try + { + return await GetTask().ConfigureAwait(false); + } + catch (AggregateException e) + { + e.Handle(ex => ex is OperationCanceledException); + } + catch (OperationCanceledException) + { + + } + return null; + } + + public void TryWait() + { + if (IsStarted && !IsCompleted) + GetSafeTask().Wait(); + } + + public void Dispose() + { + CompletionTask?.Dispose(); + } + + private async Task SearchStepwiseFurcated() { var definiteActionCount = 0; var bestSims = new List<(float Score, SolverSolution Result)>(); - var sim = new Simulator(state, config.MaxStepCount); + var state = State; + var sim = new Simulator(state, Config.MaxStepCount); var activeStates = new List() { new(new(), state) }; while (activeStates.Count != 0) { - if (token.IsCancellationRequested) + if (Token.IsCancellationRequested) break; var s = Stopwatch.StartNew(); - var tasks = new Task<(float MaxScore, int FurcatedActionIdx, SolverSolution Solution)>[config.ForkCount]; - for (var i = 0; i < config.ForkCount; i++) + var tasks = new Task<(float MaxScore, int FurcatedActionIdx, SolverSolution Solution)>[Config.ForkCount]; + for (var i = 0; i < Config.ForkCount; i++) { - var stateIdx = (int)((float)i / config.ForkCount * activeStates.Count); - var st = activeStates[stateIdx]; + var stateIdx = (int)((float)i / Config.ForkCount * activeStates.Count); tasks[i] = Task.Run(() => { - var solver = new MCTS(new(config), activeStates[stateIdx].State); - solver.Search(config.Iterations / config.ForkCount, token); - return (solver.MaxScore, stateIdx, solver.Solution()); - }, token); + var solver = new MCTS(MCTSConfig, activeStates[stateIdx].State); + solver.Search(Config.Iterations / Config.ForkCount, Token); + var solution = solver.Solution(); + var progressActions = activeStates[stateIdx].Actions.Concat(solution.Actions).Skip(definiteActionCount).ToList(); + OnWorkerProgress?.Invoke(solution with { Actions = progressActions }, solver.MaxScore); + return (solver.MaxScore, stateIdx, solution); + }, Token); } - Task.WaitAll(tasks, token); + await Task.WhenAll(tasks).WaitAsync(Token).ConfigureAwait(false); s.Stop(); + OnLog?.Invoke($"{s.Elapsed.TotalMilliseconds:0.00}ms {Config.Iterations / Config.ForkCount / s.Elapsed.TotalSeconds / 1000:0.00} kI/s/t"); - if (token.IsCancellationRequested) + if (Token.IsCancellationRequested) break; - var bestActions = tasks.Select(t => t.Result).OrderByDescending(r => r.MaxScore).Take(config.FurcatedActionCount).ToArray(); + var bestActions = tasks.Select(t => t.Result).OrderByDescending(r => r.MaxScore).Take(Config.FurcatedActionCount).ToArray(); var bestAction = bestActions[0]; - if (bestAction.MaxScore >= config.ScoreStorageThreshold) + if (bestAction.MaxScore >= Config.ScoreStorageThreshold) { - var (maxScore, furcatedActionIdx, solution) = bestAction; - var (activeActions, activeState) = activeStates[furcatedActionIdx]; + var (_, furcatedActionIdx, solution) = bestAction; + var (activeActions, _) = activeStates[furcatedActionIdx]; activeActions.AddRange(solution.Actions); + foreach (var action in activeActions.Skip(definiteActionCount)) + OnNewAction?.Invoke(action); return solution with { Actions = activeActions }; } - var newStates = new List(config.FurcatedActionCount); + var newStates = new List(Config.FurcatedActionCount); for (var i = 0; i < bestActions.Length; ++i) { - var (maxScore, furcatedActionIdx, (solutionActions, solutionNode)) = bestActions[i]; + var (maxScore, furcatedActionIdx, (solutionActions, _)) = bestActions[i]; if (solutionActions.Count == 0) continue; @@ -94,67 +202,69 @@ public static class Solver } if (definiteCount != equalCount) { - for (var i = definiteCount; i < equalCount; ++i) - actionCallback?.Invoke(refActions[i]); + foreach(var action in refActions.Take(equalCount).Skip(definiteCount)) + OnNewAction?.Invoke(action); definiteActionCount = equalCount; } } activeStates = newStates; - - Console.WriteLine($"{s.Elapsed.TotalMilliseconds:0.00}ms {config.Iterations / config.ForkCount / s.Elapsed.TotalSeconds / 1000:0.00} kI/s/t"); } if (bestSims.Count == 0) return new(new(), state); var result = bestSims.MaxBy(s => s.Score).Result; - for (var i = definiteActionCount; i < result.Actions.Count; ++i) - actionCallback?.Invoke(result.Actions[i]); + foreach (var action in result.Actions.Skip(definiteActionCount)) + OnNewAction?.Invoke(action); return result; } - private static SolverSolution SearchStepwiseForked(SolverConfig config, SimulationState state, Action? actionCallback, CancellationToken token) + private async Task SearchStepwiseForked() { var actions = new List(); - var sim = new Simulator(state, config.MaxStepCount); + var state = State; + var sim = new Simulator(state, Config.MaxStepCount); while (true) { - if (token.IsCancellationRequested) + if (Token.IsCancellationRequested) break; if (sim.IsComplete) break; - var s = Stopwatch.StartNew(); - var tasks = new Task<(float MaxScore, SolverSolution Solution)>[config.ForkCount]; - for (var i = 0; i < config.ForkCount; ++i) + var tasks = new Task<(float MaxScore, SolverSolution Solution)>[Config.ForkCount]; + for (var i = 0; i < Config.ForkCount; ++i) tasks[i] = Task.Run(() => { - var solver = new MCTS(new(config), state); - solver.Search(config.Iterations / config.ForkCount, token); - return (solver.MaxScore, solver.Solution()); - }, token); - Task.WaitAll(tasks, token); + var solver = new MCTS(MCTSConfig, state); + solver.Search(Config.Iterations / Config.ForkCount, Token); + var solution = solver.Solution(); + OnWorkerProgress?.Invoke(solution, solver.MaxScore); + return (solver.MaxScore, solution); + }, Token); + await Task.WhenAll(tasks).WaitAsync(Token).ConfigureAwait(false); s.Stop(); + OnLog?.Invoke($"{s.Elapsed.TotalMilliseconds:0.00}ms {Config.Iterations / Config.ForkCount / s.Elapsed.TotalSeconds / 1000:0.00} kI/s/t"); - if (token.IsCancellationRequested) + if (Token.IsCancellationRequested) break; var (maxScore, solution) = tasks.Select(t => t.Result).MaxBy(r => r.MaxScore); - if (maxScore >= config.ScoreStorageThreshold) + if (maxScore >= Config.ScoreStorageThreshold) { actions.AddRange(solution.Actions); + foreach (var action in solution.Actions) + OnNewAction?.Invoke(action); return solution with { Actions = actions }; } var chosenAction = solution.Actions[0]; - actionCallback?.Invoke(chosenAction); - Console.WriteLine($"{s.Elapsed.TotalMilliseconds:0.00}ms {config.Iterations / config.ForkCount / s.Elapsed.TotalSeconds / 1000:0.00} kI/s/t"); + OnNewAction?.Invoke(chosenAction); (_, state) = sim.Execute(state, chosenAction); actions.Add(chosenAction); @@ -163,84 +273,75 @@ public static class Solver return new(actions, state); } - private static SolverSolution SearchStepwise(SolverConfig config, SimulationState state, Action? actionCallback, CancellationToken token) + private Task SearchStepwise() { var actions = new List(); - var sim = new Simulator(state, config.MaxStepCount); + var state = State; + var sim = new Simulator(state, Config.MaxStepCount); while (true) { - if (token.IsCancellationRequested) + if (Token.IsCancellationRequested) break; if (sim.IsComplete) break; - var solver = new MCTS(new(config), state); + var solver = new MCTS(MCTSConfig, State); var s = Stopwatch.StartNew(); - solver.Search(config.Iterations, token); + solver.Search(Config.Iterations, Token); s.Stop(); + OnLog?.Invoke($"{s.Elapsed.TotalMilliseconds:0.00}ms {Config.Iterations / s.Elapsed.TotalSeconds / 1000:0.00} kI/s"); var solution = solver.Solution(); - if (solver.MaxScore >= config.ScoreStorageThreshold) + if (solver.MaxScore >= Config.ScoreStorageThreshold) { actions.AddRange(solution.Actions); - return solution with { Actions = actions }; + foreach (var action in solution.Actions) + OnNewAction?.Invoke(action); + return Task.FromResult(solution with { Actions = actions }); } var chosenAction = solution.Actions[0]; - actionCallback?.Invoke(chosenAction); - Console.WriteLine($"{s.Elapsed.TotalMilliseconds:0.00}ms {config.Iterations / s.Elapsed.TotalSeconds / 1000:0.00} kI/s"); + OnNewAction?.Invoke(chosenAction); (_, state) = sim.Execute(state, chosenAction); actions.Add(chosenAction); } - return new(actions, state); + return Task.FromResult(new SolverSolution(actions, state)); } - private static SolverSolution SearchOneshotForked(SolverConfig config, SimulationState state, Action? actionCallback, CancellationToken token) + private async Task SearchOneshotForked() { - var tasks = new Task<(float MaxScore, SolverSolution Solution)>[config.ForkCount]; - for (var i = 0; i < config.ForkCount; ++i) + var tasks = new Task<(float MaxScore, SolverSolution Solution)>[Config.ForkCount]; + for (var i = 0; i < Config.ForkCount; ++i) tasks[i] = Task.Run(() => { - var solver = new MCTS(new(config), state); - solver.Search(config.Iterations / config.ForkCount, token); - return (solver.MaxScore, solver.Solution()); - }, token); - Task.WaitAll(tasks, CancellationToken.None); + var solver = new MCTS(MCTSConfig, State); + solver.Search(Config.Iterations / Config.ForkCount, Token); + var solution = solver.Solution(); + OnWorkerProgress?.Invoke(solution, solver.MaxScore); + return (solver.MaxScore, solution); + }, Token); + await Task.WhenAll(tasks).WaitAsync(Token).ConfigureAwait(false); var solution = tasks.Select(t => t.Result).MaxBy(r => r.MaxScore).Solution; foreach (var action in solution.Actions) - actionCallback?.Invoke(action); + OnNewAction?.Invoke(action); return solution; } - private static SolverSolution SearchOneshot(SolverConfig config, SimulationState state, Action? actionCallback, CancellationToken token) + private Task SearchOneshot() { - var solver = new MCTS(new(config), state); - solver.Search(config.Iterations, token); + var solver = new MCTS(MCTSConfig, State); + solver.Search(Config.Iterations, Token); var solution = solver.Solution(); foreach (var action in solution.Actions) - actionCallback?.Invoke(action); + OnNewAction?.Invoke(action); - return solution; - } - - public static SolverSolution Search(SolverConfig config, SimulationState state, Action? actionCallback, CancellationToken token) - { - Func?, CancellationToken, SolverSolution> func = config.Algorithm switch - { - SolverAlgorithm.Oneshot => SearchOneshot, - SolverAlgorithm.OneshotForked => SearchOneshotForked, - SolverAlgorithm.Stepwise => SearchStepwise, - SolverAlgorithm.StepwiseForked => SearchStepwiseForked, - SolverAlgorithm.StepwiseFurcated => SearchStepwiseFurcated, - _ => throw new ArgumentOutOfRangeException(nameof(config), config, $"Invalid algorithm: {config.Algorithm}") - }; - return func(config, state, actionCallback, token); + return Task.FromResult(solution); } } diff --git a/Solver/SolverConfig.cs b/Solver/SolverConfig.cs index fffcff3..cbb7c5a 100644 --- a/Solver/SolverConfig.cs +++ b/Solver/SolverConfig.cs @@ -26,11 +26,11 @@ public readonly record struct SolverConfig public int FurcatedActionCount { get; init; } public bool StrictActions { get; init; } - public float ScoreProgressBonus { get; init; } - public float ScoreQualityBonus { get; init; } - public float ScoreDurabilityBonus { get; init; } - public float ScoreCPBonus { get; init; } - public float ScoreFewerStepsBonus { get; init; } + public float ScoreProgress { get; init; } + public float ScoreQuality { get; init; } + public float ScoreDurability { get; init; } + public float ScoreCP { get; init; } + public float ScoreSteps { get; init; } public SolverAlgorithm Algorithm { get; init; } @@ -46,11 +46,11 @@ public readonly record struct SolverConfig FurcatedActionCount = ForkCount / 2; StrictActions = true; - ScoreProgressBonus = .20f; - ScoreQualityBonus = .65f; - ScoreDurabilityBonus = .05f; - ScoreCPBonus = .05f; - ScoreFewerStepsBonus = .05f; + ScoreProgress = .20f; + ScoreQuality = .65f; + ScoreDurability = .05f; + ScoreCP = .05f; + ScoreSteps = .05f; Algorithm = SolverAlgorithm.StepwiseFurcated; } @@ -67,21 +67,4 @@ public readonly record struct SolverConfig FurcatedActionCount = Environment.ProcessorCount / 2, Algorithm = SolverAlgorithm.StepwiseForked }; - - public SolverSolution? Invoke(SimulationState state, Action? actionCallback = null, CancellationToken token = default) - { - try - { - return Solver.Search(this, state, actionCallback, token); - } - catch (AggregateException e) - { - e.Handle(ex => ex is OperationCanceledException); - } - catch (OperationCanceledException) - { - - } - return null; - } } \ No newline at end of file