From 44700abd4ef6b29175ecf3c386f6944bfffbcf1a Mon Sep 17 00:00:00 2001 From: Asriel Camora Date: Sun, 12 Nov 2023 21:23:15 +0000 Subject: [PATCH 01/15] Reliability Simulation --- Craftimizer/Configuration.cs | 1 + Craftimizer/Windows/MacroEditor.cs | 139 ++++++++++++++++++++++++++--- 2 files changed, 127 insertions(+), 13 deletions(-) diff --git a/Craftimizer/Configuration.cs b/Craftimizer/Configuration.cs index d88d579..7eda6ce 100644 --- a/Craftimizer/Configuration.cs +++ b/Craftimizer/Configuration.cs @@ -82,6 +82,7 @@ public class Configuration : IPluginConfiguration private List macros { get; set; } = new(); [JsonIgnore] public IReadOnlyList Macros => macros; + public int ReliabilitySimulationCount { get; set; } = 500; public bool ConditionRandomness { get; set; } = true; public SolverConfig SimulatorSolverConfig { get; set; } = SolverConfig.SimulatorDefault; public SolverConfig SynthHelperSolverConfig { get; set; } = SolverConfig.SynthHelperDefault; diff --git a/Craftimizer/Windows/MacroEditor.cs b/Craftimizer/Windows/MacroEditor.cs index 0114ec7..5bfbd3d 100644 --- a/Craftimizer/Windows/MacroEditor.cs +++ b/Craftimizer/Windows/MacroEditor.cs @@ -21,6 +21,7 @@ using System.Numerics; using System.Text; using System.Threading; using System.Threading.Tasks; +using RandomSim = Craftimizer.Simulator.Simulator; using Sim = Craftimizer.Simulator.SimulatorNoRandom; namespace Craftimizer.Windows; @@ -76,16 +77,77 @@ public sealed class MacroEditor : Window, IDisposable private List HQIngredientCounts { get; set; } private int StartingQuality => RecipeData.CalculateStartingQuality(HQIngredientCounts); + private readonly record struct SimulationReliablity + { + public record struct ParamReliability + { + public int Max { get; private set; } + public int Min { get; private set; } + public float Average { get; private set; } + + public ParamReliability() + { + Min = int.MaxValue; + } + + public void Add(int value) + { + Max = Math.Max(Max, value); + Min = Math.Min(Min, value); + Average += value; + } + + public void Finalize(int count) + { + if (count != 0) + Average /= count; + } + } + + public readonly ParamReliability Progress = new(); + public readonly ParamReliability Quality = new(); + + // Param is either collectability, quality, or hq%, depending on the recipe + public readonly ParamReliability Param = new(); + + public SimulationReliablity(in SimulationState startState, IEnumerable actions, int iterCount, RecipeData recipeData) + { + Func getParam; + if (recipeData.Recipe.ItemResult.Value!.IsCollectable) + getParam = s => s.Collectability; + else if (recipeData.Recipe.RequiredQuality > 0) + getParam = s => s.Quality; + else if (recipeData.RecipeInfo.MaxQuality > 0) + getParam = s => s.HQPercent; + else + getParam = s => 0; + + for (var i = 0; i < iterCount; ++i) + { + var sim = new RandomSim(startState); + var (_, state, _) = sim.ExecuteMultiple(startState, actions); + Progress.Add(state.Progress); + Quality.Add(state.Quality); + Param.Add(getParam(state)); + } + Progress.Finalize(iterCount); + Quality.Finalize(iterCount); + Param.Finalize(iterCount); + } + } + private sealed record SimulatedActionStep { public ActionType Action { get; init; } // State *after* executing the action public ActionResponse Response { get; set; } public SimulationState State { get; set; } + public SimulationReliablity Reliability { get; set; } }; private List Macro { get; set; } = new(); private SimulationState InitialState { get; set; } private SimulationState State => Macro.Count > 0 ? Macro[^1].State : InitialState; + private SimulationReliablity Reliability => Macro.Count > 0 ? Macro[^1].Reliability : new(InitialState, Array.Empty(), 0, RecipeData); private ActionType[] DefaultActions { get; } private Action>? MacroSetter { get; set; } @@ -971,23 +1033,23 @@ public sealed class MacroEditor : Window, IDisposable ImGui.TableNextColumn(); var datas = new List(3) { - new("Durability", Colors.Durability, State.Durability, RecipeData.RecipeInfo.MaxDurability, null, null), - new("Condition", default, 0, 0, null, State.Condition) + new("Durability", Colors.Durability, null, State.Durability, RecipeData.RecipeInfo.MaxDurability, null, null), + new("Condition", default, null, 0, 0, null, State.Condition) }; if (RecipeData.Recipe.ItemResult.Value!.IsCollectable) - datas.Add(new("Collectability", Colors.HQ, State.Collectability, State.MaxCollectability, $"{State.Collectability}", null)); + datas.Add(new("Collectability", Colors.HQ, Reliability.Param, State.Collectability, State.MaxCollectability, $"{State.Collectability}", null)); else if (RecipeData.Recipe.RequiredQuality > 0) - datas.Add(new("Quality %", Colors.HQ, State.Quality, RecipeData.Recipe.RequiredQuality, $"{(float)State.Quality / RecipeData.Recipe.RequiredQuality * 100:0}%", null)); + datas.Add(new("Quality %", Colors.HQ, Reliability.Param, State.Quality, RecipeData.Recipe.RequiredQuality, $"{(float)State.Quality / RecipeData.Recipe.RequiredQuality * 100:0}%", null)); else if (RecipeData.RecipeInfo.MaxQuality > 0) - datas.Add(new("HQ %", Colors.HQ, State.HQPercent, 100, $"{State.HQPercent}%", null)); + datas.Add(new("HQ %", Colors.HQ, Reliability.Param, State.HQPercent, 100, $"{State.HQPercent}%", null)); DrawBars(datas); ImGui.TableNextColumn(); datas = new List(3) { - new("Progress", Colors.Progress, State.Progress, RecipeData.RecipeInfo.MaxProgress, null, null), - new("Quality", Colors.Quality, State.Quality, RecipeData.RecipeInfo.MaxQuality, null, null), - new("CP", Colors.CP, State.CP, CharacterStats.CP, null, null) + new("Progress", Colors.Progress, Reliability.Progress, State.Progress, RecipeData.RecipeInfo.MaxProgress, null, null), + new("Quality", Colors.Quality, Reliability.Quality, State.Quality, RecipeData.RecipeInfo.MaxQuality, null, null), + new("CP", Colors.CP, null, State.CP, CharacterStats.CP, null, null) }; if (RecipeData.RecipeInfo.MaxQuality <= 0) datas.RemoveAt(1); @@ -1034,7 +1096,7 @@ public sealed class MacroEditor : Window, IDisposable } } - private readonly record struct BarData(string Name, Vector4 Color, float Value, float Max, string? Caption, Condition? Condition); + private readonly record struct BarData(string Name, Vector4 Color, SimulationReliablity.ParamReliability? Reliability, float Value, float Max, string? Caption, Condition? Condition); private void DrawBars(IEnumerable bars) { var spacing = ImGui.GetStyle().ItemSpacing.X; @@ -1074,8 +1136,46 @@ public sealed class MacroEditor : Window, IDisposable } else { - using (var color = ImRaii.PushColor(ImGuiCol.PlotHistogram, bar.Color)) + if (bar.Reliability is { } reliability) + { + // blend rgb colors, assume alpha is always 1 + static Vector4 BlendColor(Vector4 A, Vector4 B) + { + return new( + A.X * (1 - B.W) + B.X * B.W, + A.Y * (1 - B.W) + B.Y * B.W, + A.Z * (1 - B.W) + B.Z * B.W, + 1); + } + var relBars = new (float Value, Vector4 Color)[] + { + (bar.Value, bar.Color), + (reliability.Average, BlendColor(bar.Color, new(.5f,.5f,.5f,.6f))), + (reliability.Min, BlendColor(bar.Color, new(0,0,0,.6f))), + (reliability.Max, BlendColor(bar.Color, new(1,1,1,.6f))), + }.DistinctBy(v => Math.Clamp(v.Value, 0, bar.Max)).OrderByDescending(v => v.Value); + var i = 0; + var pos = ImGui.GetCursorPos(); + foreach (var relBar in relBars) + { + ImGui.SetCursorPos(pos); + { + using var frameColor = ImRaii.PushColor(ImGuiCol.FrameBg, Vector4.Zero, i != 0); + using var color = ImRaii.PushColor(ImGuiCol.PlotHistogram, relBar.Color); + ImGui.ProgressBar(Math.Clamp(relBar.Value / bar.Max, 0, 1), new(barSize, ImGui.GetFrameHeight()), string.Empty); + } + if (i++ == 0) + { + if (ImGui.IsItemHovered()) + ImGui.SetTooltip($"Min: {reliability.Min}\nAvg: {reliability.Average:0.##}\nMax: {reliability.Max}"); + } + } + } + else + { + using var color = ImRaii.PushColor(ImGuiCol.PlotHistogram, bar.Color); ImGui.ProgressBar(Math.Clamp(bar.Value / bar.Max, 0, 1), new(barSize, ImGui.GetFrameHeight()), string.Empty); + } ImGui.SameLine(0, spacing); ImGui.AlignTextToFramePadding(); if (bar.Caption is { } caption) @@ -1405,7 +1505,7 @@ public sealed class MacroEditor : Window, IDisposable if (popupImportUrlMacro is { Name: var name, Actions: var actions }) { Macro.Clear(); - foreach(var action in actions) + foreach (var action in actions) AddStep(action); Service.PluginInterface.UiBuilder.AddNotification($"Imported macro \"{name}\"", "Craftimizer Macro Imported", NotificationType.Success); @@ -1420,6 +1520,7 @@ public sealed class MacroEditor : Window, IDisposable popupImportUrlTokenSource = null; } } + private void CalculateBestMacro() { SolverTokenSource?.Cancel(); @@ -1485,8 +1586,11 @@ public sealed class MacroEditor : Window, IDisposable InitialState = new SimulationState(new(CharacterStats, RecipeData.RecipeInfo, StartingQuality)); var sim = new Sim(InitialState); var lastState = InitialState; - foreach (var step in Macro) - lastState = ((step.Response, step.State) = sim.Execute(lastState, step.Action)).State; + for (var i = 0; i < Macro.Count; i++) + { + lastState = ((Macro[i].Response, Macro[i].State) = sim.Execute(lastState, Macro[i].Action)).State; + Macro[i].Reliability = new(InitialState, Macro.Take(i + 1).Select(s => s.Action), Service.Configuration.ReliabilitySimulationCount, RecipeData); + } } private void AddStep(ActionType action, int index = -1, bool isSolver = false) @@ -1503,6 +1607,7 @@ public sealed class MacroEditor : Window, IDisposable var sim = new Sim(State); var resp = sim.Execute(State, action); Macro.Add(new() { Action = action, Response = resp.Response, State = resp.NewState }); + Macro[^1].Reliability = new(InitialState, Macro.Select(s => s.Action), Service.Configuration.ReliabilitySimulationCount, RecipeData); } else { @@ -1510,9 +1615,14 @@ public sealed class MacroEditor : Window, IDisposable var sim = new Sim(state); var resp = sim.Execute(state, action); Macro.Insert(index, new() { Action = action, Response = resp.Response, State = resp.NewState }); + Macro[index].Reliability = new(InitialState, Macro.Take(index + 1).Select(s => s.Action), Service.Configuration.ReliabilitySimulationCount, RecipeData); + state = resp.NewState; for (var i = index + 1; i < Macro.Count; i++) + { state = ((Macro[i].Response, Macro[i].State) = sim.Execute(state, Macro[i].Action)).State; + Macro[i].Reliability = new(InitialState, Macro.Take(i + 1).Select(s => s.Action), Service.Configuration.ReliabilitySimulationCount, RecipeData); + } } } @@ -1529,7 +1639,10 @@ public sealed class MacroEditor : Window, IDisposable var state = index == 0 ? InitialState : Macro[index - 1].State; var sim = new Sim(state); for (var i = index; i < Macro.Count; i++) + { state = ((Macro[i].Response, Macro[i].State) = sim.Execute(state, Macro[i].Action)).State; + Macro[i].Reliability = new(InitialState, Macro.Take(i + 1).Select(s => s.Action), Service.Configuration.ReliabilitySimulationCount, RecipeData); + } } public void Dispose() From f63483c9bd0a93190d7d568065dfc1b8d33c2ed7 Mon Sep 17 00:00:00 2001 From: Asriel Camora Date: Sun, 12 Nov 2023 18:25:48 -0800 Subject: [PATCH 02/15] Refactor reliability --- Craftimizer/Windows/MacroEditor.cs | 57 ++++++++++++++++-------------- 1 file changed, 31 insertions(+), 26 deletions(-) diff --git a/Craftimizer/Windows/MacroEditor.cs b/Craftimizer/Windows/MacroEditor.cs index 5bfbd3d..1e2380e 100644 --- a/Craftimizer/Windows/MacroEditor.cs +++ b/Craftimizer/Windows/MacroEditor.cs @@ -99,7 +99,9 @@ public sealed class MacroEditor : Window, IDisposable public void Finalize(int count) { - if (count != 0) + if (count == 0) + Average = Max = Min = 0; + else Average /= count; } } @@ -138,16 +140,33 @@ public sealed class MacroEditor : Window, IDisposable private sealed record SimulatedActionStep { - public ActionType Action { get; init; } + public ActionType Action { get; } // State *after* executing the action - public ActionResponse Response { get; set; } - public SimulationState State { get; set; } - public SimulationReliablity Reliability { get; set; } + public ActionResponse Response { get; private set; } + public SimulationState State { get; private set; } + private SimulationReliablity? Reliability { get; set; } + + public SimulatedActionStep(ActionType action, Sim sim, in SimulationState lastState, out SimulationState newState) + { + Action = action; + newState = Recalculate(sim, lastState); + } + + public SimulationState Recalculate(Sim sim, in SimulationState lastState) + { + (Response, State) = sim.Execute(lastState, Action); + Reliability = null; + return State; + } + + public SimulationReliablity GetReliability(in SimulationState initialState, IEnumerable actionSet, RecipeData recipeData) => + Reliability ??= new(initialState, actionSet, Service.Configuration.ReliabilitySimulationCount, recipeData); }; + private List Macro { get; set; } = new(); private SimulationState InitialState { get; set; } private SimulationState State => Macro.Count > 0 ? Macro[^1].State : InitialState; - private SimulationReliablity Reliability => Macro.Count > 0 ? Macro[^1].Reliability : new(InitialState, Array.Empty(), 0, RecipeData); + private SimulationReliablity Reliability => Macro.Count > 0 ? Macro[^1].GetReliability(InitialState, Macro.Select(m => m.Action), RecipeData) : new(InitialState, Array.Empty(), 0, RecipeData); private ActionType[] DefaultActions { get; } private Action>? MacroSetter { get; set; } @@ -1587,10 +1606,7 @@ public sealed class MacroEditor : Window, IDisposable var sim = new Sim(InitialState); var lastState = InitialState; for (var i = 0; i < Macro.Count; i++) - { - lastState = ((Macro[i].Response, Macro[i].State) = sim.Execute(lastState, Macro[i].Action)).State; - Macro[i].Reliability = new(InitialState, Macro.Take(i + 1).Select(s => s.Action), Service.Configuration.ReliabilitySimulationCount, RecipeData); - } + lastState = Macro[i].Recalculate(sim, lastState); } private void AddStep(ActionType action, int index = -1, bool isSolver = false) @@ -1605,24 +1621,16 @@ public sealed class MacroEditor : Window, IDisposable if (index == -1) { var sim = new Sim(State); - var resp = sim.Execute(State, action); - Macro.Add(new() { Action = action, Response = resp.Response, State = resp.NewState }); - Macro[^1].Reliability = new(InitialState, Macro.Select(s => s.Action), Service.Configuration.ReliabilitySimulationCount, RecipeData); + Macro.Add(new(action, sim, State, out _)); } else { var state = index == 0 ? InitialState : Macro[index - 1].State; var sim = new Sim(state); - var resp = sim.Execute(state, action); - Macro.Insert(index, new() { Action = action, Response = resp.Response, State = resp.NewState }); - Macro[index].Reliability = new(InitialState, Macro.Take(index + 1).Select(s => s.Action), Service.Configuration.ReliabilitySimulationCount, RecipeData); - - state = resp.NewState; + Macro.Insert(index, new(action, sim, state, out state)); + for (var i = index + 1; i < Macro.Count; i++) - { - state = ((Macro[i].Response, Macro[i].State) = sim.Execute(state, Macro[i].Action)).State; - Macro[i].Reliability = new(InitialState, Macro.Take(i + 1).Select(s => s.Action), Service.Configuration.ReliabilitySimulationCount, RecipeData); - } + state = Macro[i].Recalculate(sim, state); } } @@ -1639,10 +1647,7 @@ public sealed class MacroEditor : Window, IDisposable var state = index == 0 ? InitialState : Macro[index - 1].State; var sim = new Sim(state); for (var i = index; i < Macro.Count; i++) - { - state = ((Macro[i].Response, Macro[i].State) = sim.Execute(state, Macro[i].Action)).State; - Macro[i].Reliability = new(InitialState, Macro.Take(i + 1).Select(s => s.Action), Service.Configuration.ReliabilitySimulationCount, RecipeData); - } + state = Macro[i].Recalculate(sim, state); } public void Dispose() From bdce750238b2e4383821678cc545e92db946ca41 Mon Sep 17 00:00:00 2001 From: Asriel Camora Date: Mon, 13 Nov 2023 02:35:26 -0800 Subject: [PATCH 03/15] Add violin plots for reliability And move custom imraii stuff to imraii2 --- Craftimizer/Craftimizer.csproj | 1 + Craftimizer/ImGuiExtras.cs | 2 +- Craftimizer/ImGuiUtils.cs | 102 ++++++++++++++++------- Craftimizer/ImRaii2.cs | 92 +++++++++++++++++++++ Craftimizer/Windows/MacroClipboard.cs | 2 +- Craftimizer/Windows/MacroEditor.cs | 114 +++++++++++++------------- Craftimizer/Windows/MacroList.cs | 2 +- Craftimizer/Windows/RecipeNote.cs | 4 +- Craftimizer/Windows/Settings.cs | 22 +++-- Craftimizer/packages.lock.json | 6 ++ 10 files changed, 248 insertions(+), 99 deletions(-) create mode 100644 Craftimizer/ImRaii2.cs diff --git a/Craftimizer/Craftimizer.csproj b/Craftimizer/Craftimizer.csproj index aa4cd9f..94acb1e 100644 --- a/Craftimizer/Craftimizer.csproj +++ b/Craftimizer/Craftimizer.csproj @@ -39,6 +39,7 @@ + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Craftimizer/ImGuiExtras.cs b/Craftimizer/ImGuiExtras.cs index bc60e1a..14adb76 100644 --- a/Craftimizer/ImGuiExtras.cs +++ b/Craftimizer/ImGuiExtras.cs @@ -5,7 +5,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; -namespace Craftimizer; +namespace Craftimizer.Plugin; internal static unsafe class ImGuiExtras { diff --git a/Craftimizer/ImGuiUtils.cs b/Craftimizer/ImGuiUtils.cs index 68b83f1..7f223c9 100644 --- a/Craftimizer/ImGuiUtils.cs +++ b/Craftimizer/ImGuiUtils.cs @@ -2,12 +2,16 @@ using Craftimizer.Utils; using Dalamud.Interface; using Dalamud.Interface.Utility.Raii; using ImGuiNET; +using ImPlotNET; +using MathNet.Numerics.Statistics; using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Linq; using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; @@ -130,37 +134,6 @@ internal static class ImGuiUtils ImGui.PopID(); } - private struct EndUnconditionally : ImRaii.IEndObject, IDisposable - { - private Action EndAction { get; } - - public bool Success { get; } - - public bool Disposed { get; private set; } - - public EndUnconditionally(Action endAction, bool success) - { - EndAction = endAction; - Success = success; - Disposed = false; - } - - public void Dispose() - { - if (!Disposed) - { - EndAction(); - Disposed = true; - } - } - } - - public static ImRaii.IEndObject GroupPanel(string name, float width, out float internalWidth) - { - internalWidth = BeginGroupPanel(name, width); - return new EndUnconditionally(EndGroupPanel, true); - } - private static Vector2 UnitCircle(float theta) { var (s, c) = MathF.SinCos(theta); @@ -168,6 +141,7 @@ internal static class ImGuiUtils return new Vector2(c, -s); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static float Lerp(float a, float b, float t) => MathF.FusedMultiplyAdd(b - a, t, a); @@ -251,6 +225,72 @@ internal static class ImGuiUtils Arc(MathF.PI / 2, MathF.PI / 2 - MathF.Tau * Math.Clamp(value, 0, 1), radiusInner, radiusOuter, backgroundColor, filledColor); } + public sealed class ViolinData + { + [StructLayout(LayoutKind.Sequential)] + public struct Point + { + public float X, Y, Y2; + + public Point(float x, float y, float y2) + { + X = x; + Y = y; + Y2 = y2; + } + } + + public ReadOnlySpan Data => (DataArray ?? Array.Empty()).AsSpan(); + private Point[]? DataArray { get; set; } + public readonly float Min; + public readonly float Max; + + public ViolinData(IEnumerable samples, float min, float max, int resolution, double bandwidth) + { + Min = min; + Max = max; + bandwidth *= Max - Min; + var samplesList = samples.AsParallel().Select(s => (double)s).ToArray(); + _ = Task.Run(() => { + var s = Stopwatch.StartNew(); + var data = ParallelEnumerable.Range(0, resolution) + .Select(n => Lerp(min, max, n / (float)resolution)) + .Select(n => (n, (float)KernelDensity.EstimateGaussian(n, bandwidth, samplesList))) + .Select(n => new Point(n.n, n.Item2, -n.Item2)); + DataArray = data.ToArray(); + s.Stop(); + Log.Debug($"Violin plot processing took {s.Elapsed.TotalMilliseconds:0.00}ms"); + }); + } + } + + public static void ViolinPlot(in ViolinData data, Vector2 size) + { + using var padding = ImRaii2.PushStyle(ImPlotStyleVar.PlotPadding, Vector2.Zero); + using var plotBg = ImRaii2.PushColor(ImPlotCol.PlotBg, Vector4.Zero); + using var frameBg = ImRaii2.PushColor(ImPlotCol.FrameBg, Vector4.Zero); + using var fill = ImRaii2.PushColor(ImPlotCol.Fill, Vector4.One.WithAlpha(.5f)); + + using var plot = ImRaii2.Plot("##violin", size, ImPlotFlags.CanvasOnly); + if (plot) + { + ImPlot.SetupAxes(null, null, ImPlotAxisFlags.NoDecorations, ImPlotAxisFlags.NoDecorations | ImPlotAxisFlags.AutoFit); + ImPlot.SetupAxisLimits(ImAxis.X1, data.Min, data.Max, ImPlotCond.Always); + ImPlot.SetupFinish(); + + if (data.Data is { } points && !points.IsEmpty) + { + unsafe { + var label_id = stackalloc byte[] { (byte)'\0' }; + fixed (ViolinData.Point* p = points) + { + ImPlotNative.ImPlot_PlotShaded_FloatPtrFloatPtrFloatPtr(label_id, &p->X, &p->Y, &p->Y2, points.Length, ImPlotShadedFlags.None, 0, sizeof(ViolinData.Point)); + } + } + } + } + } + private sealed class SearchableComboData where T : class { public readonly ImmutableArray items; diff --git a/Craftimizer/ImRaii2.cs b/Craftimizer/ImRaii2.cs new file mode 100644 index 0000000..f2499e8 --- /dev/null +++ b/Craftimizer/ImRaii2.cs @@ -0,0 +1,92 @@ +using Dalamud.Interface.Utility.Raii; +using ImPlotNET; +using System; +using System.Numerics; + +namespace Craftimizer.Plugin; + +public static class ImRaii2 +{ + private struct EndUnconditionally : ImRaii.IEndObject, IDisposable + { + private Action EndAction { get; } + + public bool Success { get; } + + public bool Disposed { get; private set; } + + public EndUnconditionally(Action endAction, bool success) + { + EndAction = endAction; + Success = success; + Disposed = false; + } + + public void Dispose() + { + if (!Disposed) + { + EndAction(); + Disposed = true; + } + } + } + + private struct EndConditionally : ImRaii.IEndObject, IDisposable + { + public bool Success { get; } + + public bool Disposed { get; private set; } + + private Action EndAction { get; } + + public EndConditionally(Action endAction, bool success) + { + EndAction = endAction; + Success = success; + Disposed = false; + } + + public void Dispose() + { + if (!Disposed) + { + if (Success) + { + EndAction(); + } + + Disposed = true; + } + } + } + + public static ImRaii.IEndObject GroupPanel(string name, float width, out float internalWidth) + { + internalWidth = ImGuiUtils.BeginGroupPanel(name, width); + return new EndUnconditionally(ImGuiUtils.EndGroupPanel, true); + } + + public static ImRaii.IEndObject Plot(string title_id, Vector2 size, ImPlotFlags flags) + { + return new EndConditionally(new Action(ImPlot.EndPlot), ImPlot.BeginPlot(title_id, size, flags)); + } + + public static ImRaii.IEndObject PushStyle(ImPlotStyleVar idx, Vector2 val) + { + ImPlot.PushStyleVar(idx, val); + return new EndUnconditionally(ImPlot.PopStyleVar, true); + } + + public static ImRaii.IEndObject PushStyle(ImPlotStyleVar idx, float val) + { + ImPlot.PushStyleVar(idx, val); + return new EndUnconditionally(ImPlot.PopStyleVar, true); + } + + public static ImRaii.IEndObject PushColor(ImPlotCol idx, Vector4 col) + { + ImPlot.PushStyleColor(idx, col); + return new EndUnconditionally(ImPlot.PopStyleColor, true); + } +} diff --git a/Craftimizer/Windows/MacroClipboard.cs b/Craftimizer/Windows/MacroClipboard.cs index a320b93..ee13d72 100644 --- a/Craftimizer/Windows/MacroClipboard.cs +++ b/Craftimizer/Windows/MacroClipboard.cs @@ -36,7 +36,7 @@ public sealed class MacroClipboard : Window, IDisposable private void DrawMacro(int idx, string macro) { using var id = ImRaii.PushId(idx); - using var panel = ImGuiUtils.GroupPanel($"Macro {idx + 1}", -1, out var availWidth); + using var panel = ImRaii2.GroupPanel($"Macro {idx + 1}", -1, out var availWidth); var cursor = ImGui.GetCursorPos(); diff --git a/Craftimizer/Windows/MacroEditor.cs b/Craftimizer/Windows/MacroEditor.cs index 1e2380e..1983c8a 100644 --- a/Craftimizer/Windows/MacroEditor.cs +++ b/Craftimizer/Windows/MacroEditor.cs @@ -79,31 +79,48 @@ public sealed class MacroEditor : Window, IDisposable private readonly record struct SimulationReliablity { - public record struct ParamReliability + public sealed class ParamReliability { + private List DataList { get; } + private ImGuiUtils.ViolinData? ViolinData { get; set; } + public int Max { get; private set; } public int Min { get; private set; } + public float Median { get; private set; } public float Average { get; private set; } public ParamReliability() { - Min = int.MaxValue; + DataList = new(); } public void Add(int value) { - Max = Math.Max(Max, value); - Min = Math.Min(Min, value); - Average += value; + DataList.Add(value); } - public void Finalize(int count) + public void FinalizeData() { - if (count == 0) - Average = Max = Min = 0; + if (DataList.Count == 0) + { + Average = Median = Max = Min = 0; + return; + } + + Max = DataList.Max(); + Min = DataList.Min(); + if (DataList.Count % 2 == 0) + Median = (float)DataList.Order().Skip(DataList.Count / 2 - 1).Take(2).Average(); else - Average /= count; + Median = DataList.Order().ElementAt(DataList.Count / 2); + Average = (float)DataList.Average(); } + + public ImGuiUtils.ViolinData? GetViolinData(float barMax, int resolution, double bandwidth) => + ViolinData ??= + Min != Max ? + new(DataList, 0, barMax, resolution, bandwidth) : + null; } public readonly ParamReliability Progress = new(); @@ -132,9 +149,9 @@ public sealed class MacroEditor : Window, IDisposable Quality.Add(state.Quality); Param.Add(getParam(state)); } - Progress.Finalize(iterCount); - Quality.Finalize(iterCount); - Param.Finalize(iterCount); + Progress.FinalizeData(); + Quality.FinalizeData(); + Param.FinalizeData(); } } @@ -160,7 +177,8 @@ public sealed class MacroEditor : Window, IDisposable } public SimulationReliablity GetReliability(in SimulationState initialState, IEnumerable actionSet, RecipeData recipeData) => - Reliability ??= new(initialState, actionSet, Service.Configuration.ReliabilitySimulationCount, recipeData); + Reliability ??= + new(initialState, actionSet, Service.Configuration.ReliabilitySimulationCount, recipeData); }; private List Macro { get; set; } = new(); @@ -195,7 +213,7 @@ public sealed class MacroEditor : Window, IDisposable private CancellationTokenSource? popupImportUrlTokenSource; private MacroImport.RetrievedMacro? popupImportUrlMacro; - public MacroEditor(CharacterStats characterStats, RecipeData recipeData, CrafterBuffs buffs, IEnumerable actions, Action>? setter) : base("Craftimizer Macro Editor", WindowFlags, false) + public MacroEditor(CharacterStats characterStats, RecipeData recipeData, CrafterBuffs buffs, IEnumerable actions, Action>? setter) : base("Craftimizer Macro Editor", WindowFlags) { CharacterStats = characterStats; RecipeData = recipeData; @@ -1000,7 +1018,7 @@ public sealed class MacroEditor : Window, IDisposable continue; var actions = category.GetActions(); - using var panel = ImGuiUtils.GroupPanel(category.GetDisplayName(), -1, out var availSpace); + using var panel = ImRaii2.GroupPanel(category.GetDisplayName(), -1, out var availSpace); var itemsPerRow = (int)MathF.Floor((availSpace + spacing) / (imageSize + spacing)); var itemCount = actions.Count; var iterCount = (int)(Math.Ceiling((float)itemCount / itemsPerRow) * itemsPerRow); @@ -1076,7 +1094,7 @@ public sealed class MacroEditor : Window, IDisposable } } - using (var panel = ImGuiUtils.GroupPanel("Buffs", -1, out _)) + using (var panel = ImRaii2.GroupPanel("Buffs", -1, out _)) { using var _font = ImRaii.PushFont(AxisFont.ImFont); @@ -1134,7 +1152,7 @@ public sealed class MacroEditor : Window, IDisposable var barSize = totalSize - textSize - spacing; foreach (var bar in bars) { - using var panel = ImGuiUtils.GroupPanel(bar.Name, totalSize, out _); + using var panel = ImRaii2.GroupPanel(bar.Name, totalSize, out _); if (bar.Condition is { } condition) { using (var g = ImRaii.Group()) @@ -1155,45 +1173,25 @@ public sealed class MacroEditor : Window, IDisposable } else { - if (bar.Reliability is { } reliability) - { - // blend rgb colors, assume alpha is always 1 - static Vector4 BlendColor(Vector4 A, Vector4 B) - { - return new( - A.X * (1 - B.W) + B.X * B.W, - A.Y * (1 - B.W) + B.Y * B.W, - A.Z * (1 - B.W) + B.Z * B.W, - 1); - } - var relBars = new (float Value, Vector4 Color)[] - { - (bar.Value, bar.Color), - (reliability.Average, BlendColor(bar.Color, new(.5f,.5f,.5f,.6f))), - (reliability.Min, BlendColor(bar.Color, new(0,0,0,.6f))), - (reliability.Max, BlendColor(bar.Color, new(1,1,1,.6f))), - }.DistinctBy(v => Math.Clamp(v.Value, 0, bar.Max)).OrderByDescending(v => v.Value); - var i = 0; - var pos = ImGui.GetCursorPos(); - foreach (var relBar in relBars) - { - ImGui.SetCursorPos(pos); - { - using var frameColor = ImRaii.PushColor(ImGuiCol.FrameBg, Vector4.Zero, i != 0); - using var color = ImRaii.PushColor(ImGuiCol.PlotHistogram, relBar.Color); - ImGui.ProgressBar(Math.Clamp(relBar.Value / bar.Max, 0, 1), new(barSize, ImGui.GetFrameHeight()), string.Empty); - } - if (i++ == 0) - { - if (ImGui.IsItemHovered()) - ImGui.SetTooltip($"Min: {reliability.Min}\nAvg: {reliability.Average:0.##}\nMax: {reliability.Max}"); - } - } - } - else - { - using var color = ImRaii.PushColor(ImGuiCol.PlotHistogram, bar.Color); + var pos = ImGui.GetCursorPos(); + using (var color = ImRaii.PushColor(ImGuiCol.PlotHistogram, bar.Color)) ImGui.ProgressBar(Math.Clamp(bar.Value / bar.Max, 0, 1), new(barSize, ImGui.GetFrameHeight()), string.Empty); + if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenOverlapped)) + { + if (bar.Reliability is { } reliability) + { + if (reliability.GetViolinData(bar.Max, (int)(barSize / 2), 0.02) is { } violinData) + { + ImGui.SetCursorPos(pos); + ImGuiUtils.ViolinPlot(violinData, new(barSize, ImGui.GetFrameHeight())); + if (ImGui.IsItemHovered()) + ImGui.SetTooltip( + $"Min: {reliability.Min}\n" + + $"Med: {reliability.Median:0.##}\n" + + $"Avg: {reliability.Average:0.##}\n" + + $"Max: {reliability.Max}"); + } + } } ImGui.SameLine(0, spacing); ImGui.AlignTextToFramePadding(); @@ -1217,7 +1215,7 @@ public sealed class MacroEditor : Window, IDisposable var imageSize = ImGui.GetFrameHeight() * 2; var lastState = InitialState; - using var panel = ImGuiUtils.GroupPanel("Macro", -1, out var availSpace); + using var panel = ImRaii2.GroupPanel("Macro", -1, out var availSpace); ImGui.Dummy(new(0, imageSize)); ImGui.SameLine(0, 0); @@ -1412,7 +1410,7 @@ public sealed class MacroEditor : Window, IDisposable { bool submittedText, submittedUrl; - using (var panel = ImGuiUtils.GroupPanel("##text", -1, out var availWidth)) + using (var panel = ImRaii2.GroupPanel("##text", -1, out var availWidth)) { ImGui.AlignTextToFramePadding(); ImGuiUtils.TextCentered("Paste your macro here"); @@ -1424,7 +1422,7 @@ public sealed class MacroEditor : Window, IDisposable submittedText = ImGui.Button("Import", new(availWidth, 0)); } - using (var panel = ImGuiUtils.GroupPanel("##url", -1, out var availWidth)) + using (var panel = ImRaii2.GroupPanel("##url", -1, out var availWidth)) { var availOffset = ImGui.GetContentRegionAvail().X - availWidth; diff --git a/Craftimizer/Windows/MacroList.cs b/Craftimizer/Windows/MacroList.cs index 3601af6..6bc5b56 100644 --- a/Craftimizer/Windows/MacroList.cs +++ b/Craftimizer/Windows/MacroList.cs @@ -111,7 +111,7 @@ public sealed class MacroList : Window, IDisposable var stateNullable = GetMacroState(macro); - using var panel = ImGuiUtils.GroupPanel(macro.Name, -1, out var availWidth); + using var panel = ImRaii2.GroupPanel(macro.Name, -1, out var availWidth); var stepsAvailWidthOffset = ImGui.GetContentRegionAvail().X - availWidth; var spacing = ImGui.GetStyle().ItemSpacing.Y; var miniRowHeight = (windowHeight - spacing) / 2f; diff --git a/Craftimizer/Windows/RecipeNote.cs b/Craftimizer/Windows/RecipeNote.cs index 5abd350..a660e85 100644 --- a/Craftimizer/Windows/RecipeNote.cs +++ b/Craftimizer/Windows/RecipeNote.cs @@ -216,7 +216,7 @@ public sealed unsafe class RecipeNote : Window, IDisposable ImGui.Separator(); var panelWidth = availWidth - ImGui.GetStyle().ItemSpacing.X * 2; - using (var panel = ImGuiUtils.GroupPanel("Best Saved Macro", panelWidth, out _)) + using (var panel = ImRaii2.GroupPanel("Best Saved Macro", panelWidth, out _)) { var stepsPanelWidthOffset = ImGui.GetContentRegionAvail().X - panelWidth; if (BestSavedMacro is { } savedMacro) @@ -228,7 +228,7 @@ public sealed unsafe class RecipeNote : Window, IDisposable DrawMacro(null, null, stepsPanelWidthOffset, true); } - using (var panel = ImGuiUtils.GroupPanel("Suggested Macro", panelWidth, out _)) + using (var panel = ImRaii2.GroupPanel("Suggested Macro", panelWidth, out _)) { var stepsPanelWidthOffset = ImGui.GetContentRegionAvail().X - panelWidth; if (BestSuggestedMacro is { } suggestedMacro) diff --git a/Craftimizer/Windows/Settings.cs b/Craftimizer/Windows/Settings.cs index a11ca3c..3f9a726 100644 --- a/Craftimizer/Windows/Settings.cs +++ b/Craftimizer/Windows/Settings.cs @@ -191,7 +191,7 @@ public sealed class Settings : Window, IDisposable ImGui.SetTooltip("Disabled temporarily for testing"); DrawOption( - "Show Only One Macro Stat", + "Show Only One Macro Stat in Crafting Log", "Only one stat will be shown for a macro. If a craft will be finished, quality\n" + "is shown. Otherwise, progress is shown. Durability and remaining CP will be\n" + "hidden.", @@ -200,9 +200,21 @@ public sealed class Settings : Window, IDisposable ref isDirty ); + DrawOption( + "Reliability Trial Count", + "When testing for reliability of a macro in the editor, this many trials will be\n" + + "run. You should set this value to at least 100 to get a reliable spread of data.\n" + + "If it's too low, you may not find an outlier, and the average might be skewed.", + Config.ReliabilitySimulationCount, + 5, + 5000, + v => Config.ReliabilitySimulationCount = v, + ref isDirty + ); + ImGuiHelpers.ScaledDummy(5); - using (var panel = ImGuiUtils.GroupPanel("Copying Settings", -1, out _)) + using (var panel = ImRaii2.GroupPanel("Copying Settings", -1, out _)) { DrawOption( "Macro Copy Method", @@ -380,7 +392,7 @@ public sealed class Settings : Window, IDisposable var config = configRef; - using (var panel = ImGuiUtils.GroupPanel("General", -1, out _)) + using (var panel = ImRaii2.GroupPanel("General", -1, out _)) { if (ImGui.Button("Reset to Default", OptionButtonSize)) { @@ -501,7 +513,7 @@ public sealed class Settings : Window, IDisposable ); } - using (var panel = ImGuiUtils.GroupPanel("Advanced", -1, out _)) + using (var panel = ImRaii2.GroupPanel("Advanced", -1, out _)) { DrawOption( "Score Storage Threshold", @@ -538,7 +550,7 @@ public sealed class Settings : Window, IDisposable ); } - using (var panel = ImGuiUtils.GroupPanel("Score Weights (Advanced)", -1, out _)) + using (var panel = ImRaii2.GroupPanel("Score Weights (Advanced)", -1, out _)) { ImGui.TextWrapped("All values should add up to 1. Otherwise, the Score Storage Threshold should be changed."); ImGuiHelpers.ScaledDummy(10); diff --git a/Craftimizer/packages.lock.json b/Craftimizer/packages.lock.json index 2308529..fb17722 100644 --- a/Craftimizer/packages.lock.json +++ b/Craftimizer/packages.lock.json @@ -8,6 +8,12 @@ "resolved": "2.1.12", "contentHash": "Sc0PVxvgg4NQjcI8n10/VfUQBAS4O+Fw2pZrAqBdRMbthYGeogzu5+xmIGCGmsEZ/ukMOBuAqiNiB5qA3MRalg==" }, + "MathNet.Numerics": { + "type": "Direct", + "requested": "[5.0.0, )", + "resolved": "5.0.0", + "contentHash": "pg1W2VwaEQMAiTpGK840hZgzavnqjlCMTVSbtVCXVyT+7AX4mc1o89SPv4TBlAjhgCOo9c1Y+jZ5m3ti2YgGgA==" + }, "Meziantou.Analyzer": { "type": "Direct", "requested": "[2.0.106, )", From 3895e1f17dbf3feb79608a975672969c9614d107 Mon Sep 17 00:00:00 2001 From: Asriel Camora Date: Mon, 13 Nov 2023 20:29:19 -0800 Subject: [PATCH 04/15] Minor optimizations --- Craftimizer/ImGuiUtils.cs | 8 ++++---- Craftimizer/Windows/MacroEditor.cs | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Craftimizer/ImGuiUtils.cs b/Craftimizer/ImGuiUtils.cs index 7f223c9..de749c9 100644 --- a/Craftimizer/ImGuiUtils.cs +++ b/Craftimizer/ImGuiUtils.cs @@ -253,7 +253,7 @@ internal static class ImGuiUtils var samplesList = samples.AsParallel().Select(s => (double)s).ToArray(); _ = Task.Run(() => { var s = Stopwatch.StartNew(); - var data = ParallelEnumerable.Range(0, resolution) + var data = ParallelEnumerable.Range(0, resolution + 1) .Select(n => Lerp(min, max, n / (float)resolution)) .Select(n => (n, (float)KernelDensity.EstimateGaussian(n, bandwidth, samplesList))) .Select(n => new Point(n.n, n.Item2, -n.Item2)); @@ -268,10 +268,9 @@ internal static class ImGuiUtils { using var padding = ImRaii2.PushStyle(ImPlotStyleVar.PlotPadding, Vector2.Zero); using var plotBg = ImRaii2.PushColor(ImPlotCol.PlotBg, Vector4.Zero); - using var frameBg = ImRaii2.PushColor(ImPlotCol.FrameBg, Vector4.Zero); using var fill = ImRaii2.PushColor(ImPlotCol.Fill, Vector4.One.WithAlpha(.5f)); - using var plot = ImRaii2.Plot("##violin", size, ImPlotFlags.CanvasOnly); + using var plot = ImRaii2.Plot("##violin", size, ImPlotFlags.CanvasOnly | ImPlotFlags.NoInputs | ImPlotFlags.NoChild | ImPlotFlags.NoFrame); if (plot) { ImPlot.SetupAxes(null, null, ImPlotAxisFlags.NoDecorations, ImPlotAxisFlags.NoDecorations | ImPlotAxisFlags.AutoFit); @@ -280,7 +279,8 @@ internal static class ImGuiUtils if (data.Data is { } points && !points.IsEmpty) { - unsafe { + unsafe + { var label_id = stackalloc byte[] { (byte)'\0' }; fixed (ViolinData.Point* p = points) { diff --git a/Craftimizer/Windows/MacroEditor.cs b/Craftimizer/Windows/MacroEditor.cs index 1983c8a..801970d 100644 --- a/Craftimizer/Windows/MacroEditor.cs +++ b/Craftimizer/Windows/MacroEditor.cs @@ -1180,7 +1180,7 @@ public sealed class MacroEditor : Window, IDisposable { if (bar.Reliability is { } reliability) { - if (reliability.GetViolinData(bar.Max, (int)(barSize / 2), 0.02) is { } violinData) + if (reliability.GetViolinData(bar.Max, (int)(barSize / 5), 0.02) is { } violinData) { ImGui.SetCursorPos(pos); ImGuiUtils.ViolinPlot(violinData, new(barSize, ImGui.GetFrameHeight())); From 0db552fe8a823d15200278489465c1d117098603 Mon Sep 17 00:00:00 2001 From: Asriel Camora Date: Mon, 13 Nov 2023 22:23:16 -0800 Subject: [PATCH 05/15] IconButtonSquare properly centers and aligns FA icons --- Craftimizer/ImGuiUtils.cs | 44 +++++++++++++++++++++++++-- Craftimizer/Windows/MacroClipboard.cs | 2 +- Craftimizer/Windows/MacroEditor.cs | 8 ++--- Craftimizer/Windows/MacroList.cs | 8 ++--- Craftimizer/Windows/RecipeNote.cs | 4 +-- 5 files changed, 53 insertions(+), 13 deletions(-) diff --git a/Craftimizer/ImGuiUtils.cs b/Craftimizer/ImGuiUtils.cs index de749c9..b86e6f5 100644 --- a/Craftimizer/ImGuiUtils.cs +++ b/Craftimizer/ImGuiUtils.cs @@ -507,10 +507,50 @@ internal static class ImGuiUtils return ImGuiExtras.InputTextEx(label, hint, ref input, maxLength, size, flags | Multiline, callback, user_data); } - public static bool IconButtonSized(FontAwesomeIcon icon, Vector2 size) + private static Vector2 GetIconSize(FontAwesomeIcon icon) { using var font = ImRaii.PushFont(UiBuilder.IconFont); - var ret = ImGui.Button(icon.ToIconString(), size); + return ImGui.CalcTextSize(icon.ToIconString()); + } + + private static void DrawCenteredIcon(FontAwesomeIcon icon, Vector2 offset, Vector2 size) + { + var iconSize = GetIconSize(icon); + + float scale; + Vector2 iconOffset; + if (iconSize.X > iconSize.Y) + { + scale = size.X / iconSize.X; + iconOffset = new(0, (size.Y - (iconSize.Y * scale)) / 2f); + } + else if (iconSize.Y > iconSize.X) + { + scale = size.Y / iconSize.Y; + iconOffset = new((size.X - (iconSize.X * scale)) / 2f, 0); + } + else + { + scale = size.X / iconSize.X; + iconOffset = Vector2.Zero; + } + + ImGui.GetWindowDrawList().AddText(UiBuilder.IconFont, UiBuilder.IconFont.FontSize * scale, offset + iconOffset, ImGui.GetColorU32(ImGuiCol.Text), icon.ToIconString()); + } + + public static bool IconButtonSquare(FontAwesomeIcon icon, float size = -1) + { + var ret = false; + + var buttonSize = new Vector2(size == -1 ? ImGui.GetFrameHeight() : size); + var pos = ImGui.GetCursorScreenPos(); + var spacing = new Vector2(ImGui.GetStyle().FramePadding.Y); + + if (ImGui.Button($"###{icon.ToIconString()}", buttonSize)) + ret = true; + + DrawCenteredIcon(icon, pos + spacing, buttonSize - spacing * 2); + return ret; } diff --git a/Craftimizer/Windows/MacroClipboard.cs b/Craftimizer/Windows/MacroClipboard.cs index ee13d72..989ce6a 100644 --- a/Craftimizer/Windows/MacroClipboard.cs +++ b/Craftimizer/Windows/MacroClipboard.cs @@ -49,7 +49,7 @@ public sealed class MacroClipboard : Window, IDisposable ImGui.SetCursorPos(buttonCursor); { using var color = ImRaii.PushColor(ImGuiCol.Button, ImGui.GetColorU32(buttonActive ? ImGuiCol.ButtonActive : ImGuiCol.ButtonHovered), buttonHovered); - ImGuiUtils.IconButtonSized(FontAwesomeIcon.Paste, new(ImGui.GetFrameHeight())); + ImGuiUtils.IconButtonSquare(FontAwesomeIcon.Paste); if (buttonClicked) { ImGui.SetClipboardText(macro); diff --git a/Craftimizer/Windows/MacroEditor.cs b/Craftimizer/Windows/MacroEditor.cs index 801970d..46cb057 100644 --- a/Craftimizer/Windows/MacroEditor.cs +++ b/Craftimizer/Windows/MacroEditor.cs @@ -1314,14 +1314,14 @@ public sealed class MacroEditor : Window, IDisposable "can vary wildly depending on the solver's settings."); } ImGui.SameLine(); - if (ImGuiUtils.IconButtonSized(FontAwesomeIcon.Paste, new(height))) + if (ImGuiUtils.IconButtonSquare(FontAwesomeIcon.Paste)) Service.Plugin.CopyMacro(Macro.Select(s => s.Action).ToArray()); if (ImGui.IsItemHovered()) ImGui.SetTooltip("Copy to Clipboard"); ImGui.SameLine(); using (var _disabled = ImRaii.Disabled(SolverRunning)) { - if (ImGuiUtils.IconButtonSized(FontAwesomeIcon.FileImport, new(height))) + if (ImGuiUtils.IconButtonSquare(FontAwesomeIcon.FileImport)) ShowImportPopup(); } if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled)) @@ -1332,7 +1332,7 @@ public sealed class MacroEditor : Window, IDisposable { using (var _disabled = ImRaii.Disabled(SolverRunning)) { - if (ImGuiUtils.IconButtonSized(FontAwesomeIcon.Undo, new(height))) + if (ImGuiUtils.IconButtonSquare(FontAwesomeIcon.Undo)) { SolverStartStepCount = null; Macro.Clear(); @@ -1346,7 +1346,7 @@ public sealed class MacroEditor : Window, IDisposable ImGui.SameLine(); using (var _disabled = ImRaii.Disabled(SolverRunning)) { - if (ImGuiUtils.IconButtonSized(FontAwesomeIcon.Trash, new(height))) + if (ImGuiUtils.IconButtonSquare(FontAwesomeIcon.Trash)) { SolverStartStepCount = null; Macro.Clear(); diff --git a/Craftimizer/Windows/MacroList.cs b/Craftimizer/Windows/MacroList.cs index 6bc5b56..aee068d 100644 --- a/Craftimizer/Windows/MacroList.cs +++ b/Craftimizer/Windows/MacroList.cs @@ -197,23 +197,23 @@ public sealed class MacroList : Window, IDisposable ImGui.TableNextColumn(); { - if (ImGuiUtils.IconButtonSized(FontAwesomeIcon.Paste, new(miniRowHeight))) + if (ImGuiUtils.IconButtonSquare(FontAwesomeIcon.Paste, miniRowHeight)) Service.Plugin.CopyMacro(macro.Actions); if (ImGui.IsItemHovered()) ImGui.SetTooltip("Copy to Clipboard"); ImGui.SameLine(); - if (ImGuiUtils.IconButtonSized(FontAwesomeIcon.Trash, new(miniRowHeight)) && ImGui.GetIO().KeyShift) + if (ImGuiUtils.IconButtonSquare(FontAwesomeIcon.Trash, miniRowHeight) && ImGui.GetIO().KeyShift) Service.Configuration.RemoveMacro(macro); if (ImGui.IsItemHovered()) ImGui.SetTooltip("Delete (Hold Shift)"); - if (ImGuiUtils.IconButtonSized(FontAwesomeIcon.PencilAlt, new(miniRowHeight))) + if (ImGuiUtils.IconButtonSquare(FontAwesomeIcon.PencilAlt, miniRowHeight)) ShowRenamePopup(macro); DrawRenamePopup(macro); if (ImGui.IsItemHovered()) ImGui.SetTooltip("Rename"); ImGui.SameLine(); - if (ImGuiUtils.IconButtonSized(FontAwesomeIcon.Edit, new(miniRowHeight))) + if (ImGuiUtils.IconButtonSquare(FontAwesomeIcon.Edit, miniRowHeight)) OpenEditor(macro); if (ImGui.IsItemHovered()) ImGui.SetTooltip("Open in Simulator"); diff --git a/Craftimizer/Windows/RecipeNote.cs b/Craftimizer/Windows/RecipeNote.cs index a660e85..f33b912 100644 --- a/Craftimizer/Windows/RecipeNote.cs +++ b/Craftimizer/Windows/RecipeNote.cs @@ -644,11 +644,11 @@ public sealed unsafe class RecipeNote : Window, IDisposable ImGui.TableNextColumn(); { - if (ImGuiUtils.IconButtonSized(FontAwesomeIcon.Edit, new(miniRowHeight))) + if (ImGuiUtils.IconButtonSquare(FontAwesomeIcon.Edit, miniRowHeight)) Service.Plugin.OpenMacroEditor(CharacterStats!, RecipeData!, new(Service.ClientState.LocalPlayer!.StatusList), macro.Actions, setter); if (ImGui.IsItemHovered()) ImGui.SetTooltip("Open in Simulator"); - if (ImGuiUtils.IconButtonSized(FontAwesomeIcon.Paste, new(miniRowHeight))) + if (ImGuiUtils.IconButtonSquare(FontAwesomeIcon.Paste, miniRowHeight)) Service.Plugin.CopyMacro(macro.Actions); if (ImGui.IsItemHovered()) ImGui.SetTooltip("Copy to Clipboard"); From ecf3a1a952645f535553081a2ddb2c53d84e6e38 Mon Sep 17 00:00:00 2001 From: Asriel Camora Date: Mon, 13 Nov 2023 22:26:00 -0800 Subject: [PATCH 06/15] Use dedicated CreateSim to create new Sims --- Craftimizer/Windows/MacroEditor.cs | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/Craftimizer/Windows/MacroEditor.cs b/Craftimizer/Windows/MacroEditor.cs index 46cb057..cf50945 100644 --- a/Craftimizer/Windows/MacroEditor.cs +++ b/Craftimizer/Windows/MacroEditor.cs @@ -21,8 +21,8 @@ using System.Numerics; using System.Text; using System.Threading; using System.Threading.Tasks; -using RandomSim = Craftimizer.Simulator.Simulator; -using Sim = Craftimizer.Simulator.SimulatorNoRandom; +using Sim = Craftimizer.Simulator.Simulator; +using SimNoRandom = Craftimizer.Simulator.SimulatorNoRandom; namespace Craftimizer.Windows; @@ -143,7 +143,7 @@ public sealed class MacroEditor : Window, IDisposable for (var i = 0; i < iterCount; ++i) { - var sim = new RandomSim(startState); + var sim = new Sim(startState); var (_, state, _) = sim.ExecuteMultiple(startState, actions); Progress.Add(state.Progress); Quality.Add(state.Quality); @@ -1003,7 +1003,7 @@ public sealed class MacroEditor : Window, IDisposable private void DrawActionHotbars() { - var sim = new Sim(State); + var sim = CreateSim(State); var imageSize = ImGui.GetFrameHeight() * 2; var spacing = ImGui.GetStyle().ItemSpacing.Y; @@ -1248,7 +1248,7 @@ public sealed class MacroEditor : Window, IDisposable } if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled)) { - var sim = new Sim(lastState); + var sim = CreateSim(lastState); ImGui.SetTooltip($"{action.GetName(RecipeData!.ClassJob)}\n{actionBase.GetTooltip(sim, true)}"); } lastState = state; @@ -1601,12 +1601,15 @@ public sealed class MacroEditor : Window, IDisposable private void RecalculateState() { InitialState = new SimulationState(new(CharacterStats, RecipeData.RecipeInfo, StartingQuality)); - var sim = new Sim(InitialState); + var sim = CreateSim(InitialState); var lastState = InitialState; for (var i = 0; i < Macro.Count; i++) lastState = Macro[i].Recalculate(sim, lastState); } + private Sim CreateSim(in SimulationState state) => + Service.Configuration.ConditionRandomness ? new Sim(state) : new SimNoRandom(state); + private void AddStep(ActionType action, int index = -1, bool isSolver = false) { if (index < -1 || index >= Macro.Count) @@ -1618,13 +1621,13 @@ public sealed class MacroEditor : Window, IDisposable if (index == -1) { - var sim = new Sim(State); + var sim = CreateSim(State); Macro.Add(new(action, sim, State, out _)); } else { var state = index == 0 ? InitialState : Macro[index - 1].State; - var sim = new Sim(state); + var sim = CreateSim(state); Macro.Insert(index, new(action, sim, state, out state)); for (var i = index + 1; i < Macro.Count; i++) @@ -1643,7 +1646,7 @@ public sealed class MacroEditor : Window, IDisposable Macro.RemoveAt(index); var state = index == 0 ? InitialState : Macro[index - 1].State; - var sim = new Sim(state); + var sim = CreateSim(state); for (var i = index; i < Macro.Count; i++) state = Macro[i].Recalculate(sim, state); } From b6bb6dd4cbff90d96d302ddc4d9f45319a2001e6 Mon Sep 17 00:00:00 2001 From: Asriel Camora Date: Mon, 13 Nov 2023 22:28:00 -0800 Subject: [PATCH 07/15] Add condition randomness button --- Craftimizer/Windows/MacroEditor.cs | 32 ++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/Craftimizer/Windows/MacroEditor.cs b/Craftimizer/Windows/MacroEditor.cs index cf50945..27166e6 100644 --- a/Craftimizer/Windows/MacroEditor.cs +++ b/Craftimizer/Windows/MacroEditor.cs @@ -1155,10 +1155,13 @@ public sealed class MacroEditor : Window, IDisposable using var panel = ImRaii2.GroupPanel(bar.Name, totalSize, out _); if (bar.Condition is { } condition) { + var pos = ImGui.GetCursorPos(); using (var g = ImRaii.Group()) { + var availSize = totalSize - (spacing + ImGui.GetFrameHeight()); var size = ImGui.GetFrameHeight() + spacing + ImGui.CalcTextSize(condition.Name()).X; - ImGuiUtils.AlignCentered(size, totalSize); + + ImGuiUtils.AlignCentered(size, availSize); ImGui.GetWindowDrawList().AddCircleFilled( ImGui.GetCursorScreenPos() + new Vector2(ImGui.GetFrameHeight() / 2), ImGui.GetFrameHeight() / 2, @@ -1170,6 +1173,23 @@ public sealed class MacroEditor : Window, IDisposable } if (ImGui.IsItemHovered()) ImGui.SetTooltip(condition.Description(CharacterStats.HasSplendorousBuff)); + + ImGui.SetCursorPos(pos); + ImGuiUtils.AlignRight(ImGui.GetFrameHeight(), totalSize); + + using (var disabled = ImRaii.Disabled(SolverRunning)) + { + using var tint = ImRaii.PushColor(ImGuiCol.Text, ImGui.GetColorU32(ImGuiCol.TextDisabled), !Service.Configuration.ConditionRandomness); + if (ImGuiUtils.IconButtonSquare(FontAwesomeIcon.Dice)) + { + Service.Configuration.ConditionRandomness ^= true; + Service.Configuration.Save(); + + RecalculateState(); + } + } + if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled)) + ImGui.SetTooltip("Condition Randomness"); } else { @@ -1545,6 +1565,14 @@ public sealed class MacroEditor : Window, IDisposable SolverException = null; RevertPreviousMacro(); + + if (Service.Configuration.ConditionRandomness) + { + Service.Configuration.ConditionRandomness = false; + Service.Configuration.Save(); + RecalculateState(); + } + SolverStartStepCount = Macro.Count; var token = SolverTokenSource.Token; @@ -1607,7 +1635,7 @@ public sealed class MacroEditor : Window, IDisposable lastState = Macro[i].Recalculate(sim, lastState); } - private Sim CreateSim(in SimulationState state) => + private static Sim CreateSim(in SimulationState state) => Service.Configuration.ConditionRandomness ? new Sim(state) : new SimNoRandom(state); private void AddStep(ActionType action, int index = -1, bool isSolver = false) From ea220fa33db7854b13f08744f450b61d9056f53c Mon Sep 17 00:00:00 2001 From: Asriel Camora Date: Mon, 13 Nov 2023 22:42:03 -0800 Subject: [PATCH 08/15] Add level caps on splendorous and specialist --- Craftimizer/Windows/MacroEditor.cs | 35 +++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/Craftimizer/Windows/MacroEditor.cs b/Craftimizer/Windows/MacroEditor.cs index 27166e6..99ecc99 100644 --- a/Craftimizer/Windows/MacroEditor.cs +++ b/Craftimizer/Windows/MacroEditor.cs @@ -397,24 +397,39 @@ public sealed class MacroEditor : Window, IDisposable var imageButtonPadding = (int)(ImGui.GetStyle().FramePadding.Y / 2f); var imageButtonSize = imageSize - imageButtonPadding * 2; { - var v = CharacterStats.HasSplendorousBuff; - var tint = v ? Vector4.One : disabledTint; - if (ImGui.ImageButton(SplendorousBadge.ImGuiHandle, new Vector2(imageButtonSize), default, Vector2.One, imageButtonPadding, default, tint)) - CharacterStats = CharacterStats with { HasSplendorousBuff = !v }; - if (ImGui.IsItemHovered()) + var splendorousLevel = 90; + if (CharacterStats.HasSplendorousBuff && splendorousLevel > CharacterStats.Level) + CharacterStats = CharacterStats with { HasSplendorousBuff = false }; + + using (var d = ImRaii.Disabled(splendorousLevel > CharacterStats.Level)) + { + var v = CharacterStats.HasSplendorousBuff; + var tint = v ? Vector4.One : disabledTint; + if (ImGui.ImageButton(SplendorousBadge.ImGuiHandle, new Vector2(imageButtonSize), default, Vector2.One, imageButtonPadding, default, tint)) + CharacterStats = CharacterStats with { HasSplendorousBuff = !v }; + } + if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled)) ImGui.SetTooltip(CharacterStats.HasSplendorousBuff ? $"Splendorous Tool" : "No Splendorous Tool"); } ImGui.SameLine(0, 5); bool? newIsSpecialist = null; { var v = CharacterStats.IsSpecialist; - var tint = new Vector4(0.99f, 0.97f, 0.62f, 1f) * (v ? Vector4.One : disabledTint); - if (ImGui.ImageButton(SpecialistBadge.ImGuiHandle, new Vector2(imageButtonSize), default, Vector2.One, imageButtonPadding, default, tint)) + + var specialistLevel = 55; + if (CharacterStats.IsSpecialist && specialistLevel > CharacterStats.Level) + newIsSpecialist = v = false; + + using (var d = ImRaii.Disabled(specialistLevel > CharacterStats.Level)) { - v = !v; - newIsSpecialist = v; + var tint = new Vector4(0.99f, 0.97f, 0.62f, 1f) * (v ? Vector4.One : disabledTint); + if (ImGui.ImageButton(SpecialistBadge.ImGuiHandle, new Vector2(imageButtonSize), default, Vector2.One, imageButtonPadding, default, tint)) + { + v = !v; + newIsSpecialist = v; + } } - if (ImGui.IsItemHovered()) + if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled)) ImGui.SetTooltip(v ? $"Specialist" : "Not a Specialist"); } ImGui.SameLine(0, 5); From b9c871f2b2c441706ec5a2b5272392119844face Mon Sep 17 00:00:00 2001 From: Asriel Camora Date: Mon, 13 Nov 2023 22:58:10 -0800 Subject: [PATCH 09/15] Remove redundant initial state in sims --- Benchmark/Program.cs | 2 +- Craftimizer/Windows/MacroEditor.cs | 18 +++++++++--------- Craftimizer/Windows/MacroList.cs | 2 +- Craftimizer/Windows/RecipeNote.cs | 2 +- Simulator/Simulator.cs | 10 ---------- Simulator/SimulatorNoRandom.cs | 4 ---- Solver/MCTS.cs | 4 ++-- Solver/Simulator.cs | 2 +- Solver/Solver.cs | 6 +++--- Test/Simulator/Simulator.cs | 4 ++-- 10 files changed, 20 insertions(+), 34 deletions(-) diff --git a/Benchmark/Program.cs b/Benchmark/Program.cs index 4ba614e..eb87a15 100644 --- a/Benchmark/Program.cs +++ b/Benchmark/Program.cs @@ -106,7 +106,7 @@ internal static class Program MaxStepCount = 30, }; - var sim = new SimulatorNoRandom(new(input)); + var sim = new SimulatorNoRandom(); (_, var state) = sim.Execute(new(input), ActionType.MuscleMemory); (_, state) = sim.Execute(state, ActionType.PrudentTouch); //(_, state) = sim.Execute(state, ActionType.Manipulation); diff --git a/Craftimizer/Windows/MacroEditor.cs b/Craftimizer/Windows/MacroEditor.cs index 99ecc99..0685427 100644 --- a/Craftimizer/Windows/MacroEditor.cs +++ b/Craftimizer/Windows/MacroEditor.cs @@ -143,7 +143,7 @@ public sealed class MacroEditor : Window, IDisposable for (var i = 0; i < iterCount; ++i) { - var sim = new Sim(startState); + var sim = new Sim(); var (_, state, _) = sim.ExecuteMultiple(startState, actions); Progress.Add(state.Progress); Quality.Add(state.Quality); @@ -1018,7 +1018,7 @@ public sealed class MacroEditor : Window, IDisposable private void DrawActionHotbars() { - var sim = CreateSim(State); + var sim = CreateSim(); var imageSize = ImGui.GetFrameHeight() * 2; var spacing = ImGui.GetStyle().ItemSpacing.Y; @@ -1283,7 +1283,7 @@ public sealed class MacroEditor : Window, IDisposable } if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled)) { - var sim = CreateSim(lastState); + var sim = CreateSim(); ImGui.SetTooltip($"{action.GetName(RecipeData!.ClassJob)}\n{actionBase.GetTooltip(sim, true)}"); } lastState = state; @@ -1644,14 +1644,14 @@ public sealed class MacroEditor : Window, IDisposable private void RecalculateState() { InitialState = new SimulationState(new(CharacterStats, RecipeData.RecipeInfo, StartingQuality)); - var sim = CreateSim(InitialState); + var sim = CreateSim(); var lastState = InitialState; for (var i = 0; i < Macro.Count; i++) lastState = Macro[i].Recalculate(sim, lastState); } - private static Sim CreateSim(in SimulationState state) => - Service.Configuration.ConditionRandomness ? new Sim(state) : new SimNoRandom(state); + private static Sim CreateSim() => + Service.Configuration.ConditionRandomness ? new Sim() : new SimNoRandom(); private void AddStep(ActionType action, int index = -1, bool isSolver = false) { @@ -1664,13 +1664,13 @@ public sealed class MacroEditor : Window, IDisposable if (index == -1) { - var sim = CreateSim(State); + var sim = CreateSim(); Macro.Add(new(action, sim, State, out _)); } else { var state = index == 0 ? InitialState : Macro[index - 1].State; - var sim = CreateSim(state); + var sim = CreateSim(); Macro.Insert(index, new(action, sim, state, out state)); for (var i = index + 1; i < Macro.Count; i++) @@ -1689,7 +1689,7 @@ public sealed class MacroEditor : Window, IDisposable Macro.RemoveAt(index); var state = index == 0 ? InitialState : Macro[index - 1].State; - var sim = CreateSim(state); + var sim = CreateSim(); for (var i = index; i < Macro.Count; i++) state = Macro[i].Recalculate(sim, state); } diff --git a/Craftimizer/Windows/MacroList.cs b/Craftimizer/Windows/MacroList.cs index aee068d..dbceea8 100644 --- a/Craftimizer/Windows/MacroList.cs +++ b/Craftimizer/Windows/MacroList.cs @@ -329,7 +329,7 @@ public sealed class MacroList : Window, IDisposable return state; state = new SimulationState(new(CharacterStats, RecipeData.RecipeInfo)); - var sim = new Sim(state); + var sim = new Sim(); (_, state, _) = sim.ExecuteMultiple(state, macro.Actions); return MacroStateCache[macro] = state; } diff --git a/Craftimizer/Windows/RecipeNote.cs b/Craftimizer/Windows/RecipeNote.cs index f33b912..fcc4895 100644 --- a/Craftimizer/Windows/RecipeNote.cs +++ b/Craftimizer/Windows/RecipeNote.cs @@ -824,7 +824,7 @@ public sealed unsafe class RecipeNote : Window, IDisposable var state = new SimulationState(input); var config = Service.Configuration.SimulatorSolverConfig; var mctsConfig = new MCTSConfig(config); - var simulator = new SimulatorNoRandom(state); + var simulator = new SimulatorNoRandom(); List macros = new(Service.Configuration.Macros); token.ThrowIfCancellationRequested(); diff --git a/Simulator/Simulator.cs b/Simulator/Simulator.cs index 03dbb12..4ac07a2 100644 --- a/Simulator/Simulator.cs +++ b/Simulator/Simulator.cs @@ -35,16 +35,6 @@ public class Simulator public IEnumerable AvailableActions => ActionUtils.AvailableActions(this); - public Simulator(in SimulationState state) - { - State = state; - } - - public void SetState(in SimulationState state) - { - State = state; - } - public (ActionResponse Response, SimulationState NewState) Execute(in SimulationState state, ActionType action) { State = state; diff --git a/Simulator/SimulatorNoRandom.cs b/Simulator/SimulatorNoRandom.cs index e6c4738..9c39996 100644 --- a/Simulator/SimulatorNoRandom.cs +++ b/Simulator/SimulatorNoRandom.cs @@ -2,10 +2,6 @@ namespace Craftimizer.Simulator; public class SimulatorNoRandom : Simulator { - public SimulatorNoRandom(in SimulationState state) : base(state) - { - } - public sealed override bool RollSuccessRaw(float successRate) => successRate == 1; public sealed override Condition GetNextRandomCondition() => Condition.Normal; } diff --git a/Solver/MCTS.cs b/Solver/MCTS.cs index 76251db..25c0ea6 100644 --- a/Solver/MCTS.cs +++ b/Solver/MCTS.cs @@ -20,7 +20,7 @@ public sealed class MCTS public MCTS(in MCTSConfig config, in SimulationState state) { this.config = config; - var sim = new Simulator(state, config.MaxStepCount); + var sim = new Simulator(config.MaxStepCount); rootNode = new(new( state, null, @@ -280,7 +280,7 @@ public sealed class MCTS [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Search(int iterations, CancellationToken token) { - Simulator simulator = new(rootNode.State.State, config.MaxStepCount); + Simulator simulator = new(config.MaxStepCount); var random = rootNode.State.State.Input.Random; var n = 0; for (var i = 0; i < iterations || MaxScore == 0; i++) diff --git a/Solver/Simulator.cs b/Solver/Simulator.cs index a3a29fa..a458d53 100644 --- a/Solver/Simulator.cs +++ b/Solver/Simulator.cs @@ -20,7 +20,7 @@ internal sealed class Simulator : SimulatorNoRandom } } - public Simulator(in SimulationState state, int maxStepCount) : base(state) + public Simulator(int maxStepCount) { this.maxStepCount = maxStepCount; } diff --git a/Solver/Solver.cs b/Solver/Solver.cs index 7c9efcf..f0b17d0 100644 --- a/Solver/Solver.cs +++ b/Solver/Solver.cs @@ -116,7 +116,7 @@ public sealed class Solver : IDisposable var bestSims = new List<(float Score, SolverSolution Result)>(); var state = State; - var sim = new Simulator(state, Config.MaxStepCount); + var sim = new Simulator(Config.MaxStepCount); var activeStates = new List() { new(Array.Empty(), state) }; @@ -240,7 +240,7 @@ public sealed class Solver : IDisposable { var actions = new List(); var state = State; - var sim = new Simulator(state, Config.MaxStepCount); + var sim = new Simulator(Config.MaxStepCount); while (true) { Token.ThrowIfCancellationRequested(); @@ -303,7 +303,7 @@ public sealed class Solver : IDisposable { var actions = new List(); var state = State; - var sim = new Simulator(state, Config.MaxStepCount); + var sim = new Simulator(Config.MaxStepCount); while (true) { Token.ThrowIfCancellationRequested(); diff --git a/Test/Simulator/Simulator.cs b/Test/Simulator/Simulator.cs index 20756fc..dbf0be7 100644 --- a/Test/Simulator/Simulator.cs +++ b/Test/Simulator/Simulator.cs @@ -68,7 +68,7 @@ public class SimulatorTests int progress, int quality, int durability, int cp) { - var simulator = new SimulatorNoRandom(new(input)); + var simulator = new SimulatorNoRandom(); var (_, state, _) = simulator.ExecuteMultiple(new(input), actions); Assert.AreEqual(progress, state.Progress); Assert.AreEqual(quality, state.Quality); @@ -170,7 +170,7 @@ public class SimulatorTests }, 0, 4064, 15, 332); Assert.AreEqual(10, state.ActiveEffects.InnerQuiet); - Assert.IsTrue(ActionType.TrainedFinesse.Base().CanUse(new SimulatorNoRandom(state))); + Assert.IsTrue(ActionType.TrainedFinesse.Base().CanUse(new SimulatorNoRandom())); } [TestMethod] From 980613970c80cbf67c7219f8144b560eaa0c2591 Mon Sep 17 00:00:00 2001 From: Asriel Camora Date: Mon, 13 Nov 2023 23:15:18 -0800 Subject: [PATCH 10/15] Add ability to set initialized sim state if needed --- Craftimizer/Windows/MacroEditor.cs | 8 ++++--- Simulator/Simulator.cs | 36 ++++++++++++++++-------------- Solver/MCTS.cs | 2 +- Solver/Solver.cs | 4 ++-- Test/Simulator/Simulator.cs | 2 +- 5 files changed, 28 insertions(+), 24 deletions(-) diff --git a/Craftimizer/Windows/MacroEditor.cs b/Craftimizer/Windows/MacroEditor.cs index 0685427..b5fb4f4 100644 --- a/Craftimizer/Windows/MacroEditor.cs +++ b/Craftimizer/Windows/MacroEditor.cs @@ -1018,7 +1018,7 @@ public sealed class MacroEditor : Window, IDisposable private void DrawActionHotbars() { - var sim = CreateSim(); + var sim = CreateSim(State); var imageSize = ImGui.GetFrameHeight() * 2; var spacing = ImGui.GetStyle().ItemSpacing.Y; @@ -1283,8 +1283,7 @@ public sealed class MacroEditor : Window, IDisposable } if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled)) { - var sim = CreateSim(); - ImGui.SetTooltip($"{action.GetName(RecipeData!.ClassJob)}\n{actionBase.GetTooltip(sim, true)}"); + ImGui.SetTooltip($"{action.GetName(RecipeData!.ClassJob)}\n{actionBase.GetTooltip(CreateSim(lastState), true)}"); } lastState = state; } @@ -1653,6 +1652,9 @@ public sealed class MacroEditor : Window, IDisposable private static Sim CreateSim() => Service.Configuration.ConditionRandomness ? new Sim() : new SimNoRandom(); + private static Sim CreateSim(in SimulationState state) => + Service.Configuration.ConditionRandomness ? new Sim() { State = state } : new SimNoRandom() { State = state }; + private void AddStep(ActionType action, int index = -1, bool isSolver = false) { if (index < -1 || index >= Macro.Count) diff --git a/Simulator/Simulator.cs b/Simulator/Simulator.cs index 4ac07a2..75364f5 100644 --- a/Simulator/Simulator.cs +++ b/Simulator/Simulator.cs @@ -6,20 +6,22 @@ namespace Craftimizer.Simulator; public class Simulator { - protected SimulationState State; + public SimulationState State { init => state = value; } - public SimulationInput Input => State.Input; - public ref int ActionCount => ref State.ActionCount; - public ref int StepCount => ref State.StepCount; - public ref int Progress => ref State.Progress; - public ref int Quality => ref State.Quality; - public ref int Durability => ref State.Durability; - public ref int CP => ref State.CP; - public ref Condition Condition => ref State.Condition; - public ref Effects ActiveEffects => ref State.ActiveEffects; - public ref ActionStates ActionStates => ref State.ActionStates; + private SimulationState state; - public bool IsFirstStep => State.StepCount == 0; + public SimulationInput Input => state.Input; + public ref int ActionCount => ref state.ActionCount; + public ref int StepCount => ref state.StepCount; + public ref int Progress => ref state.Progress; + public ref int Quality => ref state.Quality; + public ref int Durability => ref state.Durability; + public ref int CP => ref state.CP; + public ref Condition Condition => ref state.Condition; + public ref Effects ActiveEffects => ref state.ActiveEffects; + public ref ActionStates ActionStates => ref state.ActionStates; + + public bool IsFirstStep => state.StepCount == 0; public virtual CompletionState CompletionState { get @@ -37,8 +39,8 @@ public class Simulator public (ActionResponse Response, SimulationState NewState) Execute(in SimulationState state, ActionType action) { - State = state; - return (Execute(action), State); + this.state = state; + return (Execute(action), this.state); } private ActionResponse Execute(ActionType action) @@ -67,16 +69,16 @@ public class Simulator public (ActionResponse Response, SimulationState NewState, int FailedActionIdx) ExecuteMultiple(in SimulationState state, IEnumerable actions) { - State = state; + this.state = state; var i = 0; foreach(var action in actions) { var resp = Execute(action); if (resp != ActionResponse.UsedAction) - return (resp, State, i); + return (resp, this.state, i); i++; } - return (ActionResponse.UsedAction, State, -1); + return (ActionResponse.UsedAction, this.state, -1); } [Pure] diff --git a/Solver/MCTS.cs b/Solver/MCTS.cs index 25c0ea6..99e6147 100644 --- a/Solver/MCTS.cs +++ b/Solver/MCTS.cs @@ -20,7 +20,7 @@ public sealed class MCTS public MCTS(in MCTSConfig config, in SimulationState state) { this.config = config; - var sim = new Simulator(config.MaxStepCount); + var sim = new Simulator(config.MaxStepCount) { State = state }; rootNode = new(new( state, null, diff --git a/Solver/Solver.cs b/Solver/Solver.cs index f0b17d0..add8bd1 100644 --- a/Solver/Solver.cs +++ b/Solver/Solver.cs @@ -240,7 +240,7 @@ public sealed class Solver : IDisposable { var actions = new List(); var state = State; - var sim = new Simulator(Config.MaxStepCount); + var sim = new Simulator(Config.MaxStepCount) { State = state }; while (true) { Token.ThrowIfCancellationRequested(); @@ -303,7 +303,7 @@ public sealed class Solver : IDisposable { var actions = new List(); var state = State; - var sim = new Simulator(Config.MaxStepCount); + var sim = new Simulator(Config.MaxStepCount) { State = state }; while (true) { Token.ThrowIfCancellationRequested(); diff --git a/Test/Simulator/Simulator.cs b/Test/Simulator/Simulator.cs index dbf0be7..886f056 100644 --- a/Test/Simulator/Simulator.cs +++ b/Test/Simulator/Simulator.cs @@ -170,7 +170,7 @@ public class SimulatorTests }, 0, 4064, 15, 332); Assert.AreEqual(10, state.ActiveEffects.InnerQuiet); - Assert.IsTrue(ActionType.TrainedFinesse.Base().CanUse(new SimulatorNoRandom())); + Assert.IsTrue(ActionType.TrainedFinesse.Base().CanUse(new SimulatorNoRandom() { State = state })); } [TestMethod] From 0a55e87b3ae7e1733135cc3e34532a99d377d88e Mon Sep 17 00:00:00 2001 From: Asriel Camora Date: Tue, 14 Nov 2023 00:27:26 -0800 Subject: [PATCH 11/15] Fix macro editor thread safety --- Craftimizer/Windows/MacroEditor.cs | 57 +++++++++++++++++++++++++++--- 1 file changed, 53 insertions(+), 4 deletions(-) diff --git a/Craftimizer/Windows/MacroEditor.cs b/Craftimizer/Windows/MacroEditor.cs index b5fb4f4..5ba0205 100644 --- a/Craftimizer/Windows/MacroEditor.cs +++ b/Craftimizer/Windows/MacroEditor.cs @@ -180,7 +180,7 @@ public sealed class MacroEditor : Window, IDisposable Reliability ??= new(initialState, actionSet, Service.Configuration.ReliabilitySimulationCount, recipeData); }; - + private List Macro { get; set; } = new(); private SimulationState InitialState { get; set; } private SimulationState State => Macro.Count > 0 ? Macro[^1].State : InitialState; @@ -191,6 +191,8 @@ public sealed class MacroEditor : Window, IDisposable private CancellationTokenSource? SolverTokenSource { get; set; } private Exception? SolverException { get; set; } private int? SolverStartStepCount { get; set; } + private object? SolverQueueLock { get; set; } + private List? SolverQueuedSteps { get; set; } private bool SolverRunning => SolverTokenSource != null; private IDalamudTextureWrap ExpertBadge { get; } @@ -255,6 +257,11 @@ public sealed class MacroEditor : Window, IDisposable SolverTokenSource?.Cancel(); } + public override void Update() + { + TryFlushSolvedSteps(); + } + public override void Draw() { var modifiedInput = false; @@ -1577,6 +1584,16 @@ public sealed class MacroEditor : Window, IDisposable SolverTokenSource?.Cancel(); SolverTokenSource = new(); SolverException = null; + if (SolverQueueLock is { }) + { + lock (SolverQueueLock) + { + SolverQueuedSteps!.Clear(); + SolverQueueLock = null; + } + } + SolverQueueLock = new(); + SolverQueuedSteps ??= new(); RevertPreviousMacro(); @@ -1622,7 +1639,7 @@ public sealed class MacroEditor : Window, IDisposable var solver = new Solver.Solver(config, state) { Token = token }; solver.OnLog += Log.Debug; - solver.OnNewAction += a => AddStep(a, isSolver: true); + solver.OnNewAction += QueueSolverStep; solver.Start(); _ = solver.GetTask().GetAwaiter().GetResult(); @@ -1655,11 +1672,11 @@ public sealed class MacroEditor : Window, IDisposable private static Sim CreateSim(in SimulationState state) => Service.Configuration.ConditionRandomness ? new Sim() { State = state } : new SimNoRandom() { State = state }; - private void AddStep(ActionType action, int index = -1, bool isSolver = false) + private void AddStep(ActionType action, int index = -1) { if (index < -1 || index >= Macro.Count) throw new ArgumentOutOfRangeException(nameof(index)); - if (!isSolver && SolverRunning) + if (SolverRunning) throw new InvalidOperationException("Cannot add steps while solver is running"); if (!SolverRunning) SolverStartStepCount = null; @@ -1680,6 +1697,38 @@ public sealed class MacroEditor : Window, IDisposable } } + private void QueueSolverStep(ActionType action) + { + if (!SolverRunning) + throw new InvalidOperationException("Cannot queue steps while solver isn't running"); + lock (SolverQueueLock!) + { + var lastState = SolverQueuedSteps!.Count > 0 ? SolverQueuedSteps[^1].State : State; + SolverQueuedSteps.Add(new(action, CreateSim(), lastState, out _)); + } + } + + private void TryFlushSolvedSteps() + { + if (SolverQueueLock == null) + return; + + lock (SolverQueueLock!) + { + if (SolverQueuedSteps!.Count > 0) + { + Macro.AddRange(SolverQueuedSteps); + SolverQueuedSteps.Clear(); + } + + if (!SolverRunning) + { + SolverQueuedSteps.Clear(); + SolverQueueLock = null; + } + } + } + private void RemoveStep(int index) { if (index < 0 || index >= Macro.Count) From 51e5f6e049a725aacda5e2fe03a0622098b3e237 Mon Sep 17 00:00:00 2001 From: Asriel Camora Date: Tue, 14 Nov 2023 00:44:21 -0800 Subject: [PATCH 12/15] Remove focused actions and observe from action set (combos exist) --- Solver/ActionSet.cs | 3 --- Solver/Simulator.cs | 14 -------------- 2 files changed, 17 deletions(-) diff --git a/Solver/ActionSet.cs b/Solver/ActionSet.cs index b7cc97d..9c7710c 100644 --- a/Solver/ActionSet.cs +++ b/Solver/ActionSet.cs @@ -24,8 +24,6 @@ public struct ActionSet ActionType.DelicateSynthesis, ActionType.PreparatoryTouch, ActionType.Reflect, - ActionType.FocusedTouch, - ActionType.FocusedSynthesis, ActionType.PrudentTouch, ActionType.Manipulation, ActionType.MuscleMemory, @@ -37,7 +35,6 @@ public struct ActionSet ActionType.StandardTouch, ActionType.Veneration, ActionType.WasteNot, - ActionType.Observe, ActionType.MastersMend, ActionType.BasicTouch, }; diff --git a/Solver/Simulator.cs b/Solver/Simulator.cs index a458d53..ea0404f 100644 --- a/Solver/Simulator.cs +++ b/Solver/Simulator.cs @@ -42,22 +42,12 @@ internal sealed class Simulator : SimulatorNoRandom if (Quality >= Input.Recipe.MaxQuality && baseAction.IncreasesQuality) return false; - if (action == ActionType.Observe && - ActionStates.Observed) - return false; - if (strict) { // always use Trained Eye if it's available if (action == ActionType.TrainedEye) return baseAction.CanUse(this); - // only allow Focused moves after Observe - if (ActionStates.Observed && - action != ActionType.FocusedSynthesis && - action != ActionType.FocusedTouch) - return false; - // don't allow quality moves under Muscle Memory for difficult crafts if (Input.Recipe.ClassJobLevel == 90 && HasEffect(EffectType.MuscleMemory) && @@ -116,10 +106,6 @@ internal sealed class Simulator : SimulatorNoRandom (HasEffect(EffectType.WasteNot) || HasEffect(EffectType.WasteNot2))) return false; - if (action == ActionType.Observe && - CP < 12) - return false; - if (action == ActionType.MastersMend && Input.Recipe.MaxDurability - Durability < 25) return false; From 466d7ab5089fe9ca17efc1849ca62d3e2006262b Mon Sep 17 00:00:00 2001 From: Asriel Camora Date: Tue, 14 Nov 2023 00:47:12 -0800 Subject: [PATCH 13/15] Fix missing percents and use % for stats of expert recipe quality bar --- Craftimizer/Windows/MacroEditor.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Craftimizer/Windows/MacroEditor.cs b/Craftimizer/Windows/MacroEditor.cs index 5ba0205..f3670f4 100644 --- a/Craftimizer/Windows/MacroEditor.cs +++ b/Craftimizer/Windows/MacroEditor.cs @@ -135,7 +135,10 @@ public sealed class MacroEditor : Window, IDisposable if (recipeData.Recipe.ItemResult.Value!.IsCollectable) getParam = s => s.Collectability; else if (recipeData.Recipe.RequiredQuality > 0) - getParam = s => s.Quality; + { + var reqQual = recipeData.Recipe.RequiredQuality; + getParam = s => (int)((float)s.Quality / reqQual * 100); + } else if (recipeData.RecipeInfo.MaxQuality > 0) getParam = s => s.HQPercent; else @@ -1098,9 +1101,12 @@ public sealed class MacroEditor : Window, IDisposable if (RecipeData.Recipe.ItemResult.Value!.IsCollectable) datas.Add(new("Collectability", Colors.HQ, Reliability.Param, State.Collectability, State.MaxCollectability, $"{State.Collectability}", null)); else if (RecipeData.Recipe.RequiredQuality > 0) - datas.Add(new("Quality %", Colors.HQ, Reliability.Param, State.Quality, RecipeData.Recipe.RequiredQuality, $"{(float)State.Quality / RecipeData.Recipe.RequiredQuality * 100:0}%", null)); + { + var qualityPercent = (float)State.Quality / RecipeData.Recipe.RequiredQuality * 100; + datas.Add(new("Quality %%", Colors.HQ, Reliability.Param, qualityPercent, 100, $"{qualityPercent:0}%", null)); + } else if (RecipeData.RecipeInfo.MaxQuality > 0) - datas.Add(new("HQ %", Colors.HQ, Reliability.Param, State.HQPercent, 100, $"{State.HQPercent}%", null)); + datas.Add(new("HQ %%", Colors.HQ, Reliability.Param, State.HQPercent, 100, $"{State.HQPercent}%", null)); DrawBars(datas); ImGui.TableNextColumn(); @@ -1194,7 +1200,7 @@ public sealed class MacroEditor : Window, IDisposable ImGui.Text(condition.Name()); } if (ImGui.IsItemHovered()) - ImGui.SetTooltip(condition.Description(CharacterStats.HasSplendorousBuff)); + ImGui.SetTooltip(condition.Description(CharacterStats.HasSplendorousBuff).Replace("%", "%%")); ImGui.SetCursorPos(pos); ImGuiUtils.AlignRight(ImGui.GetFrameHeight(), totalSize); From fc62e8ae1b3df20b5c97c524d6f852f3a15e8691 Mon Sep 17 00:00:00 2001 From: Asriel Camora Date: Tue, 14 Nov 2023 00:53:14 -0800 Subject: [PATCH 14/15] Remove condition randomness in settings window --- Craftimizer/Windows/MacroEditor.cs | 4 +++- Craftimizer/Windows/Settings.cs | 19 ------------------- 2 files changed, 3 insertions(+), 20 deletions(-) diff --git a/Craftimizer/Windows/MacroEditor.cs b/Craftimizer/Windows/MacroEditor.cs index f3670f4..89fe2b7 100644 --- a/Craftimizer/Windows/MacroEditor.cs +++ b/Craftimizer/Windows/MacroEditor.cs @@ -1217,7 +1217,9 @@ public sealed class MacroEditor : Window, IDisposable } } if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled)) - ImGui.SetTooltip("Condition Randomness"); + ImGui.SetTooltip("Condition Randomness\n" + + "Allows the condition to fluctuate randomly like a real craft.\n" + + "Turns off when generating a macro."); } else { diff --git a/Craftimizer/Windows/Settings.cs b/Craftimizer/Windows/Settings.cs index 3f9a726..4bd3482 100644 --- a/Craftimizer/Windows/Settings.cs +++ b/Craftimizer/Windows/Settings.cs @@ -641,25 +641,6 @@ public sealed class Settings : Window, IDisposable var isDirty = false; - using (var g = ImRaii.Group()) - { - using var d = ImRaii.Disabled(); - DrawOption( - "Condition Randomness", - "Allows the simulator condition to fluctuate randomly like a real craft.\n" + - "Turns off when generating a macro.", - Config.ConditionRandomness, - v => Config.ConditionRandomness = v, - ref isDirty - ); - } - if (ImGui.IsItemHovered()) - ImGui.SetTooltip("Disabled temporarily for testing"); - - ImGuiHelpers.ScaledDummy(5); - ImGui.Separator(); - ImGuiHelpers.ScaledDummy(5); - var solverConfig = Config.SimulatorSolverConfig; DrawSolverConfig(ref solverConfig, SolverConfig.SimulatorDefault, out var isSolverDirty); if (isSolverDirty) From f5b417ffc04f0e0b65f5cbccdd32fcadc6a59c85 Mon Sep 17 00:00:00 2001 From: Asriel Camora Date: Tue, 14 Nov 2023 00:57:20 -0800 Subject: [PATCH 15/15] Fix test --- Test/Solver/ActionSet.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Test/Solver/ActionSet.cs b/Test/Solver/ActionSet.cs index fd8c8a1..7d96f6a 100644 --- a/Test/Solver/ActionSet.cs +++ b/Test/Solver/ActionSet.cs @@ -83,16 +83,16 @@ public class ActionSetTests set.AddAction(ActionType.BasicSynthesis); set.AddAction(ActionType.ByregotsBlessing); set.AddAction(ActionType.DelicateSynthesis); - set.AddAction(ActionType.FocusedTouch); + set.AddAction(ActionType.Reflect); Assert.AreEqual(4, set.Count); Assert.AreEqual(ActionType.DelicateSynthesis, set.ElementAt(0)); - Assert.AreEqual(ActionType.FocusedTouch, set.ElementAt(1)); + Assert.AreEqual(ActionType.Reflect, set.ElementAt(1)); Assert.AreEqual(ActionType.ByregotsBlessing, set.ElementAt(2)); Assert.AreEqual(ActionType.BasicSynthesis, set.ElementAt(3)); - set.RemoveAction(ActionType.FocusedTouch); + set.RemoveAction(ActionType.Reflect); Assert.AreEqual(3, set.Count);