Refactor background work in recipenote, suggest macro automatically
This commit is contained in:
@@ -92,6 +92,7 @@ public class Configuration : IPluginConfiguration
|
|||||||
public bool EnableSynthHelper { get; set; } = true;
|
public bool EnableSynthHelper { get; set; } = true;
|
||||||
public bool DisableSynthHelperOnMacro { get; set; } = true;
|
public bool DisableSynthHelperOnMacro { get; set; } = true;
|
||||||
public bool ShowOptimalMacroStat { get; set; } = true;
|
public bool ShowOptimalMacroStat { get; set; } = true;
|
||||||
|
public bool SuggestMacroAutomatically { get; set; }
|
||||||
public int SynthHelperStepCount { get; set; } = 5;
|
public int SynthHelperStepCount { get; set; } = 5;
|
||||||
|
|
||||||
public bool PinSynthHelperToWindow { get; set; } = true;
|
public bool PinSynthHelperToWindow { get; set; } = true;
|
||||||
|
|||||||
@@ -64,12 +64,58 @@ public sealed unsafe class RecipeNote : Window, IDisposable
|
|||||||
public CharacterStats? CharacterStats { get; private set; }
|
public CharacterStats? CharacterStats { get; private set; }
|
||||||
public CraftableStatus CraftStatus { get; private set; }
|
public CraftableStatus CraftStatus { get; private set; }
|
||||||
|
|
||||||
private CancellationTokenSource? BestMacroTokenSource { get; set; }
|
public sealed class BackgroundTask<T> : IDisposable where T : struct
|
||||||
private Exception? BestMacroException { get; set; }
|
{
|
||||||
|
public T? Result { get; private set; }
|
||||||
|
public Exception? Exception { get; private set; }
|
||||||
|
public bool Completed { get; private set; }
|
||||||
|
|
||||||
|
private CancellationTokenSource TokenSource { get; }
|
||||||
|
private Func<CancellationToken, T> Func { get; }
|
||||||
|
|
||||||
|
public BackgroundTask(Func<CancellationToken, T> func)
|
||||||
|
{
|
||||||
|
Func = func;
|
||||||
|
TokenSource = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Start()
|
||||||
|
{
|
||||||
|
var token = TokenSource.Token;
|
||||||
|
var task = Task.Run(() => Result = Func(token), token);
|
||||||
|
_ = task.ContinueWith(t =>
|
||||||
|
{
|
||||||
|
Completed = true;
|
||||||
|
});
|
||||||
|
_ = task.ContinueWith(t =>
|
||||||
|
{
|
||||||
|
if (token.IsCancellationRequested)
|
||||||
|
return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
t.Exception!.Flatten().Handle(ex => ex is TaskCanceledException or OperationCanceledException);
|
||||||
|
}
|
||||||
|
catch (AggregateException e)
|
||||||
|
{
|
||||||
|
Exception = e;
|
||||||
|
Log.Error(e, "Calculating macros failed");
|
||||||
|
}
|
||||||
|
}, TaskContinuationOptions.OnlyOnFaulted);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Cancel() =>
|
||||||
|
TokenSource.Cancel();
|
||||||
|
|
||||||
|
public void Dispose() =>
|
||||||
|
Cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
private BackgroundTask<(Macro?, SimulationState?)>? SavedMacroTask { get; set; }
|
||||||
|
private BackgroundTask<SolverSolution>? SuggestedMacroTask { get; set; }
|
||||||
|
|
||||||
private Solver.Solver? BestMacroSolver { get; set; }
|
private Solver.Solver? BestMacroSolver { get; set; }
|
||||||
public (Macro, SimulationState)? BestSavedMacro { get; private set; }
|
|
||||||
public bool HasSavedMacro { get; private set; }
|
public bool HasSavedMacro { get; private set; }
|
||||||
public SolverSolution? BestSuggestedMacro { get; private set; }
|
|
||||||
|
|
||||||
private IDalamudTextureWrap ExpertBadge { get; }
|
private IDalamudTextureWrap ExpertBadge { get; }
|
||||||
private IDalamudTextureWrap CollectibleBadge { get; }
|
private IDalamudTextureWrap CollectibleBadge { get; }
|
||||||
@@ -119,11 +165,21 @@ public sealed unsafe class RecipeNote : Window, IDisposable
|
|||||||
if (isOpen != wasOpen)
|
if (isOpen != wasOpen)
|
||||||
{
|
{
|
||||||
if (wasOpen)
|
if (wasOpen)
|
||||||
BestMacroTokenSource?.Cancel();
|
{
|
||||||
|
SavedMacroTask?.Cancel();
|
||||||
|
SuggestedMacroTask?.Cancel();
|
||||||
|
}
|
||||||
|
else if (CraftStatus == CraftableStatus.OK)
|
||||||
|
{
|
||||||
|
if (SavedMacroTask?.Result == null && (SavedMacroTask?.Completed ?? true))
|
||||||
|
CalculateSavedMacro();
|
||||||
|
if (Service.Configuration.SuggestMacroAutomatically && SuggestedMacroTask?.Result == null && (SuggestedMacroTask?.Completed ?? true))
|
||||||
|
CalculateSuggestedMacro();
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (!BestSuggestedMacro.HasValue && CraftStatus == CraftableStatus.OK && BestMacroTokenSource == null)
|
SuggestedMacroTask?.Cancel();
|
||||||
CalculateBestMacros();
|
SuggestedMacroTask = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,7 +253,16 @@ public sealed unsafe class RecipeNote : Window, IDisposable
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (StatsChanged && CraftStatus == CraftableStatus.OK)
|
if (StatsChanged && CraftStatus == CraftableStatus.OK)
|
||||||
CalculateBestMacros();
|
{
|
||||||
|
CalculateSavedMacro();
|
||||||
|
if (Service.Configuration.SuggestMacroAutomatically)
|
||||||
|
CalculateSuggestedMacro();
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SuggestedMacroTask?.Cancel();
|
||||||
|
SuggestedMacroTask = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -262,22 +327,22 @@ public sealed unsafe class RecipeNote : Window, IDisposable
|
|||||||
using (var panel = ImRaii2.GroupPanel("Best Saved Macro", panelWidth, out _))
|
using (var panel = ImRaii2.GroupPanel("Best Saved Macro", panelWidth, out _))
|
||||||
{
|
{
|
||||||
var stepsPanelWidthOffset = ImGui.GetContentRegionAvail().X - panelWidth;
|
var stepsPanelWidthOffset = ImGui.GetContentRegionAvail().X - panelWidth;
|
||||||
if (BestSavedMacro is { } savedMacro)
|
if (SavedMacroTask?.Result is { } savedMacro && savedMacro.Item1 != null && savedMacro.Item2 != null)
|
||||||
{
|
{
|
||||||
ImGuiUtils.TextCentered(savedMacro.Item1.Name, panelWidth);
|
ImGuiUtils.TextCentered(savedMacro.Item1.Name, panelWidth);
|
||||||
DrawMacro((savedMacro.Item1.Actions, savedMacro.Item2), null, a => { savedMacro.Item1.ActionEnumerable = a; Service.Configuration.Save(); }, stepsPanelWidthOffset, true);
|
DrawMacro((savedMacro.Item1.Actions, savedMacro.Item2.Value), SavedMacroTask.Exception, null, a => { savedMacro.Item1.ActionEnumerable = a; Service.Configuration.Save(); }, stepsPanelWidthOffset, true);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
DrawMacro(null, null, null, stepsPanelWidthOffset, true);
|
DrawMacro(null, SavedMacroTask?.Exception, null, null, stepsPanelWidthOffset, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
using (var panel = ImRaii2.GroupPanel("Suggested Macro", panelWidth, out _))
|
using (var panel = ImRaii2.GroupPanel("Suggested Macro", panelWidth, out _))
|
||||||
{
|
{
|
||||||
var stepsPanelWidthOffset = ImGui.GetContentRegionAvail().X - panelWidth;
|
var stepsPanelWidthOffset = ImGui.GetContentRegionAvail().X - panelWidth;
|
||||||
if (BestSuggestedMacro is { } suggestedMacro)
|
if (SuggestedMacroTask?.Result is { } suggestedMacro)
|
||||||
DrawMacro((suggestedMacro.Actions, suggestedMacro.State), null, null, stepsPanelWidthOffset, false);
|
DrawMacro((suggestedMacro.Actions, suggestedMacro.State), SuggestedMacroTask.Exception, null, null, stepsPanelWidthOffset, false);
|
||||||
else
|
else
|
||||||
DrawMacro(null, BestMacroSolver, null, stepsPanelWidthOffset, false);
|
DrawMacro(null, SuggestedMacroTask?.Exception, BestMacroSolver, null, stepsPanelWidthOffset, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGuiHelpers.ScaledDummy(5);
|
ImGuiHelpers.ScaledDummy(5);
|
||||||
@@ -582,7 +647,7 @@ public sealed unsafe class RecipeNote : Window, IDisposable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawMacro((IReadOnlyList<ActionType> Actions, SimulationState State)? macroValue, Solver.Solver? solver, Action<IEnumerable<ActionType>>? setter, float stepsAvailWidthOffset, bool isSavedMacro)
|
private void DrawMacro((IReadOnlyList<ActionType> Actions, SimulationState State)? macroValue, Exception? exception, Solver.Solver? solver, Action<IEnumerable<ActionType>>? setter, float stepsAvailWidthOffset, bool isSavedMacro)
|
||||||
{
|
{
|
||||||
var windowHeight = 2 * ImGui.GetFrameHeightWithSpacing();
|
var windowHeight = 2 * ImGui.GetFrameHeightWithSpacing();
|
||||||
|
|
||||||
@@ -590,15 +655,16 @@ public sealed unsafe class RecipeNote : Window, IDisposable
|
|||||||
{
|
{
|
||||||
if (isSavedMacro && !HasSavedMacro)
|
if (isSavedMacro && !HasSavedMacro)
|
||||||
ImGuiUtils.TextMiddleNewLine("You have no macros!", new(ImGui.GetContentRegionAvail().X - stepsAvailWidthOffset, windowHeight + 1));
|
ImGuiUtils.TextMiddleNewLine("You have no macros!", new(ImGui.GetContentRegionAvail().X - stepsAvailWidthOffset, windowHeight + 1));
|
||||||
else if (BestMacroException == null)
|
else if (exception == null)
|
||||||
{
|
{
|
||||||
if (solver != null)
|
if (solver != null && SuggestedMacroTask != null)
|
||||||
{
|
{
|
||||||
var calcTextSize = ImGui.CalcTextSize("Calculating...");
|
var calcTextSize = ImGui.CalcTextSize("Calculating...");
|
||||||
var spacing = ImGui.GetStyle().ItemSpacing.X;
|
var spacing = ImGui.GetStyle().ItemSpacing.X;
|
||||||
var fraction = Math.Clamp((float)solver.ProgressValue / solver.ProgressMax, 0, 1);
|
var fraction = Math.Clamp((float)solver.ProgressValue / solver.ProgressMax, 0, 1);
|
||||||
var progressColors = Colors.GetSolverProgressColors(solver.ProgressStage);
|
var progressColors = Colors.GetSolverProgressColors(solver.ProgressStage);
|
||||||
|
|
||||||
|
var c = ImGui.GetCursorPos();
|
||||||
ImGuiUtils.AlignCentered(windowHeight + spacing + calcTextSize.X, ImGui.GetContentRegionAvail().X - stepsAvailWidthOffset);
|
ImGuiUtils.AlignCentered(windowHeight + spacing + calcTextSize.X, ImGui.GetContentRegionAvail().X - stepsAvailWidthOffset);
|
||||||
|
|
||||||
ImGuiUtils.ArcProgress(
|
ImGuiUtils.ArcProgress(
|
||||||
@@ -614,9 +680,20 @@ public sealed unsafe class RecipeNote : Window, IDisposable
|
|||||||
|
|
||||||
ImGuiUtils.AlignMiddle(calcTextSize, new(calcTextSize.X, windowHeight));
|
ImGuiUtils.AlignMiddle(calcTextSize, new(calcTextSize.X, windowHeight));
|
||||||
ImGui.Text("Calculating...");
|
ImGui.Text("Calculating...");
|
||||||
|
ImGui.SetCursorPos(c + new Vector2(0, windowHeight + ImGui.GetStyle().ItemSpacing.Y - 1));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
ImGuiUtils.TextMiddleNewLine("Calculating...", new(ImGui.GetContentRegionAvail().X - stepsAvailWidthOffset, windowHeight + 1));
|
{
|
||||||
|
using var _padding = ImRaii.PushStyle(ImGuiStyleVar.FramePadding, ImGui.GetStyle().FramePadding * 2);
|
||||||
|
var size = ImGui.CalcTextSize("Generate") + ImGui.GetStyle().FramePadding * 2;
|
||||||
|
var c = ImGui.GetCursorPos();
|
||||||
|
var availSize = new Vector2(ImGui.GetContentRegionAvail().X - stepsAvailWidthOffset, windowHeight);
|
||||||
|
ImGuiUtils.AlignMiddle(size, availSize);
|
||||||
|
using var _disabled = ImRaii.Disabled(!(SuggestedMacroTask?.Completed) ?? false);
|
||||||
|
if (ImGui.Button("Generate"))
|
||||||
|
CalculateSuggestedMacro();
|
||||||
|
ImGui.SetCursorPos(c + new Vector2(0, availSize.Y + ImGui.GetStyle().ItemSpacing.Y - 1));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -624,7 +701,7 @@ public sealed unsafe class RecipeNote : Window, IDisposable
|
|||||||
using (var color = ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudRed))
|
using (var color = ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudRed))
|
||||||
ImGuiUtils.TextCentered("An exception occurred");
|
ImGuiUtils.TextCentered("An exception occurred");
|
||||||
if (ImGuiUtils.ButtonCentered("Copy Error Message"))
|
if (ImGuiUtils.ButtonCentered("Copy Error Message"))
|
||||||
ImGui.SetClipboardText(BestMacroException.ToString());
|
ImGui.SetClipboardText(exception.ToString());
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -856,41 +933,10 @@ public sealed unsafe class RecipeNote : Window, IDisposable
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CalculateBestMacros()
|
private void CalculateSavedMacro()
|
||||||
{
|
{
|
||||||
BestMacroTokenSource?.Cancel();
|
SavedMacroTask?.Cancel();
|
||||||
BestMacroTokenSource = new();
|
SavedMacroTask = new(token =>
|
||||||
BestMacroSolver = null;
|
|
||||||
BestMacroException = null;
|
|
||||||
BestSavedMacro = null;
|
|
||||||
HasSavedMacro = false;
|
|
||||||
BestSuggestedMacro = null;
|
|
||||||
|
|
||||||
var token = BestMacroTokenSource.Token;
|
|
||||||
var task = Task.Run(() => CalculateBestMacrosTask(token), token);
|
|
||||||
_ = task.ContinueWith(t =>
|
|
||||||
{
|
|
||||||
if (token == BestMacroTokenSource.Token)
|
|
||||||
BestMacroTokenSource = null;
|
|
||||||
});
|
|
||||||
_ = task.ContinueWith(t =>
|
|
||||||
{
|
|
||||||
if (token.IsCancellationRequested)
|
|
||||||
return;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
t.Exception!.Flatten().Handle(ex => ex is TaskCanceledException or OperationCanceledException);
|
|
||||||
}
|
|
||||||
catch (AggregateException e)
|
|
||||||
{
|
|
||||||
BestMacroException = e;
|
|
||||||
Log.Error(e, "Calculating macros failed");
|
|
||||||
}
|
|
||||||
}, TaskContinuationOptions.OnlyOnFaulted);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CalculateBestMacrosTask(CancellationToken token)
|
|
||||||
{
|
{
|
||||||
var input = new SimulationInput(CharacterStats!, RecipeData!.RecipeInfo);
|
var input = new SimulationInput(CharacterStats!, RecipeData!.RecipeInfo);
|
||||||
var state = new SimulationState(input);
|
var state = new SimulationState(input);
|
||||||
@@ -902,8 +948,8 @@ public sealed unsafe class RecipeNote : Window, IDisposable
|
|||||||
token.ThrowIfCancellationRequested();
|
token.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
HasSavedMacro = macros.Count > 0;
|
HasSavedMacro = macros.Count > 0;
|
||||||
if (HasSavedMacro)
|
if (!HasSavedMacro)
|
||||||
{
|
return (null, null);
|
||||||
var bestSaved = macros
|
var bestSaved = macros
|
||||||
.Select(macro =>
|
.Select(macro =>
|
||||||
{
|
{
|
||||||
@@ -921,10 +967,21 @@ public sealed unsafe class RecipeNote : Window, IDisposable
|
|||||||
|
|
||||||
token.ThrowIfCancellationRequested();
|
token.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
BestSavedMacro = (bestSaved.macro, bestSaved.outState);
|
return (bestSaved.macro, bestSaved.outState);
|
||||||
|
});
|
||||||
|
SavedMacroTask.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CalculateSuggestedMacro()
|
||||||
|
{
|
||||||
|
SuggestedMacroTask?.Cancel();
|
||||||
|
SuggestedMacroTask = new(token =>
|
||||||
|
{
|
||||||
|
var input = new SimulationInput(CharacterStats!, RecipeData!.RecipeInfo);
|
||||||
|
var state = new SimulationState(input);
|
||||||
|
var config = Service.Configuration.SimulatorSolverConfig;
|
||||||
|
|
||||||
token.ThrowIfCancellationRequested();
|
token.ThrowIfCancellationRequested();
|
||||||
}
|
|
||||||
|
|
||||||
var solver = new Solver.Solver(config, state) { Token = token };
|
var solver = new Solver.Solver(config, state) { Token = token };
|
||||||
solver.OnLog += Log.Debug;
|
solver.OnLog += Log.Debug;
|
||||||
@@ -934,13 +991,15 @@ public sealed unsafe class RecipeNote : Window, IDisposable
|
|||||||
|
|
||||||
token.ThrowIfCancellationRequested();
|
token.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
BestSuggestedMacro = solution;
|
return solution;
|
||||||
|
});
|
||||||
token.ThrowIfCancellationRequested();
|
SuggestedMacroTask.Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
|
SavedMacroTask?.Cancel();
|
||||||
|
SuggestedMacroTask?.Cancel();
|
||||||
Service.WindowSystem.RemoveWindow(this);
|
Service.WindowSystem.RemoveWindow(this);
|
||||||
AxisFont?.Dispose();
|
AxisFont?.Dispose();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -192,6 +192,18 @@ public sealed class Settings : Window, IDisposable
|
|||||||
ref isDirty
|
ref isDirty
|
||||||
);
|
);
|
||||||
|
|
||||||
|
DrawOption(
|
||||||
|
"Automatically Suggest Macro in Crafting Log",
|
||||||
|
"(Can cause frame drops!) When navigating to a new recipe or changing your gear " +
|
||||||
|
"stats, automatically suggest a new macro (equivalent to clicking \"Generate\" " +
|
||||||
|
"in the Macro Editor). This can cause harsh frame drops on some computers or " +
|
||||||
|
"recipes when underleveled while navigating the crafting log. Turning this off " +
|
||||||
|
"provides a button to allow you to manually suggest a macro only when you need it.",
|
||||||
|
Config.SuggestMacroAutomatically,
|
||||||
|
v => Config.SuggestMacroAutomatically = v,
|
||||||
|
ref isDirty
|
||||||
|
);
|
||||||
|
|
||||||
DrawOption(
|
DrawOption(
|
||||||
"Show Only One Macro Stat in Crafting Log",
|
"Show Only One Macro Stat in Crafting Log",
|
||||||
"Only one stat will be shown for a macro. If a craft will be finished, quality " +
|
"Only one stat will be shown for a macro. If a craft will be finished, quality " +
|
||||||
|
|||||||
Reference in New Issue
Block a user