Split synth helper, use ienumerator to manage state
This commit is contained in:
+10
-272
@@ -1,30 +1,14 @@
|
||||
using Craftimizer.Plugin.Utils;
|
||||
using Craftimizer.Simulator;
|
||||
using Craftimizer.Simulator.Actions;
|
||||
using Craftimizer.Utils;
|
||||
using Dalamud.Game;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Interface.Windowing;
|
||||
using Dalamud.Logging;
|
||||
using Dalamud.Memory;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using ImGuiNET;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ActionType = Craftimizer.Simulator.Actions.ActionType;
|
||||
using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.ValueType;
|
||||
|
||||
namespace Craftimizer.Plugin.Windows;
|
||||
|
||||
public sealed unsafe class Craft : Window, IDisposable
|
||||
public sealed unsafe partial class Craft : Window, IDisposable
|
||||
{
|
||||
private const ImGuiWindowFlags WindowFlags = ImGuiWindowFlags.NoDecoration
|
||||
| ImGuiWindowFlags.AlwaysAutoResize
|
||||
@@ -39,26 +23,6 @@ public sealed unsafe class Craft : Window, IDisposable
|
||||
|
||||
private bool WasOpen { get; set; }
|
||||
|
||||
private CharacterStats CharacterStats { get; set; } = null!;
|
||||
private SimulationInput Input { get; set; } = null!;
|
||||
private int ActionCount { get; set; }
|
||||
private ActionStates ActionStates { get; set; }
|
||||
|
||||
// Set to true if we used an action, but it's not reflected in the addon yet
|
||||
private bool IsIntermediate { get; set; }
|
||||
private SimulationState IntermediateState { get; set; }
|
||||
|
||||
private SimulationState? SolverState { get; set; }
|
||||
private Task? SolverTask { get; set; }
|
||||
private CancellationTokenSource? SolverTaskToken { get; set; }
|
||||
private ConcurrentQueue<ActionType> SolverActionQueue { get; } = new();
|
||||
|
||||
// State is the state of the simulation *after* its corresponding action is executed.
|
||||
private List<(ActionType Action, string Tooltip, ActionResponse Response, SimulationState State)> SolverActions { get; } = new();
|
||||
private SimulatorNoRandom SolverSim { get; set; } = null!;
|
||||
|
||||
private SimulationState SolverLatestState => SolverActions.Count == 0 ? SolverState!.Value : SolverActions[^1].State;
|
||||
|
||||
public Craft() : base("Craftimizer SynthesisHelper", WindowFlags, true)
|
||||
{
|
||||
Service.WindowSystem.AddWindow(this);
|
||||
@@ -69,15 +33,16 @@ public sealed unsafe class Craft : Window, IDisposable
|
||||
|
||||
public override void Draw()
|
||||
{
|
||||
while (SolverActionQueue.TryDequeue(out var poppedAction))
|
||||
AppendGeneratedAction(poppedAction);
|
||||
SolveTick();
|
||||
DequeueSolver();
|
||||
|
||||
DrawActions();
|
||||
|
||||
ImGui.SameLine(0, 0);
|
||||
ImGui.Dummy(default);
|
||||
ImGui.BeginDisabled(!(SolverTask?.IsCompleted ?? true) || IsIntermediate);
|
||||
ImGui.BeginDisabled(!(SolverTask?.IsCompleted ?? true));
|
||||
if (ImGui.Button("Retry"))
|
||||
QueueSolve(CreateSimulationState());
|
||||
QueueSolve(GetNextState()!.Value);
|
||||
ImGui.EndDisabled();
|
||||
}
|
||||
|
||||
@@ -95,7 +60,7 @@ public sealed unsafe class Craft : Window, IDisposable
|
||||
ImGui.SameLine(0, 0);
|
||||
for (var i = 0; i < SolverActions.Count; ++i)
|
||||
{
|
||||
var (action, tooltip, _, state) = SolverActions[i];
|
||||
var (action, tooltip, state) = SolverActions[i];
|
||||
ImGui.PushID(i);
|
||||
if (ImGui.ImageButton(action.GetIcon(RecipeUtils.ClassJob).ImGuiHandle, actionSize, Vector2.Zero, Vector2.One, 0))
|
||||
{
|
||||
@@ -139,18 +104,6 @@ public sealed unsafe class Craft : Window, IDisposable
|
||||
if (Input == null)
|
||||
return;
|
||||
|
||||
var addonState = CreateSimulationState();
|
||||
if (IsIntermediate)
|
||||
{
|
||||
if (StatesEqualExceptSome(addonState, IntermediateState))
|
||||
return;
|
||||
|
||||
IsIntermediate = false;
|
||||
}
|
||||
|
||||
if (SolverState != addonState)
|
||||
QueueSolve(addonState);
|
||||
|
||||
base.PreDraw();
|
||||
}
|
||||
|
||||
@@ -159,6 +112,9 @@ public sealed unsafe class Craft : Window, IDisposable
|
||||
if (!RecipeUtils.HasValidRecipe)
|
||||
return false;
|
||||
|
||||
if (!RecipeUtils.IsCrafting)
|
||||
return false;
|
||||
|
||||
if (RecipeUtils.AddonSynthesis == null)
|
||||
return false;
|
||||
|
||||
@@ -185,64 +141,6 @@ public sealed unsafe class Craft : Window, IDisposable
|
||||
return ret;
|
||||
}
|
||||
|
||||
private void StopSolve()
|
||||
{
|
||||
if (SolverTask == null || SolverTaskToken == null)
|
||||
return;
|
||||
|
||||
if (!SolverTask.IsCompleted)
|
||||
SolverTaskToken.Cancel();
|
||||
else
|
||||
{
|
||||
SolverTaskToken.Dispose();
|
||||
SolverTask.Dispose();
|
||||
|
||||
SolverTask = null;
|
||||
SolverTaskToken = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void QueueSolve(SimulationState state)
|
||||
{
|
||||
StopSolve();
|
||||
|
||||
SolverActionQueue.Clear();
|
||||
SolverActions.Clear();
|
||||
SolverState = state;
|
||||
SolverSim = new(state);
|
||||
|
||||
SolverTaskToken = new();
|
||||
SolverTask = Task.Run(() => Config.SolverAlgorithm.Invoke(Config.SolverConfig, state, SolverActionQueue.Enqueue, SolverTaskToken.Token));
|
||||
}
|
||||
|
||||
private void AppendGeneratedAction(ActionType action)
|
||||
{
|
||||
var actionBase = action.Base();
|
||||
if (actionBase is BaseComboAction comboActionBase)
|
||||
{
|
||||
AppendGeneratedAction(comboActionBase.ActionTypeA);
|
||||
AppendGeneratedAction(comboActionBase.ActionTypeB);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (SolverActions.Count >= Config.SynthesisHelperStepCount)
|
||||
{
|
||||
StopSolve();
|
||||
return;
|
||||
}
|
||||
|
||||
var tooltip = actionBase.GetTooltip(SolverSim, false);
|
||||
var (response, state) = SolverSim.Execute(SolverLatestState, action);
|
||||
SolverActions.Add((action, tooltip, response, state));
|
||||
|
||||
if (SolverActions.Count >= Config.SynthesisHelperStepCount)
|
||||
{
|
||||
StopSolve();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ResetSimulation()
|
||||
{
|
||||
var container = InventoryManager.Instance()->GetInventoryContainer(InventoryType.EquippedItems);
|
||||
@@ -253,166 +151,6 @@ public sealed unsafe class Craft : Window, IDisposable
|
||||
Input = new(CharacterStats, RecipeUtils.Info, 0, Random);
|
||||
ActionCount = 0;
|
||||
ActionStates = new();
|
||||
IsIntermediate = false;
|
||||
}
|
||||
|
||||
private sealed class AddonValues
|
||||
{
|
||||
public AddonSynthesis* Addon { get; }
|
||||
public AtkValue* Values => Addon->AtkUnitBase.AtkValues;
|
||||
public ushort ValueCount => Addon->AtkUnitBase.AtkValuesCount;
|
||||
|
||||
public AddonValues(AddonSynthesis* addon)
|
||||
{
|
||||
Addon = addon;
|
||||
if (ValueCount != 26)
|
||||
throw new ArgumentException("AddonSynthesis must have 26 AtkValues", nameof(addon));
|
||||
}
|
||||
|
||||
public unsafe AtkValue* this[int i] => Values + i;
|
||||
|
||||
// Always 0?
|
||||
private uint Unk0 => GetUInt(0);
|
||||
// Always true?
|
||||
private bool Unk1 => GetBool(1);
|
||||
|
||||
public SeString ItemName => GetString(2);
|
||||
public uint ItemIconId => GetUInt(3);
|
||||
public uint ItemCount => GetUInt(4);
|
||||
public uint Progress => GetUInt(5);
|
||||
public uint MaxProgress => GetUInt(6);
|
||||
public uint Durability => GetUInt(7);
|
||||
public uint MaxDurability => GetUInt(8);
|
||||
public uint Quality => GetUInt(9);
|
||||
public uint HQChance => GetUInt(10);
|
||||
private uint IsShowingCollectibleInfoValue => GetUInt(11);
|
||||
private uint ConditionValue => GetUInt(12);
|
||||
public SeString ConditionName => GetString(13);
|
||||
public SeString ConditionNameAndTooltip => GetString(14);
|
||||
public uint StepCount => GetUInt(15);
|
||||
public uint ResultItemId => GetUInt(16);
|
||||
public uint MaxQuality => GetUInt(17);
|
||||
public uint RequiredQuality => GetUInt(18);
|
||||
private uint IsCollectibleValue => GetUInt(19);
|
||||
public uint Collectability => GetUInt(20);
|
||||
public uint MaxCollectability => GetUInt(21);
|
||||
public uint CollectabilityCheckpoint1 => GetUInt(22);
|
||||
public uint CollectabilityCheckpoint2 => GetUInt(23);
|
||||
public uint CollectabilityCheckpoint3 => GetUInt(24);
|
||||
public bool IsExpertRecipe => GetBool(25);
|
||||
|
||||
public bool IsShowingCollectibleInfo => IsShowingCollectibleInfoValue != 0;
|
||||
public Condition Condition => (Condition)(1 << (int)ConditionValue);
|
||||
public bool IsCollectible => IsCollectibleValue != 0;
|
||||
|
||||
private uint GetUInt(int i)
|
||||
{
|
||||
var value = this[i];
|
||||
return value->Type == ValueType.UInt ?
|
||||
value->UInt :
|
||||
throw new ArgumentException($"Value {i} is not a uint", nameof(i));
|
||||
}
|
||||
|
||||
private bool GetBool(int i)
|
||||
{
|
||||
var value = this[i];
|
||||
return value->Type == ValueType.Bool ?
|
||||
value->Byte != 0 :
|
||||
throw new ArgumentException($"Value {i} is not a boolean", nameof(i));
|
||||
}
|
||||
|
||||
private SeString GetString(int i)
|
||||
{
|
||||
var value = this[i];
|
||||
return value->Type switch
|
||||
{
|
||||
ValueType.AllocatedString or
|
||||
ValueType.String =>
|
||||
MemoryHelper.ReadSeStringNullTerminated((nint)value->String),
|
||||
_ => throw new ArgumentException($"Value {i} is not a string", nameof(i))
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private const ushort StatusInnerQuiet = 251;
|
||||
private const ushort StatusWasteNot = 252;
|
||||
private const ushort StatusVeneration = 2226;
|
||||
private const ushort StatusGreatStrides = 254;
|
||||
private const ushort StatusInnovation = 2189;
|
||||
private const ushort StatusFinalAppraisal = 2190;
|
||||
private const ushort StatusWasteNot2 = 257;
|
||||
private const ushort StatusMuscleMemory = 2191;
|
||||
private const ushort StatusManipulation = 1164;
|
||||
private const ushort StatusHeartAndSoul = 2665;
|
||||
|
||||
private SimulationState CreateSimulationState()
|
||||
{
|
||||
var player = Service.ClientState.LocalPlayer!;
|
||||
var values = new AddonValues(RecipeUtils.AddonSynthesis);
|
||||
var statusManager = ((Character*)player.Address)->GetStatusManager();
|
||||
|
||||
byte GetEffectStack(ushort id)
|
||||
{
|
||||
foreach (var status in statusManager->StatusSpan)
|
||||
if (status.StatusID == id)
|
||||
return status.StackCount;
|
||||
return 0;
|
||||
}
|
||||
bool HasEffect(ushort id)
|
||||
{
|
||||
foreach (var status in statusManager->StatusSpan)
|
||||
if (status.StatusID == id)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
return new(Input)
|
||||
{
|
||||
ActionCount = ActionCount,
|
||||
StepCount = (int)values.StepCount - 1,
|
||||
Progress = (int)values.Progress,
|
||||
Quality = (int)values.Quality,
|
||||
Durability = (int)values.Durability,
|
||||
CP = (int)player.CurrentCp,
|
||||
Condition = values.Condition,
|
||||
ActiveEffects = new()
|
||||
{
|
||||
InnerQuiet = GetEffectStack(StatusInnerQuiet),
|
||||
WasteNot = GetEffectStack(StatusWasteNot),
|
||||
Veneration = GetEffectStack(StatusVeneration),
|
||||
GreatStrides = GetEffectStack(StatusGreatStrides),
|
||||
Innovation = GetEffectStack(StatusInnovation),
|
||||
FinalAppraisal = GetEffectStack(StatusFinalAppraisal),
|
||||
WasteNot2 = GetEffectStack(StatusWasteNot2),
|
||||
MuscleMemory = GetEffectStack(StatusMuscleMemory),
|
||||
Manipulation = GetEffectStack(StatusManipulation),
|
||||
HeartAndSoul = HasEffect(StatusHeartAndSoul),
|
||||
},
|
||||
ActionStates = ActionStates
|
||||
};
|
||||
}
|
||||
|
||||
private void OnActionUsed(ActionType action)
|
||||
{
|
||||
if (RecipeUtils.AddonSynthesis == null)
|
||||
return;
|
||||
var inGameState = CreateSimulationState();
|
||||
(_, var predictedState) = new SimulatorNoRandom(inGameState).Execute(inGameState, action);
|
||||
QueueSolve(predictedState);
|
||||
|
||||
ActionCount++;
|
||||
var states = ActionStates;
|
||||
states.MutateState(action.Base());
|
||||
ActionStates = states;
|
||||
IsIntermediate = true;
|
||||
IntermediateState = CreateSimulationState();
|
||||
}
|
||||
|
||||
private static bool StatesEqualExceptSome(SimulationState a, SimulationState b)
|
||||
{
|
||||
b.CP = a.CP;
|
||||
b.ActiveEffects = a.ActiveEffects;
|
||||
return a == b;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
using Craftimizer.Simulator;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Interface.Windowing;
|
||||
using Dalamud.Memory;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using System;
|
||||
using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.ValueType;
|
||||
|
||||
namespace Craftimizer.Plugin.Windows;
|
||||
|
||||
public sealed unsafe partial class Craft : Window, IDisposable
|
||||
{
|
||||
// State variables, manually kept track of outside of the addon
|
||||
private CharacterStats CharacterStats = null!;
|
||||
private SimulationInput Input = null!;
|
||||
private int ActionCount;
|
||||
private ActionStates ActionStates;
|
||||
|
||||
private sealed class AddonValues
|
||||
{
|
||||
public AddonSynthesis* Addon { get; }
|
||||
public AtkValue* Values => Addon->AtkUnitBase.AtkValues;
|
||||
public ushort ValueCount => Addon->AtkUnitBase.AtkValuesCount;
|
||||
|
||||
public AddonValues(AddonSynthesis* addon)
|
||||
{
|
||||
Addon = addon;
|
||||
if (ValueCount != 26)
|
||||
throw new ArgumentException("AddonSynthesis must have 26 AtkValues", nameof(addon));
|
||||
}
|
||||
|
||||
public unsafe AtkValue* this[int i] => Values + i;
|
||||
|
||||
// Always 0?
|
||||
private uint Unk0 => GetUInt(0);
|
||||
// Always true?
|
||||
private bool Unk1 => GetBool(1);
|
||||
|
||||
public SeString ItemName => GetString(2);
|
||||
public uint ItemIconId => GetUInt(3);
|
||||
public uint ItemCount => GetUInt(4);
|
||||
public uint Progress => GetUInt(5);
|
||||
public uint MaxProgress => GetUInt(6);
|
||||
public uint Durability => GetUInt(7);
|
||||
public uint MaxDurability => GetUInt(8);
|
||||
public uint Quality => GetUInt(9);
|
||||
public uint HQChance => GetUInt(10);
|
||||
private uint IsShowingCollectibleInfoValue => GetUInt(11);
|
||||
private uint ConditionValue => GetUInt(12);
|
||||
public SeString ConditionName => GetString(13);
|
||||
public SeString ConditionNameAndTooltip => GetString(14);
|
||||
public uint StepCount => GetUInt(15);
|
||||
public uint ResultItemId => GetUInt(16);
|
||||
public uint MaxQuality => GetUInt(17);
|
||||
public uint RequiredQuality => GetUInt(18);
|
||||
private uint IsCollectibleValue => GetUInt(19);
|
||||
public uint Collectability => GetUInt(20);
|
||||
public uint MaxCollectability => GetUInt(21);
|
||||
public uint CollectabilityCheckpoint1 => GetUInt(22);
|
||||
public uint CollectabilityCheckpoint2 => GetUInt(23);
|
||||
public uint CollectabilityCheckpoint3 => GetUInt(24);
|
||||
public bool IsExpertRecipe => GetBool(25);
|
||||
|
||||
public bool IsShowingCollectibleInfo => IsShowingCollectibleInfoValue != 0;
|
||||
public Condition Condition => (Condition)(1 << (int)ConditionValue);
|
||||
public bool IsCollectible => IsCollectibleValue != 0;
|
||||
|
||||
private uint GetUInt(int i)
|
||||
{
|
||||
var value = this[i];
|
||||
return value->Type == ValueType.UInt ?
|
||||
value->UInt :
|
||||
throw new ArgumentException($"Value {i} is not a uint", nameof(i));
|
||||
}
|
||||
|
||||
private bool GetBool(int i)
|
||||
{
|
||||
var value = this[i];
|
||||
return value->Type == ValueType.Bool ?
|
||||
value->Byte != 0 :
|
||||
throw new ArgumentException($"Value {i} is not a boolean", nameof(i));
|
||||
}
|
||||
|
||||
private SeString GetString(int i)
|
||||
{
|
||||
var value = this[i];
|
||||
return value->Type switch
|
||||
{
|
||||
ValueType.AllocatedString or
|
||||
ValueType.String =>
|
||||
MemoryHelper.ReadSeStringNullTerminated((nint)value->String),
|
||||
_ => throw new ArgumentException($"Value {i} is not a string", nameof(i))
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private const ushort StatusInnerQuiet = 251;
|
||||
private const ushort StatusWasteNot = 252;
|
||||
private const ushort StatusVeneration = 2226;
|
||||
private const ushort StatusGreatStrides = 254;
|
||||
private const ushort StatusInnovation = 2189;
|
||||
private const ushort StatusFinalAppraisal = 2190;
|
||||
private const ushort StatusWasteNot2 = 257;
|
||||
private const ushort StatusMuscleMemory = 2191;
|
||||
private const ushort StatusManipulation = 1164;
|
||||
private const ushort StatusHeartAndSoul = 2665;
|
||||
|
||||
private SimulationState GetAddonSimulationState()
|
||||
{
|
||||
var player = Service.ClientState.LocalPlayer!;
|
||||
var values = new AddonValues(RecipeUtils.AddonSynthesis);
|
||||
var statusManager = ((Character*)player.Address)->GetStatusManager();
|
||||
|
||||
byte GetEffectStack(ushort id)
|
||||
{
|
||||
foreach (var status in statusManager->StatusSpan)
|
||||
if (status.StatusID == id)
|
||||
return status.StackCount;
|
||||
return 0;
|
||||
}
|
||||
bool HasEffect(ushort id)
|
||||
{
|
||||
foreach (var status in statusManager->StatusSpan)
|
||||
if (status.StatusID == id)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
return new(Input)
|
||||
{
|
||||
ActionCount = ActionCount,
|
||||
StepCount = (int)values.StepCount - 1,
|
||||
Progress = (int)values.Progress,
|
||||
Quality = (int)values.Quality,
|
||||
Durability = (int)values.Durability,
|
||||
CP = (int)player.CurrentCp,
|
||||
Condition = values.Condition,
|
||||
ActiveEffects = new()
|
||||
{
|
||||
InnerQuiet = GetEffectStack(StatusInnerQuiet),
|
||||
WasteNot = GetEffectStack(StatusWasteNot),
|
||||
Veneration = GetEffectStack(StatusVeneration),
|
||||
GreatStrides = GetEffectStack(StatusGreatStrides),
|
||||
Innovation = GetEffectStack(StatusInnovation),
|
||||
FinalAppraisal = GetEffectStack(StatusFinalAppraisal),
|
||||
WasteNot2 = GetEffectStack(StatusWasteNot2),
|
||||
MuscleMemory = GetEffectStack(StatusMuscleMemory),
|
||||
Manipulation = GetEffectStack(StatusManipulation),
|
||||
HeartAndSoul = HasEffect(StatusHeartAndSoul),
|
||||
},
|
||||
ActionStates = ActionStates
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
using Craftimizer.Simulator;
|
||||
using Craftimizer.Simulator.Actions;
|
||||
using Dalamud.Interface.Windowing;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Craftimizer.Plugin.Windows;
|
||||
|
||||
public sealed unsafe partial class Craft : Window, IDisposable
|
||||
{
|
||||
private SimulationState? SolverState { get; set; }
|
||||
private Task? SolverTask { get; set; }
|
||||
private CancellationTokenSource? SolverTaskToken { get; set; }
|
||||
private ConcurrentQueue<ActionType> SolverActionQueue { get; } = new();
|
||||
|
||||
// State is the state of the simulation *after* its corresponding action is executed.
|
||||
private List<(ActionType Action, string Tooltip, SimulationState State)> SolverActions { get; } = new();
|
||||
private SimulatorNoRandom SolverSim { get; set; } = null!;
|
||||
private SimulationState SolverLatestState => SolverActions.Count == 0 ? SolverState!.Value : SolverActions[^1].State;
|
||||
|
||||
private void StopSolve()
|
||||
{
|
||||
if (SolverTask == null || SolverTaskToken == null)
|
||||
return;
|
||||
|
||||
if (!SolverTask.IsCompleted)
|
||||
SolverTaskToken.Cancel();
|
||||
else
|
||||
{
|
||||
SolverTaskToken.Dispose();
|
||||
SolverTask.Dispose();
|
||||
|
||||
SolverTask = null;
|
||||
SolverTaskToken = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void QueueSolve(SimulationState state)
|
||||
{
|
||||
StopSolve();
|
||||
|
||||
SolverActionQueue.Clear();
|
||||
SolverActions.Clear();
|
||||
SolverState = state;
|
||||
SolverSim = new(state);
|
||||
|
||||
SolverTaskToken = new();
|
||||
SolverTask = Task.Run(() => Config.SolverAlgorithm.Invoke(Config.SolverConfig, state, SolverActionQueue.Enqueue, SolverTaskToken.Token));
|
||||
}
|
||||
|
||||
private void SolveTick()
|
||||
{
|
||||
var newState = GetNextState();
|
||||
if (SolverState == newState)
|
||||
return;
|
||||
|
||||
if (newState == null)
|
||||
StopSolve();
|
||||
else
|
||||
QueueSolve(newState.Value);
|
||||
}
|
||||
|
||||
private void DequeueSolver()
|
||||
{
|
||||
while (SolverActionQueue.TryDequeue(out var poppedAction))
|
||||
AppendSolverAction(poppedAction);
|
||||
}
|
||||
|
||||
private void AppendSolverAction(ActionType action)
|
||||
{
|
||||
var actionBase = action.Base();
|
||||
if (actionBase is BaseComboAction comboActionBase)
|
||||
{
|
||||
AppendSolverAction(comboActionBase.ActionTypeA);
|
||||
AppendSolverAction(comboActionBase.ActionTypeB);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (SolverActions.Count >= Config.SynthesisHelperStepCount)
|
||||
{
|
||||
StopSolve();
|
||||
return;
|
||||
}
|
||||
|
||||
var tooltip = actionBase.GetTooltip(SolverSim, false);
|
||||
var (_, state) = SolverSim.Execute(SolverLatestState, action);
|
||||
SolverActions.Add((action, tooltip, state));
|
||||
|
||||
if (SolverActions.Count >= Config.SynthesisHelperStepCount)
|
||||
StopSolve();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
using Craftimizer.Simulator;
|
||||
using Craftimizer.Simulator.Actions;
|
||||
using Dalamud.Interface.Windowing;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Craftimizer.Plugin.Windows;
|
||||
|
||||
public sealed unsafe partial class Craft : Window, IDisposable
|
||||
{
|
||||
private ConcurrentQueue<ActionType> UsedActionQueue { get; set; } = new();
|
||||
private IEnumerator<SimulationState>? StateTicker { get; set; }
|
||||
|
||||
private SimulationState? GetNextState()
|
||||
{
|
||||
if (RecipeUtils.IsCrafting && StateTicker == null)
|
||||
StateTicker = TickState();
|
||||
if (!RecipeUtils.IsCrafting && StateTicker != null)
|
||||
StateTicker = null;
|
||||
|
||||
if (StateTicker == null)
|
||||
return null;
|
||||
StateTicker.MoveNext();
|
||||
return StateTicker.Current;
|
||||
}
|
||||
|
||||
private IEnumerator<SimulationState> TickState()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
SimulationState state;
|
||||
|
||||
// Dequeue used actions
|
||||
var sim = new SimulatorNoRandom(new());
|
||||
while (true)
|
||||
{
|
||||
state = GetAddonSimulationState();
|
||||
|
||||
var dequeued = false;
|
||||
while (UsedActionQueue.TryDequeue(out var action))
|
||||
{
|
||||
dequeued = true;
|
||||
(_, state) = sim.Execute(state, action);
|
||||
ActionCount++;
|
||||
ActionStates.MutateState(action.Base());
|
||||
}
|
||||
if (dequeued)
|
||||
break;
|
||||
|
||||
// If nothing is dequeued and executed, just return the addon state
|
||||
yield return state;
|
||||
}
|
||||
|
||||
// Intermediate state, wait for addon change
|
||||
var intermediateState = GetAddonSimulationState();
|
||||
while (true)
|
||||
{
|
||||
yield return state;
|
||||
var newState = GetAddonSimulationState();
|
||||
if (!IsStateInIntermediate(newState, intermediateState))
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsStateInIntermediate(SimulationState a, SimulationState b)
|
||||
{
|
||||
b.CP = a.CP;
|
||||
b.ActiveEffects = a.ActiveEffects;
|
||||
return a == b;
|
||||
}
|
||||
|
||||
private void OnActionUsed(ActionType action)
|
||||
{
|
||||
if (!RecipeUtils.IsCrafting || RecipeUtils.AddonSynthesis == null)
|
||||
return;
|
||||
|
||||
UsedActionQueue.Enqueue(action);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user