Skip to content

Commit

Permalink
Merge pull request #407 from FFXIV-CombatReborn/concurrentdictionaryc…
Browse files Browse the repository at this point in the history
…rashfix

Enhance thread safety and refactor exception handling
  • Loading branch information
LTS-FFXIV authored Sep 24, 2024
2 parents 477a8d4 + a78b4dc commit 3808464
Show file tree
Hide file tree
Showing 5 changed files with 169 additions and 100 deletions.
38 changes: 38 additions & 0 deletions RotationSolver.Basic/Data/JobGaugeManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using Dalamud.Game.ClientState.JobGauge;
using Dalamud.Plugin.Services;
using System;

namespace RotationSolver.Basic.Data
{
/// <summary>
/// Manages job gauges and provides thread-safe access to them.
/// </summary>
public class JobGaugeManager
{
private readonly IJobGauges jobGauges;
private readonly object lockObject = new object();

/// <summary>
/// Initializes a new instance of the <see cref="JobGaugeManager"/> class.
/// </summary>
/// <param name="jobGauges">The job gauges service.</param>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="jobGauges"/> is null.</exception>
public JobGaugeManager(IJobGauges jobGauges)
{
this.jobGauges = jobGauges ?? throw new ArgumentNullException(nameof(jobGauges));
}

/// <summary>
/// Gets the job gauge of the specified type.
/// </summary>
/// <typeparam name="T">The type of the job gauge.</typeparam>
/// <returns>The job gauge of the specified type.</returns>
public T GetJobGauge<T>() where T : JobGaugeBase
{
lock (lockObject)
{
return jobGauges.Get<T>();
}
}
}
}
3 changes: 2 additions & 1 deletion RotationSolver.Basic/Helpers/ObjectHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using FFXIVClientStructs.FFXIV.Client.UI;
using FFXIVClientStructs.FFXIV.Common.Component.BGCollision;
using RotationSolver.Basic.Configuration;
using System.Collections.Concurrent;
using System.Text.RegularExpressions;

namespace RotationSolver.Basic.Helpers;
Expand Down Expand Up @@ -285,7 +286,7 @@ internal static bool IsTopPriorityHostile(this IGameObject obj)

internal static unsafe uint FateId(this IGameObject obj) => obj.Struct()->FateId;

static readonly Dictionary<uint, bool> _effectRangeCheck = new();
static readonly ConcurrentDictionary<uint, bool> _effectRangeCheck = new();

/// <summary>
/// Determines whether the specified game object can be interrupted.
Expand Down
154 changes: 85 additions & 69 deletions RotationSolver.Basic/Rotations/CustomRotation_GCD.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
partial class CustomRotation
{
private static DateTime _nextTimeToHeal = DateTime.MinValue;
private static readonly Random _random = new Random();

private IAction? GCD()
{
var act = DataCenter.CommandNextAction;
Expand All @@ -12,101 +14,115 @@ partial class CustomRotation
&& a.CanUse(out _, usedUp: true, skipAoeCheck: true)) return act;
IBaseAction.ForceEnable = false;

IBaseAction.ShouldEndSpecial = true;
try
{
IBaseAction.ShouldEndSpecial = true;

if (DataCenter.MergedStatus.HasFlag(AutoStatus.LimitBreak)
&& UseLimitBreak(out act)) return act;
if (DataCenter.MergedStatus.HasFlag(AutoStatus.LimitBreak)
&& UseLimitBreak(out act)) return act;

IBaseAction.ShouldEndSpecial = false;
IBaseAction.ShouldEndSpecial = false;

if (EmergencyGCD(out act)) return act;
if (EmergencyGCD(out act)) return act;

IBaseAction.ShouldEndSpecial = true;
IBaseAction.ShouldEndSpecial = true;

IBaseAction.TargetOverride = TargetType.Death;
IBaseAction.TargetOverride = TargetType.Death;

if (RaiseSpell(out act, false)) return act;
if (RaiseSpell(out act, false)) return act;

if (Service.Config.RaisePlayerByCasting && SwiftcastPvE.Cooldown.IsCoolingDown && RaiseSpell(out act, true)) return act;
if (Service.Config.RaisePlayerByCasting && SwiftcastPvE.Cooldown.IsCoolingDown && RaiseSpell(out act, true)) return act;

IBaseAction.TargetOverride = null;
IBaseAction.TargetOverride = null;

if (DataCenter.MergedStatus.HasFlag(AutoStatus.MoveForward)
&& MoveForwardGCD(out act))
{
if (act is IBaseAction b && ObjectHelper.DistanceToPlayer(b.Target.Target) > 5) return act;
}
if (DataCenter.MergedStatus.HasFlag(AutoStatus.MoveForward)
&& MoveForwardGCD(out act))
{
if (act is IBaseAction b && ObjectHelper.DistanceToPlayer(b.Target.Target) > 5) return act;
}

IBaseAction.TargetOverride = TargetType.Heal;
IBaseAction.TargetOverride = TargetType.Heal;

if (DataCenter.CommandStatus.HasFlag(AutoStatus.HealAreaSpell))
{
if (HealAreaGCD(out act)) return act;
}
if (DataCenter.AutoStatus.HasFlag(AutoStatus.HealAreaSpell)
&& CanHealAreaSpell)
{
IBaseAction.AutoHealCheck = true;
if (HealAreaGCD(out act)) return act;
IBaseAction.AutoHealCheck = false;
}
if (DataCenter.CommandStatus.HasFlag(AutoStatus.HealSingleSpell)
&& CanHealSingleSpell)
{
if (HealSingleGCD(out act)) return act;
}
if (DataCenter.AutoStatus.HasFlag(AutoStatus.HealSingleSpell))
{
IBaseAction.AutoHealCheck = true;
if (HealSingleGCD(out act)) return act;
IBaseAction.AutoHealCheck = false;
}
if (DataCenter.CommandStatus.HasFlag(AutoStatus.HealAreaSpell))
{
if (HealAreaGCD(out act)) return act;
}
if (DataCenter.AutoStatus.HasFlag(AutoStatus.HealAreaSpell)
&& CanHealAreaSpell)
{
IBaseAction.AutoHealCheck = true;
if (HealAreaGCD(out act)) return act;
IBaseAction.AutoHealCheck = false;
}
if (DataCenter.CommandStatus.HasFlag(AutoStatus.HealSingleSpell)
&& CanHealSingleSpell)
{
if (HealSingleGCD(out act)) return act;
}
if (DataCenter.AutoStatus.HasFlag(AutoStatus.HealSingleSpell))
{
IBaseAction.AutoHealCheck = true;
if (HealSingleGCD(out act)) return act;
IBaseAction.AutoHealCheck = false;
}

IBaseAction.TargetOverride = null;
IBaseAction.TargetOverride = null;

if (DataCenter.MergedStatus.HasFlag(AutoStatus.DefenseArea)
&& DefenseAreaGCD(out act)) return act;
if (DataCenter.MergedStatus.HasFlag(AutoStatus.DefenseArea)
&& DefenseAreaGCD(out act)) return act;

IBaseAction.TargetOverride = TargetType.BeAttacked;
IBaseAction.TargetOverride = TargetType.BeAttacked;

if (DataCenter.MergedStatus.HasFlag(AutoStatus.DefenseSingle)
&& DefenseSingleGCD(out act)) return act;
if (DataCenter.MergedStatus.HasFlag(AutoStatus.DefenseSingle)
&& DefenseSingleGCD(out act)) return act;

IBaseAction.TargetOverride = TargetType.Dispel;
if (DataCenter.MergedStatus.HasFlag(AutoStatus.Dispel)
&& DispelGCD(out act)) return act;
IBaseAction.TargetOverride = TargetType.Dispel;
if (DataCenter.MergedStatus.HasFlag(AutoStatus.Dispel)
&& DispelGCD(out act)) return act;

IBaseAction.ShouldEndSpecial = false;
IBaseAction.TargetOverride = null;
IBaseAction.ShouldEndSpecial = false;
IBaseAction.TargetOverride = null;

if (GeneralGCD(out var action)) return action;
if (GeneralGCD(out var action)) return action;

if (Service.Config.HealWhenNothingTodo && InCombat)
{
// Please don't tell me someone's fps is less than 1!!
if (DateTime.UtcNow - _nextTimeToHeal > TimeSpan.FromSeconds(1))
{
var min = Service.Config.HealWhenNothingTodoDelay.X;
var max = Service.Config.HealWhenNothingTodoDelay.Y;
_nextTimeToHeal = DateTime.UtcNow + TimeSpan.FromSeconds(new Random().NextDouble() * (max - min) + min);
}
else if (_nextTimeToHeal < DateTime.UtcNow)
if (Service.Config.HealWhenNothingTodo && InCombat)
{
_nextTimeToHeal = DateTime.UtcNow;

if (PartyMembersMinHP < Service.Config.HealWhenNothingTodoBelow)
// Please don't tell me someone's fps is less than 1!!
if (DateTime.UtcNow - _nextTimeToHeal > TimeSpan.FromSeconds(1))
{
var min = Service.Config.HealWhenNothingTodoDelay.X;
var max = Service.Config.HealWhenNothingTodoDelay.Y;
_nextTimeToHeal = DateTime.UtcNow + TimeSpan.FromSeconds(_random.NextDouble() * (max - min) + min);
}
else if (_nextTimeToHeal < DateTime.UtcNow)
{
IBaseAction.TargetOverride = TargetType.Heal;
_nextTimeToHeal = DateTime.UtcNow;

if (PartyMembersMinHP < Service.Config.HealWhenNothingTodoBelow)
{
IBaseAction.TargetOverride = TargetType.Heal;

if (DataCenter.PartyMembersDifferHP < Service.Config.HealthDifference
&& DataCenter.PartyMembersHP.Count(i => i < 1) > 2
&& HealAreaGCD(out act)) return act;
if (HealSingleGCD(out act)) return act;
if (DataCenter.PartyMembersDifferHP < Service.Config.HealthDifference
&& DataCenter.PartyMembersHP.Count(i => i < 1) > 2
&& HealAreaGCD(out act)) return act;
if (HealSingleGCD(out act)) return act;

IBaseAction.TargetOverride = null;
IBaseAction.TargetOverride = null;
}
}
}
}
catch (Exception ex)
{
// Log the exception or handle it as needed
Console.WriteLine($"Exception in GCD method: {ex.Message}");
}
finally
{
// Ensure these are reset
IBaseAction.ShouldEndSpecial = false;
IBaseAction.TargetOverride = null;
}

return null;
}
Expand Down
70 changes: 42 additions & 28 deletions RotationSolver.Basic/Rotations/CustomRotation_Invoke.cs
Original file line number Diff line number Diff line change
Expand Up @@ -197,47 +197,61 @@ private void UpdateMoveTarget(IBaseAction a)
IBaseAction.ShouldEndSpecial = false;
IBaseAction.IgnoreClipping = true;

// Check for countdown and return the appropriate action if not in combat
var countDown = Service.CountDownTime;
if (countDown > 0 && !DataCenter.InCombat)
try
{
return CountDownAction(countDown);
}
// Check for countdown and return the appropriate action if not in combat
var countDown = Service.CountDownTime;
if (countDown > 0 && !DataCenter.InCombat)
{
return CountDownAction(countDown);
}

// Reset target override
IBaseAction.TargetOverride = null;
// Reset target override
IBaseAction.TargetOverride = null;

// Attempt to get the GCD action
gcdAction = GCD();
IBaseAction.IgnoreClipping = false;
// Attempt to get the GCD action
gcdAction = GCD();
IBaseAction.IgnoreClipping = false;

// If a GCD action is available, determine if it can be used or if an ability should be used instead
if (gcdAction != null)
{
if (ActionHelper.CanUseGCD)
// If a GCD action is available, determine if it can be used or if an ability should be used instead
if (gcdAction != null)
{
if (ActionHelper.CanUseGCD)
{
return gcdAction;
}

if (Ability(gcdAction, out var ability))
{
return ability;
}

return gcdAction;
}

if (Ability(gcdAction, out var ability))
else
{
return ability;
// If no GCD action is available, attempt to use an ability
IBaseAction.IgnoreClipping = true;
if (Ability(AddlePvE, out var ability))
{
return ability;
}
IBaseAction.IgnoreClipping = false;

return null;
}

return gcdAction;
}
else
catch (Exception ex)
{
// If no GCD action is available, attempt to use an ability
IBaseAction.IgnoreClipping = true;
if (Ability(AddlePvE, out var ability))
{
return ability;
}
IBaseAction.IgnoreClipping = false;

// Log the exception or handle it as needed
Console.WriteLine($"Exception in Invoke method: {ex.Message}");
return null;
}
finally
{
// Ensure IgnoreClipping is reset
IBaseAction.IgnoreClipping = false;
}
}

/// <summary>
Expand Down
4 changes: 2 additions & 2 deletions RotationSolver.Basic/Service.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,8 @@ public static ActionID GetAdjustedActionId(ActionID id)
/// <returns>The adjusted action ID.</returns>
public static unsafe uint GetAdjustedActionId(uint id)
=> ActionManager.Instance()->GetAdjustedActionId(id);


private static readonly ConcurrentDictionary<Type, Addon?> AddonCache = new();

/// <summary>
Expand Down

0 comments on commit 3808464

Please sign in to comment.