Move simulation and macro state to separate class

This commit is contained in:
Asriel Camora
2023-11-15 01:09:03 -08:00
parent eceb8a1182
commit 2549bb47ae
2 changed files with 251 additions and 196 deletions
+234
View File
@@ -0,0 +1,234 @@
using Craftimizer.Plugin;
using Craftimizer.Simulator;
using Craftimizer.Simulator.Actions;
using System;
using System.Collections.Generic;
using System.Linq;
using Sim = Craftimizer.Simulator.Simulator;
using SimNoRandom = Craftimizer.Simulator.SimulatorNoRandom;
namespace Craftimizer.Utils;
internal sealed class SimulatedMacro
{
public readonly record struct Reliablity
{
public sealed class Param
{
private List<int> 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 Param()
{
DataList = new();
}
public void Add(int value)
{
DataList.Add(value);
}
public void FinalizeData()
{
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
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 Param Progress = new();
public readonly Param Quality = new();
// Param is either collectability, quality, or hq%, depending on the recipe
public readonly Param ParamScore = new();
public Reliablity(in SimulationState startState, IEnumerable<ActionType> actions, int iterCount, RecipeData recipeData)
{
Func<SimulationState, int> getParam;
if (recipeData.Recipe.ItemResult.Value!.IsCollectable)
getParam = s => s.Collectability;
else if (recipeData.Recipe.RequiredQuality > 0)
{
var reqQual = recipeData.Recipe.RequiredQuality;
getParam = s => (int)((float)s.Quality / reqQual * 100);
}
else if (recipeData.RecipeInfo.MaxQuality > 0)
getParam = s => s.HQPercent;
else
getParam = s => 0;
for (var i = 0; i < iterCount; ++i)
{
var sim = new Sim();
var (_, state, _) = sim.ExecuteMultiple(startState, actions);
Progress.Add(state.Progress);
Quality.Add(state.Quality);
ParamScore.Add(getParam(state));
}
Progress.FinalizeData();
Quality.FinalizeData();
ParamScore.FinalizeData();
}
}
private sealed record Step
{
public ActionType Action { get; }
// State *after* executing the action
public ActionResponse Response { get; private set; }
public SimulationState State { get; private set; }
private Reliablity? Reliability { get; set; }
public Step(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 Reliablity GetReliability(in SimulationState initialState, IEnumerable<ActionType> actionSet, RecipeData recipeData) =>
Reliability ??=
new(initialState, actionSet, Service.Configuration.ReliabilitySimulationCount, recipeData);
};
private List<Step> Macro { get; set; } = new();
private SimulationState initialState;
public SimulationState InitialState
{
get => initialState;
set
{
if (initialState != value)
{
initialState = value;
RecalculateState();
}
}
}
private object QueueLock { get; } = new();
private List<Step> QueuedSteps { get; set; } = new();
public SimulationState State => Macro.Count > 0 ? Macro[^1].State : InitialState;
public IEnumerable<ActionType> Actions => Macro.Select(m => m.Action);
public int Count => Macro.Count;
public (ActionType Action, ActionResponse Response, SimulationState State) this[int i]
{
get
{
var step = Macro[i];
return (step.Action, step.Response, step.State);
}
}
public Reliablity GetReliability(RecipeData recipeData) =>
Macro.Count > 0 ?
Macro[^1].GetReliability(InitialState, Macro.Select(m => m.Action), recipeData) :
new(InitialState, Array.Empty<ActionType>(), 0, recipeData);
public void RemoveRange(int index, int count) =>
Macro.RemoveRange(index, count);
public void Clear() =>
Macro.Clear();
public void Add(ActionType action)
{
var sim = CreateSim();
Macro.Add(new(action, sim, State, out _));
}
public void Insert(int index, ActionType action)
{
if (index < 0 || index >= Macro.Count)
throw new ArgumentOutOfRangeException(nameof(index));
var state = index == 0 ? InitialState : Macro[index - 1].State;
var sim = CreateSim();
Macro.Insert(index, new(action, sim, state, out state));
for (var i = index + 1; i < Macro.Count; i++)
state = Macro[i].Recalculate(sim, state);
}
public void RemoveAt(int index)
{
if (index < 0 || index >= Macro.Count)
throw new ArgumentOutOfRangeException(nameof(index));
Macro.RemoveAt(index);
var state = index == 0 ? InitialState : Macro[index - 1].State;
var sim = CreateSim();
for (var i = index; i < Macro.Count; i++)
state = Macro[i].Recalculate(sim, state);
}
public void Enqueue(ActionType action)
{
lock (QueueLock)
{
var lastState = QueuedSteps.Count > 0 ? QueuedSteps[^1].State : State;
QueuedSteps.Add(new(action, CreateSim(), lastState, out _));
}
}
public void ClearQueue()
{
lock (QueueLock)
{
QueuedSteps.Clear();
}
}
public void FlushQueue()
{
lock (QueueLock)
{
if (QueuedSteps.Count > 0)
{
Macro.AddRange(QueuedSteps);
QueuedSteps.Clear();
}
}
}
public void RecalculateState()
{
var sim = CreateSim();
var lastState = InitialState;
for (var i = 0; i < Macro.Count; i++)
lastState = Macro[i].Recalculate(sim, lastState);
}
private static Sim CreateSim() =>
Service.Configuration.ConditionRandomness ? new Sim() : new SimNoRandom();
}
+17 -196
View File
@@ -77,125 +77,16 @@ public sealed class MacroEditor : Window, IDisposable
private List<int> HQIngredientCounts { get; set; } private List<int> HQIngredientCounts { get; set; }
private int StartingQuality => RecipeData.CalculateStartingQuality(HQIngredientCounts); private int StartingQuality => RecipeData.CalculateStartingQuality(HQIngredientCounts);
private readonly record struct SimulationReliablity private SimulatedMacro Macro { get; set; } = new();
{ private SimulationState State => Macro.State;
public sealed class ParamReliability private SimulatedMacro.Reliablity Reliability => Macro.GetReliability(RecipeData);
{
private List<int> 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()
{
DataList = new();
}
public void Add(int value)
{
DataList.Add(value);
}
public void FinalizeData()
{
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
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();
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<ActionType> actions, int iterCount, RecipeData recipeData)
{
Func<SimulationState, int> getParam;
if (recipeData.Recipe.ItemResult.Value!.IsCollectable)
getParam = s => s.Collectability;
else if (recipeData.Recipe.RequiredQuality > 0)
{
var reqQual = recipeData.Recipe.RequiredQuality;
getParam = s => (int)((float)s.Quality / reqQual * 100);
}
else if (recipeData.RecipeInfo.MaxQuality > 0)
getParam = s => s.HQPercent;
else
getParam = s => 0;
for (var i = 0; i < iterCount; ++i)
{
var sim = new Sim();
var (_, state, _) = sim.ExecuteMultiple(startState, actions);
Progress.Add(state.Progress);
Quality.Add(state.Quality);
Param.Add(getParam(state));
}
Progress.FinalizeData();
Quality.FinalizeData();
Param.FinalizeData();
}
}
private sealed record SimulatedActionStep
{
public ActionType Action { get; }
// State *after* executing the action
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<ActionType> actionSet, RecipeData recipeData) =>
Reliability ??=
new(initialState, actionSet, Service.Configuration.ReliabilitySimulationCount, recipeData);
};
private List<SimulatedActionStep> 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].GetReliability(InitialState, Macro.Select(m => m.Action), RecipeData) : new(InitialState, Array.Empty<ActionType>(), 0, RecipeData);
private ActionType[] DefaultActions { get; } private ActionType[] DefaultActions { get; }
private Action<IEnumerable<ActionType>>? MacroSetter { get; set; } private Action<IEnumerable<ActionType>>? MacroSetter { get; set; }
private CancellationTokenSource? SolverTokenSource { get; set; } private CancellationTokenSource? SolverTokenSource { get; set; }
private Exception? SolverException { get; set; } private Exception? SolverException { get; set; }
private int? SolverStartStepCount { get; set; } private int? SolverStartStepCount { get; set; }
private object? SolverQueueLock { get; set; }
private List<SimulatedActionStep>? SolverQueuedSteps { get; set; }
private Solver.Solver? SolverObject { get; set; } private Solver.Solver? SolverObject { get; set; }
private bool SolverRunning => SolverTokenSource != null; private bool SolverRunning => SolverTokenSource != null;
@@ -263,7 +154,7 @@ public sealed class MacroEditor : Window, IDisposable
public override void Update() public override void Update()
{ {
TryFlushSolvedSteps(); Macro.FlushQueue();
} }
public override void Draw() public override void Draw()
@@ -1100,14 +991,14 @@ public sealed class MacroEditor : Window, IDisposable
new("Condition", default, null, 0, 0, null, State.Condition) new("Condition", default, null, 0, 0, null, State.Condition)
}; };
if (RecipeData.Recipe.ItemResult.Value!.IsCollectable) if (RecipeData.Recipe.ItemResult.Value!.IsCollectable)
datas.Add(new("Collectability", Colors.HQ, Reliability.Param, State.Collectability, State.MaxCollectability, $"{State.Collectability}", null)); datas.Add(new("Collectability", Colors.HQ, Reliability.ParamScore, State.Collectability, State.MaxCollectability, $"{State.Collectability}", null));
else if (RecipeData.Recipe.RequiredQuality > 0) else if (RecipeData.Recipe.RequiredQuality > 0)
{ {
var qualityPercent = (float)State.Quality / RecipeData.Recipe.RequiredQuality * 100; var qualityPercent = (float)State.Quality / RecipeData.Recipe.RequiredQuality * 100;
datas.Add(new("Quality %%", Colors.HQ, Reliability.Param, qualityPercent, 100, $"{qualityPercent:0}%", null)); datas.Add(new("Quality %%", Colors.HQ, Reliability.ParamScore, qualityPercent, 100, $"{qualityPercent:0}%", null));
} }
else if (RecipeData.RecipeInfo.MaxQuality > 0) 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.ParamScore, State.HQPercent, 100, $"{State.HQPercent}%", null));
DrawBars(datas); DrawBars(datas);
ImGui.TableNextColumn(); ImGui.TableNextColumn();
@@ -1162,7 +1053,7 @@ public sealed class MacroEditor : Window, IDisposable
} }
} }
private readonly record struct BarData(string Name, Vector4 Color, SimulationReliablity.ParamReliability? Reliability, float Value, float Max, string? Caption, Condition? Condition); private readonly record struct BarData(string Name, Vector4 Color, SimulatedMacro.Reliablity.Param? Reliability, float Value, float Max, string? Caption, Condition? Condition);
private void DrawBars(IEnumerable<BarData> bars) private void DrawBars(IEnumerable<BarData> bars)
{ {
var spacing = ImGui.GetStyle().ItemSpacing.X; var spacing = ImGui.GetStyle().ItemSpacing.X;
@@ -1264,7 +1155,7 @@ public sealed class MacroEditor : Window, IDisposable
{ {
var spacing = ImGui.GetStyle().ItemSpacing.X; var spacing = ImGui.GetStyle().ItemSpacing.X;
var imageSize = ImGui.GetFrameHeight() * 2; var imageSize = ImGui.GetFrameHeight() * 2;
var lastState = InitialState; var lastState = Macro.InitialState;
using var panel = ImRaii2.GroupPanel("Macro", -1, out var availSpace); using var panel = ImRaii2.GroupPanel("Macro", -1, out var availSpace);
ImGui.Dummy(new(0, imageSize)); ImGui.Dummy(new(0, imageSize));
@@ -1378,7 +1269,7 @@ public sealed class MacroEditor : Window, IDisposable
} }
ImGui.SameLine(); ImGui.SameLine();
if (ImGuiUtils.IconButtonSquare(FontAwesomeIcon.Paste)) if (ImGuiUtils.IconButtonSquare(FontAwesomeIcon.Paste))
Service.Plugin.CopyMacro(Macro.Select(s => s.Action).ToArray()); Service.Plugin.CopyMacro(Macro.Actions.ToArray());
if (ImGui.IsItemHovered()) if (ImGui.IsItemHovered())
ImGui.SetTooltip("Copy to Clipboard"); ImGui.SetTooltip("Copy to Clipboard");
ImGui.SameLine(); ImGui.SameLine();
@@ -1438,7 +1329,7 @@ public sealed class MacroEditor : Window, IDisposable
{ {
if (!string.IsNullOrWhiteSpace(popupSaveAsMacroName)) if (!string.IsNullOrWhiteSpace(popupSaveAsMacroName))
{ {
var newMacro = new Macro() { Name = popupSaveAsMacroName, Actions = Macro.Select(s => s.Action).ToArray() }; var newMacro = new Macro() { Name = popupSaveAsMacroName, Actions = Macro.Actions.ToArray() };
Service.Configuration.AddMacro(newMacro); Service.Configuration.AddMacro(newMacro);
MacroSetter = actions => MacroSetter = actions =>
{ {
@@ -1606,16 +1497,7 @@ public sealed class MacroEditor : Window, IDisposable
SolverTokenSource?.Cancel(); SolverTokenSource?.Cancel();
SolverTokenSource = new(); SolverTokenSource = new();
SolverException = null; SolverException = null;
if (SolverQueueLock is { }) Macro.ClearQueue();
{
lock (SolverQueueLock)
{
SolverQueuedSteps!.Clear();
SolverQueueLock = null;
}
}
SolverQueueLock = new();
SolverQueuedSteps ??= new();
RevertPreviousMacro(); RevertPreviousMacro();
@@ -1665,7 +1547,7 @@ public sealed class MacroEditor : Window, IDisposable
using (SolverObject = new Solver.Solver(config, state) { Token = token }) using (SolverObject = new Solver.Solver(config, state) { Token = token })
{ {
SolverObject.OnLog += Log.Debug; SolverObject.OnLog += Log.Debug;
SolverObject.OnNewAction += QueueSolverStep; SolverObject.OnNewAction += Macro.Enqueue;
SolverObject.Start(); SolverObject.Start();
_ = SolverObject.GetTask().GetAwaiter().GetResult(); _ = SolverObject.GetTask().GetAwaiter().GetResult();
} }
@@ -1681,95 +1563,34 @@ public sealed class MacroEditor : Window, IDisposable
private void SaveMacro() private void SaveMacro()
{ {
MacroSetter?.Invoke(Macro.Select(s => s.Action)); MacroSetter?.Invoke(Macro.Actions);
} }
private void RecalculateState() private void RecalculateState()
{ {
InitialState = new SimulationState(new(CharacterStats, RecipeData.RecipeInfo, StartingQuality)); Macro.InitialState = new SimulationState(new(CharacterStats, RecipeData.RecipeInfo, StartingQuality));
var sim = CreateSim();
var lastState = InitialState;
for (var i = 0; i < Macro.Count; i++)
lastState = Macro[i].Recalculate(sim, lastState);
} }
private static Sim CreateSim() =>
Service.Configuration.ConditionRandomness ? new Sim() : new SimNoRandom();
private static Sim CreateSim(in SimulationState state) => private static Sim CreateSim(in SimulationState state) =>
Service.Configuration.ConditionRandomness ? new Sim() { State = state } : new SimNoRandom() { State = state }; Service.Configuration.ConditionRandomness ? new Sim() { State = state } : new SimNoRandom() { State = state };
private void AddStep(ActionType action, int index = -1) private void AddStep(ActionType action)
{ {
if (index < -1 || index >= Macro.Count)
throw new ArgumentOutOfRangeException(nameof(index));
if (SolverRunning) if (SolverRunning)
throw new InvalidOperationException("Cannot add steps while solver is running"); throw new InvalidOperationException("Cannot add steps while solver is running");
if (!SolverRunning) if (!SolverRunning)
SolverStartStepCount = null; SolverStartStepCount = null;
if (index == -1) Macro.Add(action);
{
var sim = CreateSim();
Macro.Add(new(action, sim, State, out _));
}
else
{
var state = index == 0 ? InitialState : Macro[index - 1].State;
var sim = CreateSim();
Macro.Insert(index, new(action, sim, state, out state));
for (var i = index + 1; i < Macro.Count; i++)
state = Macro[i].Recalculate(sim, state);
}
}
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) private void RemoveStep(int index)
{ {
if (index < 0 || index >= Macro.Count)
throw new ArgumentOutOfRangeException(nameof(index));
if (SolverRunning) if (SolverRunning)
throw new InvalidOperationException("Cannot remove steps while solver is running"); throw new InvalidOperationException("Cannot remove steps while solver is running");
SolverStartStepCount = null; SolverStartStepCount = null;
Macro.RemoveAt(index); Macro.RemoveAt(index);
var state = index == 0 ? InitialState : Macro[index - 1].State;
var sim = CreateSim();
for (var i = index; i < Macro.Count; i++)
state = Macro[i].Recalculate(sim, state);
} }
public void Dispose() public void Dispose()