Skip to content

Commit

Permalink
Add solver progress bar
Browse files Browse the repository at this point in the history
  • Loading branch information
WorkingRobot committed Nov 14, 2023
1 parent 12359ed commit 8f5f199
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 11 deletions.
19 changes: 18 additions & 1 deletion Craftimizer/Windows/MacroEditor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,8 @@ public SimulationReliablity GetReliability(in SimulationState initialState, IEnu
private int? SolverStartStepCount { get; set; }
private object? SolverQueueLock { get; set; }
private List<SimulatedActionStep>? SolverQueuedSteps { get; set; }
private int solverProgress;
private int maxSolverProgress;
private bool SolverRunning => SolverTokenSource != null;

private IDalamudTextureWrap ExpertBadge { get; }
Expand Down Expand Up @@ -1269,7 +1271,7 @@ private void DrawMacro()
ImGui.Dummy(new(0, imageSize));
ImGui.SameLine(0, 0);

var macroActionsHeight = ImGui.GetFrameHeightWithSpacing();
var macroActionsHeight = ImGui.GetFrameHeightWithSpacing() * (1 + (SolverRunning ? 1 : 0));
var childHeight = ImGui.GetContentRegionAvail().Y - ImGui.GetStyle().ItemSpacing.Y * 2 - ImGui.GetStyle().CellPadding.Y - macroActionsHeight - ImGui.GetStyle().ItemSpacing.Y * 2;

using (var child = ImRaii.Child("##macroActions", new(availSpace, childHeight)))
Expand Down Expand Up @@ -1308,6 +1310,19 @@ private void DrawMacro()
ImGui.Dummy(default);
ImGui.GetWindowDrawList().AddLine(pos, pos + new Vector2(availSpace, 0), ImGui.GetColorU32(ImGuiCol.Border));
ImGui.Dummy(default);
if (SolverRunning)
{
var percentWidth = ImGui.CalcTextSize("100%").X;
var progressWidth = availSpace - percentWidth - spacing;
var fraction = Math.Clamp((float)solverProgress / maxSolverProgress, 0, 1);
using (var color = ImRaii.PushColor(ImGuiCol.PlotHistogram, ImGuiColors.DalamudGrey3))
ImGui.ProgressBar(fraction, new(progressWidth, ImGui.GetFrameHeight()), string.Empty);
if (ImGui.IsItemHovered())
ImGui.SetTooltip($"Solver Progress: {solverProgress} / {maxSolverProgress}");
ImGui.SameLine(0, spacing);
ImGui.AlignTextToFramePadding();
ImGuiUtils.TextRight($"{fraction * 100:0}%", percentWidth);
}
DrawMacroActions(availSpace);
}

Expand Down Expand Up @@ -1602,6 +1617,7 @@ private void CalculateBestMacro()
}
SolverQueueLock = new();
SolverQueuedSteps ??= new();
solverProgress = 0;

RevertPreviousMacro();

Expand Down Expand Up @@ -1648,6 +1664,7 @@ private void CalculateBestMacroTask(SimulationState state, CancellationToken tok
var solver = new Solver.Solver(config, state) { Token = token };
solver.OnLog += Log.Debug;
solver.OnNewAction += QueueSolverStep;
solver.OnProgress += (s, p, m) => { Interlocked.Exchange(ref solverProgress, p); Interlocked.Exchange(ref maxSolverProgress, m); };
solver.Start();
_ = solver.GetTask().GetAwaiter().GetResult();

Expand Down
16 changes: 11 additions & 5 deletions Solver/MCTS.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ public sealed class MCTS
private readonly Node rootNode;
private readonly RootScores rootScores;

public const int ProgressUpdateFrequency = 1 << 10;
private const int StaleProgressThreshold = 1 << 12;

public float MaxScore => rootScores.MaxScore;

public MCTS(in MCTSConfig config, in SimulationState state)
Expand Down Expand Up @@ -278,11 +281,11 @@ static bool NodesIncomplete(Node node, Stack<Node> path)
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Search(int iterations, CancellationToken token)
public void Search(int iterations, CancellationToken token, Action? progressCallback)
{
Simulator simulator = new(config.MaxStepCount);
var random = rootNode.State.State.Input.Random;
var n = 0;
var staleCounter = 0;
for (var i = 0; i < iterations || MaxScore == 0; i++)
{
token.ThrowIfCancellationRequested();
Expand All @@ -293,9 +296,9 @@ public void Search(int iterations, CancellationToken token)
{
if (endNode == selectedNode)
{
if (n++ > 5000)
if (staleCounter++ >= StaleProgressThreshold)
{
n = 0;
staleCounter = 0;
if (AllNodesComplete())
{
//Console.WriteLine("All nodes solved for. Can't find a valid solution.");
Expand All @@ -305,10 +308,13 @@ public void Search(int iterations, CancellationToken token)
}
}
else
n = 0;
staleCounter = 0;
}

Backpropagate(endNode, score);

if ((i & (ProgressUpdateFrequency - 1)) == ProgressUpdateFrequency - 1)
progressCallback?.Invoke();
}
}

Expand Down
64 changes: 59 additions & 5 deletions Solver/Solver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,13 @@ public sealed class Solver : IDisposable
private MCTSConfig MCTSConfig => new(Config);
private Task? CompletionTask { get; set; }

private int progressSequence;
private int progress;
private int maxProgress;

public delegate void LogDelegate(string text);
public delegate void WorkerProgressDelegate(SolverSolution solution, float score);
public delegate void ProgressDelegate(int sequence, int value, int maxValue);
public delegate void NewActionDelegate(ActionType action);
public delegate void SolutionDelegate(SolverSolution solution);

Expand All @@ -32,6 +37,11 @@ public sealed class Solver : IDisposable
// For example, to get to the terminal state, execute all OnNewAction actions, then execute all Solution actions.
public event WorkerProgressDelegate? OnWorkerProgress;

// Always called in some form in every algorithm.
// In iterative algorithms, the sequence can increment and reset the value back to 0.
// In other algorithms, the sequence is always 0 and the value increases monotonically.
public event ProgressDelegate? OnProgress;

// Always called when a new step is generated.
public event NewActionDelegate? OnNewAction;

Expand Down Expand Up @@ -63,6 +73,7 @@ private async Task RunTask()
{
Token.ThrowIfCancellationRequested();

progressSequence = progress = 0;
Solution = await SearchFunc().ConfigureAwait(false);
}

Expand Down Expand Up @@ -110,8 +121,35 @@ private void InvokeNewAction(ActionType action)
OnNewAction?.Invoke(sanitizedAction);
}

private void IncrementProgress() =>
IncrementProgressBy(MCTS.ProgressUpdateFrequency);

private void IncrementRemainingProgress(int iterations) =>
IncrementProgressBy(iterations & MCTS.ProgressUpdateFrequency);

private void IncrementProgressBy(int value)
{
OnProgress?.Invoke(progressSequence, Interlocked.Add(ref progress, value), maxProgress);
}

private void IncrementProgressSequence()
{
Interlocked.Exchange(ref progress, 0);
progressSequence++;
OnProgress?.Invoke(progressSequence, 0, maxProgress);
}

private void SearchWithIncrement(MCTS mcts, int iterations)
{
mcts.Search(iterations, Token, IncrementProgress);
IncrementRemainingProgress(iterations);
}

private async Task<SolverSolution> SearchStepwiseFurcated()
{
var iterCount = Config.Iterations / Config.ForkCount;
maxProgress = iterCount * Config.ForkCount;

var definiteActionCount = 0;
var bestSims = new List<(float Score, SolverSolution Result)>();

Expand All @@ -136,7 +174,7 @@ private async Task<SolverSolution> SearchStepwiseFurcated()
await semaphore.WaitAsync(Token).ConfigureAwait(false);
try
{
solver.Search(Config.Iterations / Config.ForkCount, Token);
SearchWithIncrement(solver, iterCount);
}
finally
{
Expand Down Expand Up @@ -223,6 +261,8 @@ private async Task<SolverSolution> SearchStepwiseFurcated()
}
}

IncrementProgressSequence();

activeStates = newStates;
}

Expand All @@ -238,6 +278,9 @@ private async Task<SolverSolution> SearchStepwiseFurcated()

private async Task<SolverSolution> SearchStepwiseForked()
{
var iterCount = Config.Iterations / Config.ForkCount;
maxProgress = iterCount * Config.ForkCount;

var actions = new List<ActionType>();
var state = State;
var sim = new Simulator(Config.MaxStepCount) { State = state };
Expand All @@ -258,7 +301,7 @@ private async Task<SolverSolution> SearchStepwiseForked()
await semaphore.WaitAsync(Token).ConfigureAwait(false);
try
{
solver.Search(Config.Iterations / Config.ForkCount, Token);
SearchWithIncrement(solver, iterCount);
}
finally
{
Expand Down Expand Up @@ -294,13 +337,17 @@ private async Task<SolverSolution> SearchStepwiseForked()

(_, state) = sim.Execute(state, chosenAction);
actions.Add(chosenAction);

IncrementProgressSequence();
}

return new(actions, state);
}

private Task<SolverSolution> SearchStepwise()
{
maxProgress = Config.Iterations;

var actions = new List<ActionType>();
var state = State;
var sim = new Simulator(Config.MaxStepCount) { State = state };
Expand All @@ -314,7 +361,7 @@ private Task<SolverSolution> SearchStepwise()
var solver = new MCTS(MCTSConfig, state);

var s = Stopwatch.StartNew();
solver.Search(Config.Iterations, Token);
SearchWithIncrement(solver, Config.Iterations);
s.Stop();
OnLog?.Invoke($"{s.Elapsed.TotalMilliseconds:0.00}ms {Config.Iterations / s.Elapsed.TotalSeconds / 1000:0.00} kI/s");

Expand All @@ -333,13 +380,18 @@ private Task<SolverSolution> SearchStepwise()

(_, state) = sim.Execute(state, chosenAction);
actions.Add(chosenAction);

IncrementProgressSequence();
}

return Task.FromResult(new SolverSolution(actions, state));
}

private async Task<SolverSolution> SearchOneshotForked()
{
var iterCount = Config.Iterations / Config.ForkCount;
maxProgress = iterCount * Config.ForkCount;

using var semaphore = new SemaphoreSlim(0, Config.MaxThreadCount);
var tasks = new Task<(float MaxScore, SolverSolution Solution)>[Config.ForkCount];
for (var i = 0; i < Config.ForkCount; ++i)
Expand All @@ -349,7 +401,7 @@ private async Task<SolverSolution> SearchOneshotForked()
await semaphore.WaitAsync(Token).ConfigureAwait(false);
try
{
solver.Search(Config.Iterations / Config.ForkCount, Token);
SearchWithIncrement(solver, iterCount);
}
finally
{
Expand All @@ -375,8 +427,10 @@ private async Task<SolverSolution> SearchOneshotForked()

private Task<SolverSolution> SearchOneshot()
{
maxProgress = Config.Iterations;

var solver = new MCTS(MCTSConfig, State);
solver.Search(Config.Iterations, Token);
SearchWithIncrement(solver, Config.Iterations);
var solution = solver.Solution();
foreach (var action in solution.Actions)
InvokeNewAction(action);
Expand Down

1 comment on commit 8f5f199

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Benchmark

Benchmark suite Current: 8f5f199 Previous: a8c3f34 Ratio
Craftimizer.Benchmark.Bench.Solve(State: 5372D31C98FA4C357F54029912394B0F5ECBE94AEC9D12C1C2B7F453C62ACD2F, Config: E1C895829E5BB1533C41D3C71AD65B6B3E1A60B5EE2D35A845744C251E71EFA5) 2192592866.6666665 ns (± 16269635.632704021)
Craftimizer.Benchmark.Bench.Solve(State: 5372D31C98FA4C357F54029912394B0F5ECBE94AEC9D12C1C2B7F453C62ACD2F, Config: E1C895829E5BB1533C41D3C71AD65B6B3E1A60B5EE2D35A845744C251E71EFA5) 1772084173.3333333 ns (± 21049311.336426415)
Craftimizer.Benchmark.Bench.Solve(State: 99B0F1AD46A18B4D8262F9BA75ABE23507217C2F20FBF895A49282DDFEF50190, Config: E1C895829E5BB1533C41D3C71AD65B6B3E1A60B5EE2D35A845744C251E71EFA5) 2241867380 ns (± 21476215.832011536)
Craftimizer.Benchmark.Bench.Solve(State: 99B0F1AD46A18B4D8262F9BA75ABE23507217C2F20FBF895A49282DDFEF50190, Config: E1C895829E5BB1533C41D3C71AD65B6B3E1A60B5EE2D35A845744C251E71EFA5) 1732867285.7142856 ns (± 12573552.650204815)

This comment was automatically generated by workflow using github-action-benchmark.

Please sign in to comment.