mirror of
https://github.com/goatcorp/Dalamud.git
synced 2026-01-02 13:53:40 +01:00
chore: simplify refcounting logic, more concurrency fixes
This commit is contained in:
parent
8a300cc98e
commit
24ad2d4c8b
1 changed files with 90 additions and 95 deletions
|
|
@ -233,13 +233,12 @@ internal class TextureManager : IDisposable, IServiceType, ITextureSubstitutionP
|
||||||
/// Get texture info.
|
/// Get texture info.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="path">Path to the texture.</param>
|
/// <param name="path">Path to the texture.</param>
|
||||||
/// <param name="refresh">Whether or not the texture should be reloaded if it was unloaded.</param>
|
|
||||||
/// <param name="rethrow">
|
/// <param name="rethrow">
|
||||||
/// If true, exceptions caused by texture load will not be caught.
|
/// If true, exceptions caused by texture load will not be caught.
|
||||||
/// If false, exceptions will be caught and a dummy texture will be returned to prevent plugins from using invalid texture handles.
|
/// If false, exceptions will be caught and a dummy texture will be returned to prevent plugins from using invalid texture handles.
|
||||||
/// </param>
|
/// </param>
|
||||||
/// <returns>Info object storing texture metadata.</returns>
|
/// <returns>Info object storing texture metadata.</returns>
|
||||||
internal TextureInfo? GetInfo(string path, bool refresh = true, bool rethrow = false)
|
internal TextureInfo GetInfo(string path, bool rethrow = false)
|
||||||
{
|
{
|
||||||
TextureInfo? info;
|
TextureInfo? info;
|
||||||
lock (this.activeTextures)
|
lock (this.activeTextures)
|
||||||
|
|
@ -248,106 +247,94 @@ internal class TextureManager : IDisposable, IServiceType, ITextureSubstitutionP
|
||||||
{
|
{
|
||||||
Debug.Assert(rethrow, "This should never run when getting outside of creator");
|
Debug.Assert(rethrow, "This should never run when getting outside of creator");
|
||||||
|
|
||||||
if (!refresh)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
info = new TextureInfo();
|
info = new TextureInfo();
|
||||||
this.activeTextures.Add(path, info);
|
this.activeTextures.Add(path, info);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (info == null)
|
if (info == null)
|
||||||
throw new Exception("null info in activeTextures");
|
throw new Exception("null info in activeTextures");
|
||||||
|
|
||||||
// NOTE: We need to increase the refcount here while locking the collection!
|
|
||||||
// Otherwise, if this is loaded from a task, cleanup might already try to delete it
|
|
||||||
// before it can be increased.
|
|
||||||
info.RefCount++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (refresh && info.KeepAliveCount == 0)
|
if (info.KeepAliveCount == 0)
|
||||||
info.LastAccess = DateTime.UtcNow;
|
info.LastAccess = DateTime.UtcNow;
|
||||||
|
|
||||||
if (info is { Wrap: not null })
|
if (info is { Wrap: not null })
|
||||||
return info;
|
return info;
|
||||||
|
|
||||||
if (refresh)
|
if (!this.im.IsReady)
|
||||||
{
|
|
||||||
if (!this.im.IsReady)
|
|
||||||
throw new InvalidOperationException("Cannot create textures before scene is ready");
|
throw new InvalidOperationException("Cannot create textures before scene is ready");
|
||||||
|
|
||||||
string? interceptPath = null;
|
string? interceptPath = null;
|
||||||
this.InterceptTexDataLoad?.Invoke(path, ref interceptPath);
|
this.InterceptTexDataLoad?.Invoke(path, ref interceptPath);
|
||||||
|
|
||||||
if (interceptPath != null)
|
if (interceptPath != null)
|
||||||
{
|
{
|
||||||
Log.Verbose("Intercept: {OriginalPath} => {ReplacePath}", path, interceptPath);
|
Log.Verbose("Intercept: {OriginalPath} => {ReplacePath}", path, interceptPath);
|
||||||
path = interceptPath;
|
path = interceptPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
TextureWrap? wrap;
|
TextureWrap? wrap;
|
||||||
try
|
try
|
||||||
|
{
|
||||||
|
// We want to load this from the disk, probably, if the path has a root
|
||||||
|
// Not sure if this can cause issues with e.g. network drives, might have to rethink
|
||||||
|
// and add a flag instead if it does.
|
||||||
|
if (Path.IsPathRooted(path))
|
||||||
{
|
{
|
||||||
// We want to load this from the disk, probably, if the path has a root
|
if (Path.GetExtension(path) == ".tex")
|
||||||
// Not sure if this can cause issues with e.g. network drives, might have to rethink
|
|
||||||
// and add a flag instead if it does.
|
|
||||||
if (Path.IsPathRooted(path))
|
|
||||||
{
|
{
|
||||||
if (Path.GetExtension(path) == ".tex")
|
// Attempt to load via Lumina
|
||||||
{
|
var file = this.dataManager.GameData.GetFileFromDisk<TexFile>(path);
|
||||||
// Attempt to load via Lumina
|
wrap = this.GetTexture(file);
|
||||||
var file = this.dataManager.GameData.GetFileFromDisk<TexFile>(path);
|
Log.Verbose("Texture {Path} loaded FS via Lumina", path);
|
||||||
wrap = this.GetTexture(file);
|
|
||||||
Log.Verbose("Texture {Path} loaded FS via Lumina", path);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Attempt to load image
|
|
||||||
wrap = this.im.LoadImage(path);
|
|
||||||
Log.Verbose("Texture {Path} loaded FS via LoadImage", path);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Load regularly from dats
|
// Attempt to load image
|
||||||
var file = this.dataManager.GetFile<TexFile>(path);
|
wrap = this.im.LoadImage(path);
|
||||||
if (file == null)
|
Log.Verbose("Texture {Path} loaded FS via LoadImage", path);
|
||||||
throw new Exception("Could not load TexFile from dat.");
|
|
||||||
|
|
||||||
wrap = this.GetTexture(file);
|
|
||||||
Log.Verbose("Texture {Path} loaded from SqPack", path);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (wrap == null)
|
|
||||||
throw new Exception("Could not create texture");
|
|
||||||
|
|
||||||
// TODO: We could support this, but I don't think it's worth it at the moment.
|
|
||||||
var extents = new Vector2(wrap.Width, wrap.Height);
|
|
||||||
if (info.Extents != Vector2.Zero && info.Extents != extents)
|
|
||||||
Log.Warning("Texture at {Path} changed size between reloads, this is currently not supported.", path);
|
|
||||||
|
|
||||||
info.Extents = extents;
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
else
|
||||||
{
|
{
|
||||||
Log.Error(e, "Could not load texture from {Path}", path);
|
// Load regularly from dats
|
||||||
|
var file = this.dataManager.GetFile<TexFile>(path);
|
||||||
// When creating the texture initially, we want to be able to pass errors back to the plugin
|
if (file == null)
|
||||||
if (rethrow)
|
throw new Exception("Could not load TexFile from dat.");
|
||||||
throw;
|
|
||||||
|
wrap = this.GetTexture(file);
|
||||||
// This means that the load failed due to circumstances outside of our control,
|
Log.Verbose("Texture {Path} loaded from SqPack", path);
|
||||||
// and we can't do anything about it. Return a dummy texture so that the plugin still
|
|
||||||
// has something to draw.
|
|
||||||
wrap = this.fallbackTextureWrap;
|
|
||||||
|
|
||||||
// Prevent divide-by-zero
|
|
||||||
if (info.Extents == Vector2.Zero)
|
|
||||||
info.Extents = Vector2.One;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (wrap == null)
|
||||||
|
throw new Exception("Could not create texture");
|
||||||
|
|
||||||
info.Wrap = wrap;
|
// TODO: We could support this, but I don't think it's worth it at the moment.
|
||||||
|
var extents = new Vector2(wrap.Width, wrap.Height);
|
||||||
|
if (info.Extents != Vector2.Zero && info.Extents != extents)
|
||||||
|
Log.Warning("Texture at {Path} changed size between reloads, this is currently not supported.", path);
|
||||||
|
|
||||||
|
info.Extents = extents;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Could not load texture from {Path}", path);
|
||||||
|
|
||||||
|
// When creating the texture initially, we want to be able to pass errors back to the plugin
|
||||||
|
if (rethrow)
|
||||||
|
throw;
|
||||||
|
|
||||||
|
// This means that the load failed due to circumstances outside of our control,
|
||||||
|
// and we can't do anything about it. Return a dummy texture so that the plugin still
|
||||||
|
// has something to draw.
|
||||||
|
wrap = this.fallbackTextureWrap;
|
||||||
|
|
||||||
|
// Prevent divide-by-zero
|
||||||
|
if (info.Extents == Vector2.Zero)
|
||||||
|
info.Extents = Vector2.One;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
info.Wrap = wrap;
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -359,23 +346,23 @@ internal class TextureManager : IDisposable, IServiceType, ITextureSubstitutionP
|
||||||
/// <param name="keepAlive">Whether or not this handle was created in keep-alive mode.</param>
|
/// <param name="keepAlive">Whether or not this handle was created in keep-alive mode.</param>
|
||||||
internal void NotifyTextureDisposed(string path, bool keepAlive)
|
internal void NotifyTextureDisposed(string path, bool keepAlive)
|
||||||
{
|
{
|
||||||
var info = this.GetInfo(path, false);
|
lock (this.activeTextures)
|
||||||
|
|
||||||
// This texture was already disposed
|
|
||||||
if (info == null)
|
|
||||||
{
|
{
|
||||||
Log.Warning("Disposing unknown texture {Path}", path);
|
if (!this.activeTextures.TryGetValue(path, out var info))
|
||||||
return;
|
{
|
||||||
|
Log.Warning("Disposing texture that didn't exist: {Path}", path);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
info.RefCount--;
|
||||||
|
|
||||||
|
if (keepAlive)
|
||||||
|
info.KeepAliveCount--;
|
||||||
|
|
||||||
|
// Clean it up by the next update. If it's re-requested in-between, we don't reload it.
|
||||||
|
if (info.RefCount <= 0)
|
||||||
|
info.LastAccess = default;
|
||||||
}
|
}
|
||||||
|
|
||||||
info.RefCount--;
|
|
||||||
|
|
||||||
if (keepAlive)
|
|
||||||
info.KeepAliveCount--;
|
|
||||||
|
|
||||||
// Clean it up by the next update. If it's re-requested in-between, we don't reload it.
|
|
||||||
if (info.RefCount <= 0)
|
|
||||||
info.LastAccess = default;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string FormatIconPath(uint iconId, string? type, bool highResolution)
|
private static string FormatIconPath(uint iconId, string? type, bool highResolution)
|
||||||
|
|
@ -391,14 +378,22 @@ internal class TextureManager : IDisposable, IServiceType, ITextureSubstitutionP
|
||||||
|
|
||||||
private TextureManagerTextureWrap? CreateWrap(string path, bool keepAlive)
|
private TextureManagerTextureWrap? CreateWrap(string path, bool keepAlive)
|
||||||
{
|
{
|
||||||
// This will create the texture.
|
lock (this.activeTextures)
|
||||||
// That's fine, it's probably used immediately and this will let the plugin catch load errors.
|
{
|
||||||
var info = this.GetInfo(path, rethrow: true)!;
|
// This will create the texture.
|
||||||
|
// That's fine, it's probably used immediately and this will let the plugin catch load errors.
|
||||||
|
var info = this.GetInfo(path, rethrow: true);
|
||||||
|
|
||||||
if (keepAlive)
|
// We need to increase the refcounts here while locking the collection!
|
||||||
info.KeepAliveCount++;
|
// Otherwise, if this is loaded from a task, cleanup might already try to delete it
|
||||||
|
// before it can be increased.
|
||||||
|
info.RefCount++;
|
||||||
|
|
||||||
return new TextureManagerTextureWrap(path, info.Extents, keepAlive, this);
|
if (keepAlive)
|
||||||
|
info.KeepAliveCount++;
|
||||||
|
|
||||||
|
return new TextureManagerTextureWrap(path, info.Extents, keepAlive, this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void FrameworkOnUpdate(Framework fw)
|
private void FrameworkOnUpdate(Framework fw)
|
||||||
|
|
@ -589,7 +584,7 @@ internal class TextureManagerTextureWrap : IDalamudTextureWrap
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IntPtr ImGuiHandle => !this.IsDisposed ?
|
public IntPtr ImGuiHandle => !this.IsDisposed ?
|
||||||
this.manager.GetInfo(this.path)!.Wrap!.ImGuiHandle :
|
this.manager.GetInfo(this.path).Wrap!.ImGuiHandle :
|
||||||
throw new InvalidOperationException("Texture already disposed. You may not render it.");
|
throw new InvalidOperationException("Texture already disposed. You may not render it.");
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue