mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-27 02:49:18 +01:00
Make state+texture retrieval done with one API call
This commit is contained in:
parent
71bb02347f
commit
e12b2f7803
8 changed files with 325 additions and 150 deletions
|
|
@ -21,12 +21,15 @@ internal sealed class FileSystemSharableTexture : SharableTexture
|
|||
this.UnderlyingWrap = this.CreateTextureAsync();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string SourcePathForDebug => this.path;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString() =>
|
||||
$"{nameof(FileSystemSharableTexture)}#{this.InstanceIdForDebug}({this.path})";
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void FinalRelease()
|
||||
protected override void ReleaseResources()
|
||||
{
|
||||
this.DisposeSuppressingWrap = null;
|
||||
_ = this.UnderlyingWrap?.ToContentDisposedTask(true);
|
||||
|
|
@ -34,7 +37,7 @@ internal sealed class FileSystemSharableTexture : SharableTexture
|
|||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Revive() =>
|
||||
protected override void ReviveResources() =>
|
||||
this.UnderlyingWrap = this.CreateTextureAsync();
|
||||
|
||||
private Task<IDalamudTextureWrap> CreateTextureAsync() =>
|
||||
|
|
|
|||
|
|
@ -25,11 +25,14 @@ internal sealed class GamePathSharableTexture : SharableTexture
|
|||
this.UnderlyingWrap = this.CreateTextureAsync();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string SourcePathForDebug => this.path;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString() => $"{nameof(GamePathSharableTexture)}#{this.InstanceIdForDebug}({this.path})";
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void FinalRelease()
|
||||
protected override void ReleaseResources()
|
||||
{
|
||||
this.DisposeSuppressingWrap = null;
|
||||
_ = this.UnderlyingWrap?.ToContentDisposedTask(true);
|
||||
|
|
@ -37,7 +40,7 @@ internal sealed class GamePathSharableTexture : SharableTexture
|
|||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Revive() =>
|
||||
protected override void ReviveResources() =>
|
||||
this.UnderlyingWrap = this.CreateTextureAsync();
|
||||
|
||||
private Task<IDalamudTextureWrap> CreateTextureAsync() =>
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ internal abstract class SharableTexture : IRefCountable
|
|||
|
||||
private readonly object reviveLock = new();
|
||||
|
||||
private bool resourceReleased;
|
||||
private int refCount;
|
||||
private long selfReferenceExpiry;
|
||||
private IDalamudTextureWrap? availableOnAccessWrapForApi9;
|
||||
|
|
@ -30,21 +31,8 @@ internal abstract class SharableTexture : IRefCountable
|
|||
this.InstanceIdForDebug = Interlocked.Increment(ref instanceCounter);
|
||||
this.refCount = 1;
|
||||
this.selfReferenceExpiry = Environment.TickCount64 + SelfReferenceDurationTicks;
|
||||
this.ReviveEnabled = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a weak reference to an object that demands this objects to be alive.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// TextureManager must keep references to all shared textures, regardless of whether textures' contents are
|
||||
/// flushed, because API9 functions demand that the returned textures may be stored so that they can used anytime,
|
||||
/// possibly reviving a dead-inside object. The object referenced by this property is given out to such use cases,
|
||||
/// which gets created from <see cref="GetAvailableOnAccessWrapForApi9"/>. If this no longer points to an alive
|
||||
/// object, and <see cref="availableOnAccessWrapForApi9"/> is null, then this object is not used from API9 use case.
|
||||
/// </remarks>
|
||||
public WeakReference<IDalamudTextureWrap>? RevivalPossibility { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the instance ID. Debug use only.
|
||||
/// </summary>
|
||||
|
|
@ -63,6 +51,16 @@ internal abstract class SharableTexture : IRefCountable
|
|||
/// </summary>
|
||||
public int RefCountForDebug => this.refCount;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the source path. Debug use only.
|
||||
/// </summary>
|
||||
public abstract string SourcePathForDebug { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance of <see cref="SharableTexture"/> supports revival.
|
||||
/// </summary>
|
||||
public bool HasRevivalPossibility => this.RevivalPossibility?.TryGetTarget(out _) is true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the underlying texture wrap.
|
||||
/// </summary>
|
||||
|
|
@ -74,9 +72,16 @@ internal abstract class SharableTexture : IRefCountable
|
|||
protected DisposeSuppressingTextureWrap? DisposeSuppressingWrap { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance of <see cref="SharableTexture"/> supports reviving.
|
||||
/// Gets or sets a weak reference to an object that demands this objects to be alive.
|
||||
/// </summary>
|
||||
protected bool ReviveEnabled { get; private set; }
|
||||
/// <remarks>
|
||||
/// TextureManager must keep references to all shared textures, regardless of whether textures' contents are
|
||||
/// flushed, because API9 functions demand that the returned textures may be stored so that they can used anytime,
|
||||
/// possibly reviving a dead-inside object. The object referenced by this property is given out to such use cases,
|
||||
/// which gets created from <see cref="GetAvailableOnAccessWrapForApi9"/>. If this no longer points to an alive
|
||||
/// object, and <see cref="availableOnAccessWrapForApi9"/> is null, then this object is not used from API9 use case.
|
||||
/// </remarks>
|
||||
private WeakReference<IDalamudTextureWrap>? RevivalPossibility { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int AddRef() => this.TryAddRef(out var newRefCount) switch
|
||||
|
|
@ -96,8 +101,35 @@ internal abstract class SharableTexture : IRefCountable
|
|||
return newRefCount;
|
||||
|
||||
case IRefCountable.RefCountResult.FinalRelease:
|
||||
this.FinalRelease();
|
||||
return newRefCount;
|
||||
// This case may not be entered while TryAddRef is in progress.
|
||||
// Note that IRefCountable.AlterRefCount guarantees that either TAR or Release will be called for one
|
||||
// generation of refCount; they never are called together for the same generation of refCount.
|
||||
// If TAR is called when refCount >= 1, and then Release is called, case StillAlive will be run.
|
||||
// If TAR is called when refCount == 0, and then Release is called:
|
||||
// ... * if TAR was done: case FinalRelease will be run.
|
||||
// ... * if TAR was not done: case AlreadyDisposed will be run.
|
||||
// ... Because refCount will be altered as the last step of TAR.
|
||||
// If Release is called when refCount == 1, and then TAR is called,
|
||||
// ... the resource may be released yet, so TAR waits for resourceReleased inside reviveLock,
|
||||
// ... while Release releases the underlying resource and then sets resourceReleased inside reviveLock.
|
||||
// ... Once that's done, TAR may revive the object safely.
|
||||
while (true)
|
||||
{
|
||||
lock (this.reviveLock)
|
||||
{
|
||||
if (this.resourceReleased)
|
||||
{
|
||||
// I cannot think of a case that the code entering this code block, but just in case.
|
||||
Thread.Yield();
|
||||
continue;
|
||||
}
|
||||
|
||||
this.ReleaseResources();
|
||||
this.resourceReleased = true;
|
||||
|
||||
return newRefCount;
|
||||
}
|
||||
}
|
||||
|
||||
case IRefCountable.RefCountResult.AlreadyDisposed:
|
||||
throw new ObjectDisposedException(nameof(SharableTexture));
|
||||
|
|
@ -108,16 +140,22 @@ internal abstract class SharableTexture : IRefCountable
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases self-reference, if it should expire.
|
||||
/// Releases self-reference, if conditions are met.
|
||||
/// </summary>
|
||||
/// <param name="immediate">If set to <c>true</c>, the self-reference will be released immediately.</param>
|
||||
/// <returns>Number of the new reference count that may or may not have changed.</returns>
|
||||
public int ReleaseSelfReferenceIfExpired()
|
||||
public int ReleaseSelfReference(bool immediate)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
var exp = this.selfReferenceExpiry;
|
||||
if (exp > Environment.TickCount64)
|
||||
return this.refCount;
|
||||
switch (immediate)
|
||||
{
|
||||
case false when exp > Environment.TickCount64:
|
||||
return this.refCount;
|
||||
case true when exp == SelfReferenceExpiryExpired:
|
||||
return this.refCount;
|
||||
}
|
||||
|
||||
if (exp != Interlocked.CompareExchange(ref this.selfReferenceExpiry, SelfReferenceExpiryExpired, exp))
|
||||
continue;
|
||||
|
|
@ -127,28 +165,6 @@ internal abstract class SharableTexture : IRefCountable
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disables revival.
|
||||
/// </summary>
|
||||
public void DisableReviveAndReleaseSelfReference()
|
||||
{
|
||||
this.ReviveEnabled = false;
|
||||
|
||||
while (true)
|
||||
{
|
||||
var exp = this.selfReferenceExpiry;
|
||||
if (exp == SelfReferenceExpiryExpired)
|
||||
return;
|
||||
|
||||
if (exp != Interlocked.CompareExchange(ref this.selfReferenceExpiry, SelfReferenceExpiryExpired, exp))
|
||||
continue;
|
||||
|
||||
this.availableOnAccessWrapForApi9 = null;
|
||||
this.Release();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the texture if immediately available. The texture is guarnateed to be available for the rest of the frame.
|
||||
/// Invocation from non-main thread will exhibit an undefined behavior.
|
||||
|
|
@ -233,31 +249,41 @@ internal abstract class SharableTexture : IRefCountable
|
|||
/// <summary>
|
||||
/// Cleans up this instance of <see cref="SharableTexture"/>.
|
||||
/// </summary>
|
||||
protected abstract void FinalRelease();
|
||||
protected abstract void ReleaseResources();
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to restore the reference to this texture.
|
||||
/// </summary>
|
||||
protected abstract void Revive();
|
||||
protected abstract void ReviveResources();
|
||||
|
||||
private IRefCountable.RefCountResult TryAddRef(out int newRefCount)
|
||||
{
|
||||
var alterResult = IRefCountable.AlterRefCount(1, ref this.refCount, out newRefCount);
|
||||
if (alterResult != IRefCountable.RefCountResult.AlreadyDisposed || !this.ReviveEnabled)
|
||||
if (alterResult != IRefCountable.RefCountResult.AlreadyDisposed)
|
||||
return alterResult;
|
||||
lock (this.reviveLock)
|
||||
|
||||
while (true)
|
||||
{
|
||||
alterResult = IRefCountable.AlterRefCount(1, ref this.refCount, out newRefCount);
|
||||
if (alterResult != IRefCountable.RefCountResult.AlreadyDisposed)
|
||||
return alterResult;
|
||||
lock (this.reviveLock)
|
||||
{
|
||||
if (!this.resourceReleased)
|
||||
{
|
||||
Thread.Yield();
|
||||
continue;
|
||||
}
|
||||
|
||||
this.Revive();
|
||||
Interlocked.Increment(ref this.refCount);
|
||||
alterResult = IRefCountable.AlterRefCount(1, ref this.refCount, out newRefCount);
|
||||
if (alterResult != IRefCountable.RefCountResult.AlreadyDisposed)
|
||||
return alterResult;
|
||||
|
||||
if (this.RevivalPossibility?.TryGetTarget(out var target) is true)
|
||||
this.availableOnAccessWrapForApi9 = target;
|
||||
this.ReviveResources();
|
||||
if (this.RevivalPossibility?.TryGetTarget(out var target) is true)
|
||||
this.availableOnAccessWrapForApi9 = target;
|
||||
|
||||
return IRefCountable.RefCountResult.StillAlive;
|
||||
Interlocked.Increment(ref this.refCount);
|
||||
this.resourceReleased = false;
|
||||
return IRefCountable.RefCountResult.StillAlive;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
|
@ -58,6 +59,7 @@ internal sealed class TextureManager : IServiceType, IDisposable, ITextureProvid
|
|||
private readonly ConcurrentLru<GameIconLookup, string> lookupToPath = new(PathLookupLruCount);
|
||||
private readonly ConcurrentDictionary<string, SharableTexture> gamePathTextures = new();
|
||||
private readonly ConcurrentDictionary<string, SharableTexture> fileSystemTextures = new();
|
||||
private readonly HashSet<SharableTexture> invalidatedTextures = new();
|
||||
|
||||
private bool disposing;
|
||||
|
||||
|
|
@ -73,12 +75,22 @@ internal sealed class TextureManager : IServiceType, IDisposable, ITextureProvid
|
|||
/// <summary>
|
||||
/// Gets all the loaded textures from the game resources. Debug use only.
|
||||
/// </summary>
|
||||
public IReadOnlyDictionary<string, SharableTexture> GamePathTextures => this.gamePathTextures;
|
||||
public ICollection<SharableTexture> GamePathTextures => this.gamePathTextures.Values;
|
||||
|
||||
/// <summary>
|
||||
/// Gets all the loaded textures from the game resources. Debug use only.
|
||||
/// </summary>
|
||||
public IReadOnlyDictionary<string, SharableTexture> FileSystemTextures => this.fileSystemTextures;
|
||||
public ICollection<SharableTexture> FileSystemTextures => this.fileSystemTextures.Values;
|
||||
|
||||
/// <summary>
|
||||
/// Gets all the loaded textures that are invalidated from <see cref="InvalidatePaths"/>. Debug use only.
|
||||
/// </summary>
|
||||
/// <remarks><c>lock</c> on use of the value returned from this property.</remarks>
|
||||
[SuppressMessage(
|
||||
"ReSharper",
|
||||
"InconsistentlySynchronizedField",
|
||||
Justification = "Debug use only; users are expected to lock around this")]
|
||||
public ICollection<SharableTexture> InvalidatedTextures => this.invalidatedTextures;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
|
|
@ -88,9 +100,9 @@ internal sealed class TextureManager : IServiceType, IDisposable, ITextureProvid
|
|||
|
||||
this.disposing = true;
|
||||
foreach (var v in this.gamePathTextures.Values)
|
||||
v.DisableReviveAndReleaseSelfReference();
|
||||
v.ReleaseSelfReference(true);
|
||||
foreach (var v in this.fileSystemTextures.Values)
|
||||
v.DisableReviveAndReleaseSelfReference();
|
||||
v.ReleaseSelfReference(true);
|
||||
|
||||
this.lookupToPath.Clear();
|
||||
this.gamePathTextures.Clear();
|
||||
|
|
@ -142,52 +154,61 @@ internal sealed class TextureManager : IServiceType, IDisposable, ITextureProvid
|
|||
[Api10ToDo(Api10ToDoAttribute.DeleteCompatBehavior)]
|
||||
[Obsolete("See interface definition.")]
|
||||
public IDalamudTextureWrap? GetTextureFromFile(FileInfo file, bool keepAlive = false) =>
|
||||
this.fileSystemTextures.GetOrAdd(file.FullName, CreateFileSystemSharableTexture).GetAvailableOnAccessWrapForApi9();
|
||||
this.fileSystemTextures.GetOrAdd(file.FullName, CreateFileSystemSharableTexture)
|
||||
.GetAvailableOnAccessWrapForApi9();
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool ImmediateGetStateFromGameIcon(in GameIconLookup lookup, out Exception? exception) =>
|
||||
this.ImmediateGetStateFromGame(this.lookupToPath.GetOrAdd(lookup, this.GetIconPathByValue), out exception);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool ImmediateGetStateFromGame(string path, out Exception? exception)
|
||||
{
|
||||
if (!this.gamePathTextures.TryGetValue(path, out var texture))
|
||||
{
|
||||
exception = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
exception = texture.UnderlyingWrap?.Exception;
|
||||
return texture.UnderlyingWrap?.IsCompletedSuccessfully ?? false;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool ImmediateGetStateFromFile(string file, out Exception? exception)
|
||||
{
|
||||
if (!this.fileSystemTextures.TryGetValue(file, out var texture))
|
||||
{
|
||||
exception = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
exception = texture.UnderlyingWrap?.Exception;
|
||||
return texture.UnderlyingWrap?.IsCompletedSuccessfully ?? false;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IDalamudTextureWrap ImmediateGetFromGameIcon(in GameIconLookup lookup) =>
|
||||
this.ImmediateGetFromGame(this.lookupToPath.GetOrAdd(lookup, this.GetIconPathByValue));
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IDalamudTextureWrap ImmediateGetFromGame(string path) =>
|
||||
this.gamePathTextures.GetOrAdd(path, CreateGamePathSharableTexture).GetImmediate()
|
||||
?? this.dalamudAssetManager.Empty4X4;
|
||||
this.ImmediateTryGetFromGame(path, out var texture, out _)
|
||||
? texture
|
||||
: this.dalamudAssetManager.Empty4X4;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IDalamudTextureWrap ImmediateGetFromFile(string file) =>
|
||||
this.fileSystemTextures.GetOrAdd(file, CreateFileSystemSharableTexture).GetImmediate()
|
||||
?? this.dalamudAssetManager.Empty4X4;
|
||||
this.ImmediateTryGetFromFile(file, out var texture, out _)
|
||||
? texture
|
||||
: this.dalamudAssetManager.Empty4X4;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool ImmediateTryGetFromGameIcon(
|
||||
in GameIconLookup lookup,
|
||||
[NotNullWhen(true)] out IDalamudTextureWrap? texture,
|
||||
out Exception? exception) =>
|
||||
this.ImmediateTryGetFromGame(
|
||||
this.lookupToPath.GetOrAdd(lookup, this.GetIconPathByValue),
|
||||
out texture,
|
||||
out exception);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool ImmediateTryGetFromGame(
|
||||
string path,
|
||||
[NotNullWhen(true)] out IDalamudTextureWrap? texture,
|
||||
out Exception? exception)
|
||||
{
|
||||
ThreadSafety.AssertMainThread();
|
||||
var t = this.gamePathTextures.GetOrAdd(path, CreateGamePathSharableTexture);
|
||||
texture = t.GetImmediate();
|
||||
exception = t.UnderlyingWrap?.Exception;
|
||||
return texture is not null;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool ImmediateTryGetFromFile(
|
||||
string file,
|
||||
[NotNullWhen(true)] out IDalamudTextureWrap? texture,
|
||||
out Exception? exception)
|
||||
{
|
||||
ThreadSafety.AssertMainThread();
|
||||
var t = this.fileSystemTextures.GetOrAdd(file, CreateFileSystemSharableTexture);
|
||||
texture = t.GetImmediate();
|
||||
exception = t.UnderlyingWrap?.Exception;
|
||||
return texture is not null;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task<IDalamudTextureWrap> GetFromGameIconAsync(in GameIconLookup lookup) =>
|
||||
|
|
@ -336,7 +357,22 @@ internal sealed class TextureManager : IServiceType, IDisposable, ITextureProvid
|
|||
foreach (var path in paths)
|
||||
{
|
||||
if (this.gamePathTextures.TryRemove(path, out var r))
|
||||
r.DisableReviveAndReleaseSelfReference();
|
||||
{
|
||||
if (r.ReleaseSelfReference(true) != 0 || r.HasRevivalPossibility)
|
||||
{
|
||||
lock (this.invalidatedTextures)
|
||||
this.invalidatedTextures.Add(r);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.fileSystemTextures.TryRemove(path, out r))
|
||||
{
|
||||
if (r.ReleaseSelfReference(true) != 0 || r.HasRevivalPossibility)
|
||||
{
|
||||
lock (this.invalidatedTextures)
|
||||
this.invalidatedTextures.Add(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -359,17 +395,35 @@ internal sealed class TextureManager : IServiceType, IDisposable, ITextureProvid
|
|||
|
||||
private void FrameworkOnUpdate(IFramework unused)
|
||||
{
|
||||
foreach (var (k, v) in this.gamePathTextures)
|
||||
if (!this.gamePathTextures.IsEmpty)
|
||||
{
|
||||
if (v.ReleaseSelfReferenceIfExpired() == 0 && v.RevivalPossibility?.TryGetTarget(out _) is not true)
|
||||
_ = this.gamePathTextures.TryRemove(k, out _);
|
||||
foreach (var (k, v) in this.gamePathTextures)
|
||||
{
|
||||
if (TextureFinalReleasePredicate(v))
|
||||
_ = this.gamePathTextures.TryRemove(k, out _);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var (k, v) in this.fileSystemTextures)
|
||||
if (!this.fileSystemTextures.IsEmpty)
|
||||
{
|
||||
if (v.ReleaseSelfReferenceIfExpired() == 0 && v.RevivalPossibility?.TryGetTarget(out _) is not true)
|
||||
_ = this.fileSystemTextures.TryRemove(k, out _);
|
||||
foreach (var (k, v) in this.fileSystemTextures)
|
||||
{
|
||||
if (TextureFinalReleasePredicate(v))
|
||||
_ = this.fileSystemTextures.TryRemove(k, out _);
|
||||
}
|
||||
}
|
||||
|
||||
// ReSharper disable once InconsistentlySynchronizedField
|
||||
if (this.invalidatedTextures.Count != 0)
|
||||
{
|
||||
lock (this.invalidatedTextures)
|
||||
this.invalidatedTextures.RemoveWhere(TextureFinalReleasePredicate);
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
static bool TextureFinalReleasePredicate(SharableTexture v) =>
|
||||
v.ReleaseSelfReference(false) == 0 && !v.HasRevivalPossibility;
|
||||
}
|
||||
|
||||
private string GetIconPathByValue(GameIconLookup lookup) =>
|
||||
|
|
|
|||
|
|
@ -117,9 +117,8 @@ public class IconBrowserWidget : IDataWindowWidget
|
|||
try
|
||||
{
|
||||
var cursor = ImGui.GetCursorScreenPos();
|
||||
var texture = texm.ImmediateGetFromGameIcon(new((uint)iconId));
|
||||
|
||||
if (texm.ImmediateGetStateFromGameIcon(new((uint)iconId), out var exc) || exc is null)
|
||||
if (texm.ImmediateTryGetFromGameIcon(new((uint)iconId), out var texture, out var exc))
|
||||
{
|
||||
ImGui.Image(texture.ImGuiHandle, this.iconSize);
|
||||
|
||||
|
|
@ -145,9 +144,9 @@ public class IconBrowserWidget : IDataWindowWidget
|
|||
ImGui.SetTooltip(iconId.ToString());
|
||||
}
|
||||
}
|
||||
else
|
||||
else if (exc is not null)
|
||||
{
|
||||
// This texture was null, draw nothing, and prevent from trying to show it again.
|
||||
// This texture failed to load; draw nothing, and prevent from trying to show it again.
|
||||
this.nullValues.Add(iconId);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ using System.IO;
|
|||
using System.Numerics;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Dalamud.Interface.Components;
|
||||
using Dalamud.Interface.Internal.Notifications;
|
||||
using Dalamud.Interface.Internal.SharableTextures;
|
||||
using Dalamud.Interface.Utility;
|
||||
|
|
@ -62,15 +63,26 @@ internal class TexWidget : IDataWindowWidget
|
|||
GC.Collect();
|
||||
|
||||
ImGui.PushID("loadedGameTextures");
|
||||
if (ImGui.CollapsingHeader($"Loaded Game Textures: {this.textureManager.GamePathTextures.Count}###header"))
|
||||
if (ImGui.CollapsingHeader($"Loaded Game Textures: {this.textureManager.GamePathTextures.Count:g}###header"))
|
||||
this.DrawLoadedTextures(this.textureManager.GamePathTextures);
|
||||
ImGui.PopID();
|
||||
|
||||
ImGui.PushID("loadedFileTextures");
|
||||
if (ImGui.CollapsingHeader($"Loaded File Textures: {this.textureManager.FileSystemTextures.Count}###header"))
|
||||
if (ImGui.CollapsingHeader($"Loaded File Textures: {this.textureManager.FileSystemTextures.Count:g}###header"))
|
||||
this.DrawLoadedTextures(this.textureManager.FileSystemTextures);
|
||||
ImGui.PopID();
|
||||
|
||||
lock (this.textureManager.InvalidatedTextures)
|
||||
{
|
||||
ImGui.PushID("invalidatedTextures");
|
||||
if (ImGui.CollapsingHeader($"Invalidated: {this.textureManager.InvalidatedTextures.Count:g}###header"))
|
||||
{
|
||||
this.DrawLoadedTextures(this.textureManager.InvalidatedTextures);
|
||||
}
|
||||
|
||||
ImGui.PopID();
|
||||
}
|
||||
|
||||
if (ImGui.CollapsingHeader("Load Game File by Icon ID", ImGuiTreeNodeFlags.DefaultOpen))
|
||||
this.DrawIconInput();
|
||||
|
||||
|
|
@ -133,18 +145,33 @@ internal class TexWidget : IDataWindowWidget
|
|||
}
|
||||
}
|
||||
|
||||
private unsafe void DrawLoadedTextures(IReadOnlyDictionary<string, SharableTexture> textures)
|
||||
private unsafe void DrawLoadedTextures(ICollection<SharableTexture> textures)
|
||||
{
|
||||
var im = Service<InterfaceManager>.Get();
|
||||
if (!ImGui.BeginTable("##table", 6))
|
||||
return;
|
||||
|
||||
const int numIcons = 3;
|
||||
float iconWidths;
|
||||
using (im.IconFontHandle?.Push())
|
||||
{
|
||||
iconWidths = ImGui.CalcTextSize(FontAwesomeIcon.Image.ToIconString()).X;
|
||||
iconWidths += ImGui.CalcTextSize(FontAwesomeIcon.Sync.ToIconString()).X;
|
||||
iconWidths += ImGui.CalcTextSize(FontAwesomeIcon.Trash.ToIconString()).X;
|
||||
}
|
||||
|
||||
ImGui.TableSetupScrollFreeze(0, 1);
|
||||
ImGui.TableSetupColumn("ID", ImGuiTableColumnFlags.WidthFixed, ImGui.CalcTextSize("000000").X);
|
||||
ImGui.TableSetupColumn("Preview", ImGuiTableColumnFlags.WidthFixed, ImGui.CalcTextSize("Preview_").X);
|
||||
ImGui.TableSetupColumn("Source", ImGuiTableColumnFlags.WidthStretch);
|
||||
ImGui.TableSetupColumn("RefCount", ImGuiTableColumnFlags.WidthFixed, ImGui.CalcTextSize("RefCount__").X);
|
||||
ImGui.TableSetupColumn("SelfRef", ImGuiTableColumnFlags.WidthFixed, ImGui.CalcTextSize("00.000___").X);
|
||||
ImGui.TableSetupColumn("CanRevive", ImGuiTableColumnFlags.WidthFixed, ImGui.CalcTextSize("CanRevive__").X);
|
||||
ImGui.TableSetupColumn(
|
||||
"Actions",
|
||||
ImGuiTableColumnFlags.WidthFixed,
|
||||
iconWidths +
|
||||
(ImGui.GetStyle().FramePadding.X * 2 * numIcons) +
|
||||
(ImGui.GetStyle().ItemSpacing.X * 1 * numIcons));
|
||||
ImGui.TableHeadersRow();
|
||||
|
||||
var clipper = new ImGuiListClipperPtr(ImGuiNative.ImGuiListClipper_ImGuiListClipper());
|
||||
|
|
@ -168,20 +195,37 @@ internal class TexWidget : IDataWindowWidget
|
|||
if (!valid)
|
||||
break;
|
||||
|
||||
var (key, texture) = enu.Current;
|
||||
ImGui.TableNextRow();
|
||||
|
||||
if (enu.Current is not { } texture)
|
||||
{
|
||||
// Should not happen
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.AlignTextToFramePadding();
|
||||
ImGui.TextUnformatted("?");
|
||||
continue;
|
||||
}
|
||||
|
||||
var remain = texture.SelfReferenceExpiresInForDebug;
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.AlignTextToFramePadding();
|
||||
this.TextRightAlign($"{texture.InstanceIdForDebug:n0}");
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, Vector2.Zero);
|
||||
ImGui.PushStyleColor(ImGuiCol.Button, Vector4.Zero);
|
||||
ImGui.PushStyleColor(ImGuiCol.ButtonActive, Vector4.Zero);
|
||||
ImGui.PushStyleColor(ImGuiCol.ButtonHovered, Vector4.Zero);
|
||||
ImGui.Button("Hover");
|
||||
ImGui.PopStyleColor(3);
|
||||
ImGui.PopStyleVar();
|
||||
this.TextCopiable(texture.SourcePathForDebug, true);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
this.TextRightAlign($"{texture.RefCountForDebug:n0}");
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
this.TextRightAlign(remain <= 0 ? "-" : $"{remain:00.000}");
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(texture.HasRevivalPossibility ? "Yes" : "No");
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGuiComponents.IconButton(FontAwesomeIcon.Image);
|
||||
if (ImGui.IsItemHovered() && texture.GetImmediate() is { } immediate)
|
||||
{
|
||||
ImGui.BeginTooltip();
|
||||
|
|
@ -189,18 +233,21 @@ internal class TexWidget : IDataWindowWidget
|
|||
ImGui.EndTooltip();
|
||||
}
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
this.TextCopiable(key);
|
||||
ImGui.SameLine();
|
||||
if (ImGuiComponents.IconButton(FontAwesomeIcon.Sync))
|
||||
this.textureManager.InvalidatePaths(new[] { texture.SourcePathForDebug });
|
||||
if (ImGui.IsItemHovered())
|
||||
ImGui.SetTooltip($"Call {nameof(ITextureSubstitutionProvider.InvalidatePaths)}.");
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
this.TextRightAlign($"{texture.RefCountForDebug:n0}");
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
var remain = texture.SelfReferenceExpiresInForDebug;
|
||||
this.TextRightAlign(remain <= 0 ? "-" : $"{remain:00.000}");
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(texture.RevivalPossibility?.TryGetTarget(out _) is true ? "Yes" : "No");
|
||||
ImGui.SameLine();
|
||||
if (remain <= 0)
|
||||
ImGui.BeginDisabled();
|
||||
if (ImGuiComponents.IconButton(FontAwesomeIcon.Trash))
|
||||
texture.ReleaseSelfReference(true);
|
||||
if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
|
||||
ImGui.SetTooltip("Release self-reference immediately.");
|
||||
if (remain <= 0)
|
||||
ImGui.EndDisabled();
|
||||
}
|
||||
|
||||
if (!valid)
|
||||
|
|
|
|||
|
|
@ -11,26 +11,23 @@ namespace Dalamud.Plugin.Services;
|
|||
/// <summary>
|
||||
/// Service that grants you access to textures you may render via ImGui.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <b>Immediate functions</b><br />
|
||||
/// Immediate functions do not throw, unless they are called outside the UI thread.<br />
|
||||
/// Instances of <see cref="IDalamudTextureWrap"/> returned from Immediate functions do not have to be disposed.
|
||||
/// Calling <see cref="IDisposable.Dispose"/> on them is a no-op; it is safe to call <see cref="IDisposable.Dispose"/>
|
||||
/// on them, as it will do nothing.<br />
|
||||
/// Use <see cref="ImmediateGetFromGameIcon"/> and alike if you don't care about the load state and dimensions of the
|
||||
/// texture to be loaded. These functions will return a valid texture that is empty (fully transparent).<br />
|
||||
/// Use <see cref="ImmediateTryGetFromGameIcon"/> and alike if you do. These functions will return the load state,
|
||||
/// loaded texture if available, and the load exception on failure.<br />
|
||||
/// <br />
|
||||
/// <b>All other functions</b><br />
|
||||
/// Instances of <see cref="IDalamudTextureWrap"/> or <see cref="Task"/><<see cref="IDalamudTextureWrap"/>>
|
||||
/// returned from all other functions must be <see cref="IDisposable.Dispose"/>d after use.
|
||||
/// </remarks>
|
||||
public partial interface ITextureProvider
|
||||
{
|
||||
/// <summary>Gets the state of the background load task for <see cref="ImmediateGetFromGameIcon"/>.</summary>
|
||||
/// <param name="lookup">The icon specifier.</param>
|
||||
/// <param name="exception">The exception, if failed.</param>
|
||||
/// <returns><c>true</c> if loaded; <c>false</c> if not fully loaded or failed.</returns>
|
||||
public bool ImmediateGetStateFromGameIcon(in GameIconLookup lookup, out Exception? exception);
|
||||
|
||||
/// <summary>Gets the state of the background load task for <see cref="ImmediateGetFromGameIcon"/>.</summary>
|
||||
/// <param name="path">The game-internal path to a .tex, .atex, or an image file such as .png.</param>
|
||||
/// <param name="exception">The exception, if failed.</param>
|
||||
/// <returns><c>true</c> if loaded; <c>false</c> if not fully loaded or failed.</returns>
|
||||
public bool ImmediateGetStateFromGame(string path, out Exception? exception);
|
||||
|
||||
/// <summary>Gets the state of the background load task for <see cref="ImmediateGetFromGameIcon"/>.</summary>
|
||||
/// <param name="file">The filesystem path to a .tex, .atex, or an image file such as .png.</param>
|
||||
/// <param name="exception">The exception, if failed.</param>
|
||||
/// <returns><c>true</c> if loaded; <c>false</c> if not fully loaded or failed.</returns>
|
||||
public bool ImmediateGetStateFromFile(string file, out Exception? exception);
|
||||
|
||||
/// <summary>Gets the corresponding game icon for use with the current frame.</summary>
|
||||
/// <param name="lookup">The icon specifier.</param>
|
||||
/// <returns>An instance of <see cref="IDalamudTextureWrap"/> that is guaranteed to be available for the current
|
||||
|
|
@ -61,6 +58,49 @@ public partial interface ITextureProvider
|
|||
/// empty texture instead.</remarks>
|
||||
/// <exception cref="InvalidOperationException">Thrown when called outside the UI thread.</exception>
|
||||
public IDalamudTextureWrap ImmediateGetFromFile(string file);
|
||||
|
||||
/// <summary>Gets the corresponding game icon for use with the current frame.</summary>
|
||||
/// <param name="lookup">The icon specifier.</param>
|
||||
/// <param name="texture">An instance of <see cref="IDalamudTextureWrap"/> that is guaranteed to be available for
|
||||
/// the current frame being drawn, or <c>null</c> if texture is not loaded (yet).</param>
|
||||
/// <param name="exception">The load exception, if any.</param>
|
||||
/// <returns><c>true</c> if <paramref name="texture"/> points to the loaded texture; <c>false</c> if the texture is
|
||||
/// still being loaded, or the load has failed.</returns>
|
||||
/// <remarks><see cref="IDisposable.Dispose"/> on the returned <paramref name="texture"/> will be ignored.</remarks>
|
||||
/// <exception cref="InvalidOperationException">Thrown when called outside the UI thread.</exception>
|
||||
public bool ImmediateTryGetFromGameIcon(
|
||||
in GameIconLookup lookup,
|
||||
[NotNullWhen(true)] out IDalamudTextureWrap? texture,
|
||||
out Exception? exception);
|
||||
|
||||
/// <summary>Gets a texture from a file shipped as a part of the game resources for use with the current frame.
|
||||
/// </summary>
|
||||
/// <param name="path">The game-internal path to a .tex, .atex, or an image file such as .png.</param>
|
||||
/// <param name="texture">An instance of <see cref="IDalamudTextureWrap"/> that is guaranteed to be available for
|
||||
/// the current frame being drawn, or <c>null</c> if texture is not loaded (yet).</param>
|
||||
/// <param name="exception">The load exception, if any.</param>
|
||||
/// <returns><c>true</c> if <paramref name="texture"/> points to the loaded texture; <c>false</c> if the texture is
|
||||
/// still being loaded, or the load has failed.</returns>
|
||||
/// <remarks><see cref="IDisposable.Dispose"/> on the returned <paramref name="texture"/> will be ignored.</remarks>
|
||||
/// <exception cref="InvalidOperationException">Thrown when called outside the UI thread.</exception>
|
||||
public bool ImmediateTryGetFromGame(
|
||||
string path,
|
||||
[NotNullWhen(true)] out IDalamudTextureWrap? texture,
|
||||
out Exception? exception);
|
||||
|
||||
/// <summary>Gets a texture from a file on the filesystem for use with the current frame.</summary>
|
||||
/// <param name="file">The filesystem path to a .tex, .atex, or an image file such as .png.</param>
|
||||
/// <param name="texture">An instance of <see cref="IDalamudTextureWrap"/> that is guaranteed to be available for
|
||||
/// the current frame being drawn, or <c>null</c> if texture is not loaded (yet).</param>
|
||||
/// <param name="exception">The load exception, if any.</param>
|
||||
/// <returns><c>true</c> if <paramref name="texture"/> points to the loaded texture; <c>false</c> if the texture is
|
||||
/// still being loaded, or the load has failed.</returns>
|
||||
/// <remarks><see cref="IDisposable.Dispose"/> on the returned <paramref name="texture"/> will be ignored.</remarks>
|
||||
/// <exception cref="InvalidOperationException">Thrown when called outside the UI thread.</exception>
|
||||
public bool ImmediateTryGetFromFile(
|
||||
string file,
|
||||
[NotNullWhen(true)] out IDalamudTextureWrap? texture,
|
||||
out Exception? exception);
|
||||
|
||||
/// <summary>Gets the corresponding game icon for use with the current frame.</summary>
|
||||
/// <param name="lookup">The icon specifier.</param>
|
||||
|
|
|
|||
|
|
@ -33,5 +33,8 @@ public interface ITextureSubstitutionProvider
|
|||
/// and paths that are newly substituted.
|
||||
/// </summary>
|
||||
/// <param name="paths">The paths with a changed substitution status.</param>
|
||||
/// <remarks>
|
||||
/// This function will not invalidate the copies of the textures loaded from plugins.
|
||||
/// </remarks>
|
||||
public void InvalidatePaths(IEnumerable<string> paths);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue