Dalamud/Dalamud/Game/ClientState/Conditions/Condition.cs
srkizer 87b9edb448
Add IInternal/PublicDisposableService (#1696)
* Add IInternal/PublicDisposableService

Plugins are exposed interfaces that are not inherited from
`IDisposable`, but services implementing plugin interfaces often
implement `IDisposable`. Some plugins may try to call
`IDisposable.Dispose` on everything provided, and it also is possible to
use `using` clause too eagerly while working on Dalamud itself, such as
writing `using var smth = await Service<SomeService>.GetAsync();`. Such
behaviors often lead to a difficult-to-debug errors, and making those
services either not an `IDisposable` or making `IDisposable.Dispose` do
nothing if the object has been loaded would prevent such errors. As
`ServiceManager` must be the only class dealing with construction and
disposal of services, `IInternalDisposableService` has been added to
limit who can dispose the object. `IPublicDisposableService` also has
been added to classes that can be constructed and accessed directly by
plugins; for those, `Dispose` will be ignored if the instance is a
service instance, and only `DisposeService` will respond.

In addition, `DalamudPluginInterface` and `UiBuilder` also have been
changed so that their `IDisposable.Dispose` no longer respond, and
instead, internal functions have been added to only allow disposal from
Dalamud.

* Cleanup

* Postmerge fixes

* More explanation on RunOnFrameworkThread(ClearHooks)

* Mark ReliableFileStorage public ctor obsolete

---------

Co-authored-by: goat <16760685+goaaats@users.noreply.github.com>
2024-03-16 15:58:05 +00:00

185 lines
4.9 KiB
C#

using Dalamud.IoC;
using Dalamud.IoC.Internal;
using Dalamud.Plugin.Services;
using Serilog;
namespace Dalamud.Game.ClientState.Conditions;
/// <summary>
/// Provides access to conditions (generally player state). You can check whether a player is in combat, mounted, etc.
/// </summary>
[InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService]
internal sealed partial class Condition : IInternalDisposableService, ICondition
{
/// <summary>
/// Gets the current max number of conditions. You can get this just by looking at the condition sheet and how many rows it has.
/// </summary>
internal const int MaxConditionEntries = 104;
[ServiceManager.ServiceDependency]
private readonly Framework framework = Service<Framework>.Get();
private readonly bool[] cache = new bool[MaxConditionEntries];
private bool isDisposed;
[ServiceManager.ServiceConstructor]
private Condition(ClientState clientState)
{
var resolver = clientState.AddressResolver;
this.Address = resolver.ConditionFlags;
// Initialization
for (var i = 0; i < MaxConditionEntries; i++)
this.cache[i] = this[i];
this.framework.Update += this.FrameworkUpdate;
}
/// <summary>Finalizes an instance of the <see cref="Condition" /> class.</summary>
~Condition() => this.Dispose(false);
/// <inheritdoc/>
public event ICondition.ConditionChangeDelegate? ConditionChange;
/// <inheritdoc/>
public int MaxEntries => MaxConditionEntries;
/// <inheritdoc/>
public IntPtr Address { get; private set; }
/// <inheritdoc/>
public unsafe bool this[int flag]
{
get
{
if (flag < 0 || flag >= MaxConditionEntries)
return false;
return *(bool*)(this.Address + flag);
}
}
/// <inheritdoc/>
public bool this[ConditionFlag flag]
=> this[(int)flag];
/// <inheritdoc/>
void IInternalDisposableService.DisposeService() => this.Dispose(true);
/// <inheritdoc/>
public bool Any()
{
for (var i = 0; i < MaxConditionEntries; i++)
{
var cond = this[i];
if (cond)
return true;
}
return false;
}
/// <inheritdoc/>
public bool Any(params ConditionFlag[] flags)
{
foreach (var flag in flags)
{
// this[i] performs range checking, so no need to check here
if (this[flag])
{
return true;
}
}
return false;
}
private void Dispose(bool disposing)
{
if (this.isDisposed)
return;
if (disposing)
{
this.framework.Update -= this.FrameworkUpdate;
}
this.isDisposed = true;
}
private void FrameworkUpdate(IFramework unused)
{
for (var i = 0; i < MaxConditionEntries; i++)
{
var value = this[i];
if (value != this.cache[i])
{
this.cache[i] = value;
try
{
this.ConditionChange?.Invoke((ConditionFlag)i, value);
}
catch (Exception ex)
{
Log.Error(ex, $"While invoking {nameof(this.ConditionChange)}, an exception was thrown.");
}
}
}
}
}
/// <summary>
/// Plugin-scoped version of a Condition service.
/// </summary>
[PluginInterface]
[InterfaceVersion("1.0")]
[ServiceManager.ScopedService]
#pragma warning disable SA1015
[ResolveVia<ICondition>]
#pragma warning restore SA1015
internal class ConditionPluginScoped : IInternalDisposableService, ICondition
{
[ServiceManager.ServiceDependency]
private readonly Condition conditionService = Service<Condition>.Get();
/// <summary>
/// Initializes a new instance of the <see cref="ConditionPluginScoped"/> class.
/// </summary>
internal ConditionPluginScoped()
{
this.conditionService.ConditionChange += this.ConditionChangedForward;
}
/// <inheritdoc/>
public event ICondition.ConditionChangeDelegate? ConditionChange;
/// <inheritdoc/>
public int MaxEntries => this.conditionService.MaxEntries;
/// <inheritdoc/>
public IntPtr Address => this.conditionService.Address;
/// <inheritdoc/>
public bool this[int flag] => this.conditionService[flag];
/// <inheritdoc/>
void IInternalDisposableService.DisposeService()
{
this.conditionService.ConditionChange -= this.ConditionChangedForward;
this.ConditionChange = null;
}
/// <inheritdoc/>
public bool Any() => this.conditionService.Any();
/// <inheritdoc/>
public bool Any(params ConditionFlag[] flags) => this.conditionService.Any(flags);
private void ConditionChangedForward(ConditionFlag flag, bool value) => this.ConditionChange?.Invoke(flag, value);
}