Plugin: Add Raphael

This commit is contained in:
Asriel Camora
2024-11-28 02:38:01 -08:00
parent 9a39ef936e
commit 150c2c9716
10 changed files with 265 additions and 135 deletions
+2
View File
@@ -13,7 +13,9 @@
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<ProduceReferenceAssembly>false</ProduceReferenceAssembly>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
</PropertyGroup>
+19 -1
View File
@@ -226,7 +226,25 @@ internal static class ImGuiUtils
public static void ArcProgress(float value, float radius, float ratio, uint backgroundColor, uint filledColor)
{
Arc(MathF.PI / 2, MathF.PI / 2 - MathF.Tau * Math.Clamp(value, 0, 1), radius, ratio, backgroundColor, filledColor);
float startAngle, endAngle;
// https://github.com/ocornut/imgui/commit/c895e987adf746a997b655c64a6a8916c549ff6f#diff-d750e175eb584ba76bc560b8e54cf113ccbb31dd33f75078c1588925e197a3afR1304-R1310
if (value < 0)
{
const float ArcSize = 0.15f;
startAngle = -value % 1;
endAngle = startAngle + ArcSize;
startAngle = float.Lerp(MathF.PI / 2, -3 * MathF.PI / 2, startAngle);
endAngle = float.Lerp(MathF.PI / 2, -3 * MathF.PI / 2, endAngle);
}
else
{
startAngle = MathF.PI / 2;
endAngle = MathF.PI / 2 - MathF.Tau * Math.Clamp(value, 0, 1);
}
Arc(startAngle, endAngle, radius, ratio, backgroundColor, filledColor);
}
public sealed class ViolinData
+8
View File
@@ -151,6 +151,14 @@ public sealed class Plugin : IDalamudPlugin
ClipboardWindow = new(macros);
}
public IActiveNotification DisplaySolverWarning(string text) =>
DisplayNotification(new()
{
Content = text,
Title = "Solver Warning",
Type = NotificationType.Warning
});
public IActiveNotification DisplayNotification(Notification notification)
{
var ret = Service.NotificationManager.AddNotification(notification);
+12 -4
View File
@@ -191,9 +191,11 @@ internal static class DynamicBars
var progressWidth = availSpace.Value - percentWidth - spacing;
var progressColors = Colors.GetSolverProgressColors(solver.ProgressStage);
fraction = Math.Clamp(fraction, 0, 1);
using (ImRaii.PushColor(ImGuiCol.FrameBg, progressColors.Background))
using (ImRaii.PushColor(ImGuiCol.PlotHistogram, progressColors.Foreground))
ImGui.ProgressBar(Math.Clamp(fraction, 0, 1), new(progressWidth, ImGui.GetFrameHeight()), string.Empty);
ImGui.ProgressBar(solver.IsIndeterminate ? (float)-ImGui.GetTime() : fraction, new(progressWidth, ImGui.GetFrameHeight()), string.Empty);
if (ImGui.IsItemHovered())
DrawProgressBarTooltip(solver);
ImGui.SameLine(0, spacing);
@@ -203,9 +205,15 @@ internal static class DynamicBars
public static void DrawProgressBarTooltip(Solver.Solver solver)
{
var tooltip = $"Solver Progress: {solver.ProgressValue:N0} / {solver.ProgressMax:N0}";
if (solver.ProgressValue > solver.ProgressMax)
tooltip += $"\n\nThis is taking longer than expected. Check to see if your gear stats are good and the solver settings are adequate.";
string tooltip;
if (solver.IsIndeterminate)
tooltip = "Initializing Solver";
else
{
tooltip = $"Solver Progress: {solver.ProgressValue:N0} / {solver.ProgressMax:N0}";
if (solver.ProgressValue > solver.ProgressMax)
tooltip += $"\n\nThis is taking longer than expected. Check to see if your gear stats are good and the solver settings are adequate.";
}
ImGuiUtils.TooltipWrapped(tooltip);
}
}
+40 -4
View File
@@ -1,6 +1,7 @@
using Craftimizer.Plugin;
using Craftimizer.Simulator;
using Craftimizer.Simulator.Actions;
using DotNext.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -95,6 +96,7 @@ internal sealed class SimulatedMacro
private sealed record Step
{
public ActionType Action { get; }
public bool IsEphemeral { get; }
// State *after* executing the action
public ActionResponse Response { get; private set; }
public SimulationState State { get; private set; }
@@ -107,9 +109,10 @@ internal sealed class SimulatedMacro
}
// Call recalculate after this please!
public Step(ActionType action)
public Step(ActionType action, bool isEphemeral = false)
{
Action = action;
IsEphemeral = isEphemeral;
}
public SimulationState Recalculate(Sim sim, in SimulationState lastState)
@@ -125,6 +128,7 @@ internal sealed class SimulatedMacro
};
private List<Step> Macro { get; set; } = [];
private SimulationState initialState;
public SimulationState InitialState
{
@@ -140,6 +144,7 @@ internal sealed class SimulatedMacro
}
private object QueueLock { get; } = new();
private List<Step> QueuedSteps { get; set; } = [];
private List<Step> QueuedEphemeralSteps { get; set; } = [];
public SimulationState FirstState => Macro.Count > 0 ? Macro[0].State : InitialState;
public SimulationState State => Macro.Count > 0 ? Macro[^1].State : InitialState;
@@ -158,7 +163,7 @@ internal sealed class SimulatedMacro
public Reliablity GetReliability(RecipeData recipeData, Index? idx = null) =>
Macro.Count > 0 ?
Macro[idx ?? ^1].GetReliability(InitialState, Macro.Select(m => m.Action), recipeData) :
Macro[idx ?? ^1].GetReliability(InitialState, Actions.ToArray(), recipeData) :
new(InitialState, Array.Empty<ActionType>(), 0, recipeData);
private void TryRecalculateFrom(int index)
@@ -213,6 +218,15 @@ internal sealed class SimulatedMacro
TryRecalculateFrom(Math.Min(fromIdx, toIdx));
}
public void RemoveEphemeral()
{
for (var i = Macro.Count - 1; i >= 0; --i)
{
if (Macro[i].IsEphemeral)
Macro.RemoveAt(i);
}
}
public int Enqueue(ActionType action, int? maxSize = null)
{
lock (QueueLock)
@@ -220,8 +234,25 @@ internal sealed class SimulatedMacro
if (maxSize is { } size && QueuedSteps.Count + Macro.Count >= size)
return size;
QueuedEphemeralSteps.Clear();
QueuedSteps.Add(new(action));
return QueuedSteps.Count + Macro.Count;
return Macro.Count + QueuedSteps.Count;
}
}
public int EnqueueEphemeral(IEnumerable<ActionType> actions, int? maxSize = null)
{
lock (QueueLock)
{
QueuedEphemeralSteps.Clear();
foreach (var action in actions)
{
if (maxSize is { } size && QueuedSteps.Count + QueuedEphemeralSteps.Count + Macro.Count >= size)
return size;
QueuedEphemeralSteps.Add(new(action, true));
}
return Macro.Count + QueuedSteps.Count + QueuedEphemeralSteps.Count;
}
}
@@ -230,6 +261,7 @@ internal sealed class SimulatedMacro
lock (QueueLock)
{
QueuedSteps.Clear();
QueuedEphemeralSteps.Clear();
}
}
@@ -237,11 +269,15 @@ internal sealed class SimulatedMacro
{
lock (QueueLock)
{
if (QueuedSteps.Count > 0)
if (QueuedSteps.Count > 0 || QueuedEphemeralSteps.Count > 0)
{
RemoveEphemeral();
var startIdx = Macro.Count;
Macro.AddRange(QueuedSteps);
Macro.AddRange(QueuedEphemeralSteps);
QueuedSteps.Clear();
QueuedEphemeralSteps.Clear();
TryRecalculateFrom(startIdx);
}
}
+6 -2
View File
@@ -1182,7 +1182,7 @@ public sealed class MacroEditor : Window, IDisposable
var actionBase = action.Base();
var failedAction = response != ActionResponse.UsedAction;
using var id = ImRaii.PushId(i);
if (ImGui.ImageButton(action.GetIcon(RecipeData!.ClassJob).ImGuiHandle, new(imageSize), default, Vector2.One, 0, default, failedAction ? new(1, 1, 1, ImGui.GetStyle().DisabledAlpha) : Vector4.One))
if (ImGui.ImageButton(action.GetIcon(RecipeData!.ClassJob).ImGuiHandle, new(imageSize), default, Vector2.One, 0, default, failedAction ? new(1, 1, 1, ImGui.GetStyle().DisabledAlpha) : Vector4.One) && !SolverRunning)
RemoveStep(i);
if (response is ActionResponse.ActionNotUnlocked ||
(
@@ -1550,10 +1550,14 @@ public sealed class MacroEditor : Window, IDisposable
var solver = new Solver.Solver(config, state) { Token = token };
solver.OnLog += Log.Debug;
solver.OnWarn += t => Service.Plugin.DisplaySolverWarning(t);
solver.OnNewAction += a => Macro.Enqueue(a);
solver.OnSuggestSolution += a => Macro.EnqueueEphemeral(a.Actions);
SolverObject = solver;
solver.Start();
_ = solver.GetTask().GetAwaiter().GetResult();
var t = solver.GetTask();
_ = t.ContinueWith(_ => Macro.RemoveEphemeral());
_ = t.GetAwaiter().GetResult();
token.ThrowIfCancellationRequested();
+2 -1
View File
@@ -844,7 +844,7 @@ public sealed unsafe class RecipeNote : Window, IDisposable
{
var progressColors = Colors.GetSolverProgressColors(solver.ProgressStage);
ImGuiUtils.ArcProgress(
fraction,
solver.IsIndeterminate ? (float)-ImGui.GetTime() : fraction,
windowHeight / 2f + 2,
.5f,
ImGui.ColorConvertFloat4ToU32(progressColors.Background),
@@ -1187,6 +1187,7 @@ public sealed unsafe class RecipeNote : Window, IDisposable
var solver = new Solver.Solver(config, state) { Token = token };
solver.OnLog += Log.Debug;
solver.OnWarn += t => Service.Plugin.DisplaySolverWarning(t);
BestMacroSolver = solver;
solver.Start();
var solution = solver.GetTask().GetAwaiter().GetResult();
+160 -121
View File
@@ -149,6 +149,7 @@ public sealed class Settings : Window, IDisposable
SolverAlgorithm.Stepwise => "Stepwise",
SolverAlgorithm.StepwiseForked => "Stepwise Forked",
SolverAlgorithm.StepwiseGenetic => "Stepwise Genetic",
SolverAlgorithm.Raphael => "Optimal (Slow)",
_ => "Unknown",
};
@@ -160,9 +161,12 @@ public sealed class Settings : Window, IDisposable
SolverAlgorithm.Stepwise => "Run through all iterations and pick the next best step, " +
"and repeat using previous steps as a starting point",
SolverAlgorithm.StepwiseForked => "Stepwise, but using multiple solvers simultaneously",
SolverAlgorithm.StepwiseGenetic => "Stepwise Forked, but the top N next best steps are " +
SolverAlgorithm.StepwiseGenetic => "Stepwise Forked, but the top N next best steps are " +
"selected from the solvers, and each one is equally " +
"used as a starting point",
SolverAlgorithm.Raphael => "Finds the best solution, every time. This solver has " +
"very different options compared to the rest, as it " +
"is designed using an entirely different algorithm.",
_ => "Unknown"
};
@@ -527,118 +531,140 @@ public sealed class Settings : Window, IDisposable
ref isDirty
);
DrawOption(
"Target Iterations",
"The total number of iterations to run per crafting step. " +
"Higher values require more computational power. Higher values " +
"also may decrease variance, so other values should be tweaked " +
"as necessary to get a more favorable outcome.",
config.Iterations,
1000,
1000000,
v => config = config with { Iterations = v },
ref isDirty
);
DrawOption(
"Max Iterations",
"The solver may go about the target iteration value if the craft " +
"is sufficiently difficult, and it wasn't able to find any way to " +
"complete it yet. In rare cases, the solver might go on for a very " +
"long time. This maximum is here to prevent the solver from stealing " +
"all your RAM.",
config.MaxIterations,
config.Iterations,
5000000,
v => config = config with { MaxIterations = v },
ref isDirty
);
DrawOption(
"Max Step Count",
"The maximum number of crafting steps; this is generally the only " +
"setting you should change, and it should be set to around 5 steps " +
"more than what you'd expect. If this value is too low, the solver " +
"won't learn much per iteration; too high and it will waste time " +
"on useless extra steps.",
config.MaxStepCount,
1,
100,
v => config = config with { MaxStepCount = v },
ref isDirty
);
DrawOption(
"Exploration Constant",
"A constant that decides how often the solver will explore new, " +
"possibly good paths. If this value is too high, " +
"moves will mostly be decided at random.",
config.ExplorationConstant,
0,
10,
v => config = config with { ExplorationConstant = v },
ref isDirty
);
DrawOption(
"Score Weighting Constant",
"A constant ranging from 0 to 1 that configures how the solver " +
"scores and picks paths to travel to next. A value of 0 means " +
"actions will be chosen based on their average outcome, whereas " +
"1 uses their best outcome achieved so far.",
config.MaxScoreWeightingConstant,
0,
1,
v => config = config with { MaxScoreWeightingConstant = v },
ref isDirty
);
using (var d = ImRaii.Disabled(config.Algorithm is not (SolverAlgorithm.OneshotForked or SolverAlgorithm.StepwiseForked or SolverAlgorithm.StepwiseGenetic)))
if (config.Algorithm != SolverAlgorithm.Raphael)
{
DrawOption(
"Max Core Count",
"The number of cores to use when solving. You should use as many " +
"as you can. If it's too high, it will have an effect on your gameplay " +
$"experience. A good estimate would be 1 or 2 cores less than your " +
$"system (FYI, you have {Environment.ProcessorCount} cores), but make sure to accomodate " +
$"for any other tasks you have in the background, if you have any.\n" +
"(Only used in the Forked and Genetic algorithms)",
config.MaxThreadCount,
1,
Environment.ProcessorCount,
v => config = config with { MaxThreadCount = v },
"Target Iterations",
"The total number of iterations to run per crafting step. " +
"Higher values require more computational power. Higher values " +
"also may decrease variance, so other values should be tweaked " +
"as necessary to get a more favorable outcome.",
config.Iterations,
1000,
1000000,
v => config = config with { Iterations = v },
ref isDirty
);
using (var d = ImRaii.Disabled(config.Algorithm is not (SolverAlgorithm.OneshotForked or SolverAlgorithm.StepwiseForked or SolverAlgorithm.StepwiseGenetic)))
DrawOption(
"Fork Count",
"Split the number of iterations across different solvers. In general, " +
"you should increase this value to at least the number of cores in " +
$"your system (FYI, you have {Environment.ProcessorCount} cores) to attain the most speedup. " +
"The higher the number, the more chance you have of finding a " +
"better local maximum; this concept similar but not equivalent " +
"to the exploration constant.\n" +
"(Only used in the Forked and Genetic algorithms)",
config.ForkCount,
1,
500,
v => config = config with { ForkCount = v },
"Max Iterations",
"The solver may go about the target iteration value if the craft " +
"is sufficiently difficult, and it wasn't able to find any way to " +
"complete it yet. In rare cases, the solver might go on for a very " +
"long time. This maximum is here to prevent the solver from stealing " +
"all your RAM.",
config.MaxIterations,
config.Iterations,
5000000,
v => config = config with { MaxIterations = v },
ref isDirty
);
using (var d = ImRaii.Disabled(config.Algorithm is not SolverAlgorithm.StepwiseGenetic))
DrawOption(
"Elitist Action Count",
"On every craft step, pick this many top solutions and use them as " +
"the input for the next craft step. For best results, use Fork Count / 2 " +
"and add about 1 or 2 more if needed.\n" +
"(Only used in the Stepwise Genetic algorithm)",
config.FurcatedActionCount,
"Max Step Count",
"The maximum number of crafting steps; this is generally the only " +
"setting you should change, and it should be set to around 5 steps " +
"more than what you'd expect. If this value is too low, the solver " +
"won't learn much per iteration; too high and it will waste time " +
"on useless extra steps.",
config.MaxStepCount,
1,
500,
v => config = config with { FurcatedActionCount = v },
100,
v => config = config with { MaxStepCount = v },
ref isDirty
);
DrawOption(
"Exploration Constant",
"A constant that decides how often the solver will explore new, " +
"possibly good paths. If this value is too high, " +
"moves will mostly be decided at random.",
config.ExplorationConstant,
0,
10,
v => config = config with { ExplorationConstant = v },
ref isDirty
);
DrawOption(
"Score Weighting Constant",
"A constant ranging from 0 to 1 that configures how the solver " +
"scores and picks paths to travel to next. A value of 0 means " +
"actions will be chosen based on their average outcome, whereas " +
"1 uses their best outcome achieved so far.",
config.MaxScoreWeightingConstant,
0,
1,
v => config = config with { MaxScoreWeightingConstant = v },
ref isDirty
);
using (ImRaii.Disabled(config.Algorithm is not (SolverAlgorithm.OneshotForked or SolverAlgorithm.StepwiseForked or SolverAlgorithm.StepwiseGenetic)))
DrawOption(
"Max Core Count",
"The number of cores to use when solving. You should use as many " +
"as you can. If it's too high, it will have an effect on your gameplay " +
$"experience. A good estimate would be 1 or 2 cores less than your " +
$"system (FYI, you have {Environment.ProcessorCount} cores), but make sure to accomodate " +
$"for any other tasks you have in the background, if you have any.\n" +
"(Only used in the Forked and Genetic algorithms)",
config.MaxThreadCount,
1,
Environment.ProcessorCount,
v => config = config with { MaxThreadCount = v },
ref isDirty
);
using (ImRaii.Disabled(config.Algorithm is not (SolverAlgorithm.OneshotForked or SolverAlgorithm.StepwiseForked or SolverAlgorithm.StepwiseGenetic)))
DrawOption(
"Fork Count",
"Split the number of iterations across different solvers. In general, " +
"you should increase this value to at least the number of cores in " +
$"your system (FYI, you have {Environment.ProcessorCount} cores) to attain the most speedup. " +
"The higher the number, the more chance you have of finding a " +
"better local maximum; this concept similar but not equivalent " +
"to the exploration constant.\n" +
"(Only used in the Forked and Genetic algorithms)",
config.ForkCount,
1,
500,
v => config = config with { ForkCount = v },
ref isDirty
);
using (ImRaii.Disabled(config.Algorithm is not SolverAlgorithm.StepwiseGenetic))
DrawOption(
"Elitist Action Count",
"On every craft step, pick this many top solutions and use them as " +
"the input for the next craft step. For best results, use Fork Count / 2 " +
"and add about 1 or 2 more if needed.\n" +
"(Only used in the Stepwise Genetic algorithm)",
config.FurcatedActionCount,
1,
500,
v => config = config with { FurcatedActionCount = v },
ref isDirty
);
}
else
{
DrawOption(
"Ensure 100% Reliability",
"Find a rotation that can reach the target quality no matter " +
"how unlucky the random conditions are.",
config.Adversarial,
v => config = config with { Adversarial = v },
ref isDirty
);
DrawOption(
"Backload Progress",
"Find a rotation that only uses Progress-increasing actions " +
"at the end of the rotation. May speed up solve times.",
config.BackloadProgress,
v => config = config with { BackloadProgress = v },
ref isDirty
);
}
}
using (var panel = ImRaii2.GroupPanel("Action Pool", -1, out var poolWidth))
@@ -658,27 +684,40 @@ public sealed class Settings : Window, IDisposable
using (var panel = ImRaii2.GroupPanel("Advanced", -1, out _))
{
DrawOption(
"Max Rollout Step Count",
"The maximum number of crafting steps every iteration can consider. " +
"Decreasing this value can have unintended side effects. Only change " +
"this value if you absolutely know what you're doing.",
config.MaxRolloutStepCount,
1,
50,
v => config = config with { MaxRolloutStepCount = v },
ref isDirty
);
if (config.Algorithm != SolverAlgorithm.Raphael)
{
DrawOption(
"Max Rollout Step Count",
"The maximum number of crafting steps every iteration can consider. " +
"Decreasing this value can have unintended side effects. Only change " +
"this value if you absolutely know what you're doing.",
config.MaxRolloutStepCount,
1,
50,
v => config = config with { MaxRolloutStepCount = v },
ref isDirty
);
DrawOption(
"Strict Actions",
"When finding the next possible actions to execute, use a heuristic " +
"to restrict which actions to attempt taking. This results in a much " +
"better macro at the cost of not finding an extremely creative one.",
config.StrictActions,
v => config = config with { StrictActions = v },
ref isDirty
);
DrawOption(
"Strict Actions",
"When finding the next possible actions to execute, use a heuristic " +
"to restrict which actions to attempt taking. This results in a much " +
"better macro at the cost of not finding an extremely creative one.",
config.StrictActions,
v => config = config with { StrictActions = v },
ref isDirty
);
}
else
{
DrawOption(
"Unsound Branch Pruning",
"TBD",
config.UnsoundBranchPruning,
v => config = config with { UnsoundBranchPruning = v },
ref isDirty
);
}
}
using (var panel = ImRaii2.GroupPanel("Score Weights (Advanced)", -1, out _))
+1
View File
@@ -611,6 +611,7 @@ public sealed unsafe class SynthHelper : Window, IDisposable
var solver = new Solver.Solver(config, state) { Token = token };
solver.OnLog += Log.Debug;
solver.OnWarn += t => Service.Plugin.DisplaySolverWarning(t);
solver.OnNewAction += EnqueueAction;
SolverObject = solver;
solver.Start();
+14 -1
View File
@@ -28,6 +28,11 @@
"System.IO.Hashing": "8.0.0"
}
},
"Raphael.Net": {
"type": "Transitive",
"resolved": "1.0.0",
"contentHash": "J/D1pw2MT52hENysYs7SA++PBT4sHwUcPnTzjb9/lLigNJazob4v7WbBe2enKpfDAI6hO8al7p2Qlt2258fk8g=="
},
"System.IO.Hashing": {
"type": "Transitive",
"resolved": "8.0.0",
@@ -40,9 +45,17 @@
"type": "Project",
"dependencies": {
"Craftimizer.Simulator": "[1.0.0, )",
"DotNext": "[5.14.0, )"
"DotNext": "[5.14.0, )",
"Raphael.Net": "[1.0.0, )"
}
}
},
"net8.0-windows7.0/win-x64": {
"Raphael.Net": {
"type": "Transitive",
"resolved": "1.0.0",
"contentHash": "J/D1pw2MT52hENysYs7SA++PBT4sHwUcPnTzjb9/lLigNJazob4v7WbBe2enKpfDAI6hO8al7p2Qlt2258fk8g=="
}
}
}
}