mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-12 10:17:22 +01:00
Make IFontHandle.Push return IDisposable, and add IFontHandle.Pop
This commit is contained in:
parent
a409ea60d6
commit
29b3e0aa97
9 changed files with 185 additions and 47 deletions
|
|
@ -70,6 +70,7 @@
|
|||
<PackageReference Include="JetBrains.Annotations" Version="2021.2.0" />
|
||||
<PackageReference Include="Lumina" Version="3.15.2" />
|
||||
<PackageReference Include="Lumina.Excel" Version="6.5.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.ObjectPool" Version="8.0.1" />
|
||||
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.46-beta">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
|
|
|
|||
|
|
@ -72,8 +72,8 @@ public sealed class GameFontHandle : IFontHandle
|
|||
/// <returns>An <see cref="IDisposable"/> that can be used to pop the font on dispose.</returns>
|
||||
public IDisposable Push() => this.fontHandle.Push();
|
||||
|
||||
/// <inheritdoc/>
|
||||
IFontHandle.FontPopper IFontHandle.Push() => this.fontHandle.Push();
|
||||
/// <inheritdoc />
|
||||
public void Pop() => this.fontHandle.Pop();
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<IFontHandle> WaitAsync() => this.fontHandle.WaitAsync();
|
||||
|
|
|
|||
|
|
@ -230,6 +230,11 @@ internal class InterfaceManager : IDisposable, IServiceType
|
|||
/// </summary>
|
||||
public Task FontBuildTask => WhenFontsReady().dalamudAtlas!.BuildTask;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of calls to <see cref="PresentDetour"/> so far.
|
||||
/// </summary>
|
||||
public long CumulativePresentCalls { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Dispose of managed and unmanaged resources.
|
||||
/// </summary>
|
||||
|
|
@ -647,6 +652,8 @@ internal class InterfaceManager : IDisposable, IServiceType
|
|||
*/
|
||||
private IntPtr PresentDetour(IntPtr swapChain, uint syncInterval, uint presentFlags)
|
||||
{
|
||||
this.CumulativePresentCalls++;
|
||||
|
||||
Debug.Assert(this.presentHook is not null, "How did PresentDetour get called when presentHook is null?");
|
||||
Debug.Assert(this.dalamudAtlas is not null, "dalamudAtlas should have been set already");
|
||||
|
||||
|
|
|
|||
|
|
@ -163,6 +163,7 @@ internal class GamePrebakedFontsTestWidget : IDataWindowWidget, IDisposable
|
|||
.ToArray());
|
||||
|
||||
var offsetX = ImGui.CalcTextSize("99.9pt").X + (ImGui.GetStyle().FramePadding.X * 2);
|
||||
var counter = 0;
|
||||
foreach (var (family, items) in this.fontHandles)
|
||||
{
|
||||
if (!ImGui.CollapsingHeader($"{family} Family"))
|
||||
|
|
@ -188,11 +189,22 @@ internal class GamePrebakedFontsTestWidget : IDataWindowWidget, IDisposable
|
|||
{
|
||||
if (!this.useGlobalScale)
|
||||
ImGuiNative.igSetWindowFontScale(1 / ImGuiHelpers.GlobalScale);
|
||||
if (counter++ % 2 == 0)
|
||||
{
|
||||
using var pushPop = handle.Value.Push();
|
||||
ImGuiNative.igTextUnformatted(
|
||||
this.testStringBuffer.Data,
|
||||
this.testStringBuffer.Data + this.testStringBuffer.Length);
|
||||
}
|
||||
else
|
||||
{
|
||||
handle.Value.Push();
|
||||
ImGuiNative.igTextUnformatted(
|
||||
this.testStringBuffer.Data,
|
||||
this.testStringBuffer.Data + this.testStringBuffer.Length);
|
||||
handle.Value.Pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
|
|
|||
|
|
@ -65,17 +65,23 @@ public interface IFontHandle : IDisposable
|
|||
ImFontLocked Lock();
|
||||
|
||||
/// <summary>
|
||||
/// Pushes the current font into ImGui font stack using <see cref="ImGui.PushFont"/>, if available.<br />
|
||||
/// Pushes the current font into ImGui font stack, if available.<br />
|
||||
/// Use <see cref="ImGui.GetFont"/> to access the current font.<br />
|
||||
/// You may not access the font once you dispose this object.
|
||||
/// </summary>
|
||||
/// <returns>A disposable object that will call <see cref="ImGui.PopFont"/>(1) on dispose.</returns>
|
||||
/// <returns>A disposable object that will pop the font on dispose.</returns>
|
||||
/// <exception cref="InvalidOperationException">If called outside of the main thread.</exception>
|
||||
/// <remarks>
|
||||
/// Only intended for use with <c>using</c> keywords, such as <c>using (handle.Push())</c>.<br />
|
||||
/// Should you store or transfer the return value to somewhere else, use <see cref="IDisposable"/> as the type.
|
||||
/// This function uses <see cref="ImGui.PushFont"/>, and may do extra things.
|
||||
/// Use <see cref="IDisposable.Dispose"/> or <see cref="Pop"/> to undo this operation.
|
||||
/// Do not use <see cref="ImGui.PopFont"/>.
|
||||
/// </remarks>
|
||||
FontPopper Push();
|
||||
IDisposable Push();
|
||||
|
||||
/// <summary>
|
||||
/// Pops the font pushed to ImGui using <see cref="Push"/>, cleaning up any extra information as needed.
|
||||
/// </summary>
|
||||
void Pop();
|
||||
|
||||
/// <summary>
|
||||
/// Waits for <see cref="Available"/> to become <c>true</c>.
|
||||
|
|
@ -124,37 +130,4 @@ public interface IFontHandle : IDisposable
|
|||
this.ImFont = default;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The wrapper for popping fonts.
|
||||
/// </summary>
|
||||
public struct FontPopper : IDisposable
|
||||
{
|
||||
private int count;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FontPopper"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="fontPtr">The font to push.</param>
|
||||
/// <param name="push">Whether to push.</param>
|
||||
internal FontPopper(ImFontPtr fontPtr, bool push)
|
||||
{
|
||||
if (!push)
|
||||
return;
|
||||
|
||||
ThreadSafety.AssertMainThread();
|
||||
|
||||
this.count = 1;
|
||||
ImGui.PushFont(fontPtr);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
ThreadSafety.AssertMainThread();
|
||||
|
||||
while (this.count-- > 0)
|
||||
ImGui.PopFont();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,12 +2,15 @@
|
|||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Dalamud.Interface.Internal;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Logging.Internal;
|
||||
using Dalamud.Utility;
|
||||
|
||||
using ImGuiNET;
|
||||
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Interface.ManagedFontAtlas.Internals;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -15,7 +18,10 @@ namespace Dalamud.Interface.ManagedFontAtlas.Internals;
|
|||
/// </summary>
|
||||
internal class DelegateFontHandle : IFontHandle.IInternal
|
||||
{
|
||||
private readonly List<IDisposable> pushedFonts = new(8);
|
||||
|
||||
private IFontHandleManager? manager;
|
||||
private long lastCumulativePresentCalls;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DelegateFontHandle"/> class.
|
||||
|
|
@ -53,6 +59,8 @@ internal class DelegateFontHandle : IFontHandle.IInternal
|
|||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
if (this.pushedFonts.Count > 0)
|
||||
Log.Warning($"{nameof(IFontHandle)}.{nameof(IDisposable.Dispose)}: fonts were still in a stack.");
|
||||
this.manager?.FreeFontHandle(this);
|
||||
this.manager = null;
|
||||
this.Disposed?.InvokeSafely(this);
|
||||
|
|
@ -96,7 +104,33 @@ internal class DelegateFontHandle : IFontHandle.IInternal
|
|||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IFontHandle.FontPopper Push() => new(this.ImFont, this.Available);
|
||||
public IDisposable Push()
|
||||
{
|
||||
ThreadSafety.AssertMainThread();
|
||||
var cumulativePresentCalls = Service<InterfaceManager>.GetNullable()?.CumulativePresentCalls ?? 0L;
|
||||
if (this.lastCumulativePresentCalls != cumulativePresentCalls)
|
||||
{
|
||||
this.lastCumulativePresentCalls = cumulativePresentCalls;
|
||||
if (this.pushedFonts.Count > 0)
|
||||
{
|
||||
Log.Warning(
|
||||
$"{nameof(this.Push)} has been called, but the handle-private stack was not empty. " +
|
||||
$"You might be missing a call to {nameof(this.Pop)}.");
|
||||
this.pushedFonts.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
var rented = SimplePushedFont.Rent(this.pushedFonts, this.ImFont, this.Available);
|
||||
this.pushedFonts.Add(rented);
|
||||
return rented;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Pop()
|
||||
{
|
||||
ThreadSafety.AssertMainThread();
|
||||
this.pushedFonts[^1].Dispose();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task<IFontHandle> WaitAsync()
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@ using ImGuiNET;
|
|||
|
||||
using Lumina.Data.Files;
|
||||
|
||||
using Serilog;
|
||||
|
||||
using Vector4 = System.Numerics.Vector4;
|
||||
|
||||
namespace Dalamud.Interface.ManagedFontAtlas.Internals;
|
||||
|
|
@ -35,7 +37,10 @@ internal class GamePrebakedFontHandle : IFontHandle.IInternal
|
|||
/// </summary>
|
||||
public static readonly char SeIconCharMax = (char)Enum.GetValues<SeIconChar>().Max();
|
||||
|
||||
private readonly List<IDisposable> pushedFonts = new(8);
|
||||
|
||||
private IFontHandleManager? manager;
|
||||
private long lastCumulativePresentCalls;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GamePrebakedFontHandle"/> class.
|
||||
|
|
@ -160,7 +165,33 @@ internal class GamePrebakedFontHandle : IFontHandle.IInternal
|
|||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IFontHandle.FontPopper Push() => new(this.ImFont, this.Available);
|
||||
public IDisposable Push()
|
||||
{
|
||||
ThreadSafety.AssertMainThread();
|
||||
var cumulativePresentCalls = Service<InterfaceManager>.GetNullable()?.CumulativePresentCalls ?? 0L;
|
||||
if (this.lastCumulativePresentCalls != cumulativePresentCalls)
|
||||
{
|
||||
this.lastCumulativePresentCalls = cumulativePresentCalls;
|
||||
if (this.pushedFonts.Count > 0)
|
||||
{
|
||||
Log.Warning(
|
||||
$"{nameof(this.Push)} has been called, but the handle-private stack was not empty. " +
|
||||
$"You might be missing a call to {nameof(this.Pop)}.");
|
||||
this.pushedFonts.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
var rented = SimplePushedFont.Rent(this.pushedFonts, this.ImFont, this.Available);
|
||||
this.pushedFonts.Add(rented);
|
||||
return rented;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Pop()
|
||||
{
|
||||
ThreadSafety.AssertMainThread();
|
||||
this.pushedFonts[^1].Dispose();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task<IFontHandle> WaitAsync()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,78 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
|
||||
using Dalamud.Interface.Utility;
|
||||
|
||||
using ImGuiNET;
|
||||
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Interface.ManagedFontAtlas.Internals;
|
||||
|
||||
/// <summary>
|
||||
/// Reusable font push/popper.
|
||||
/// </summary>
|
||||
internal sealed class SimplePushedFont : IDisposable
|
||||
{
|
||||
// Using constructor instead of DefaultObjectPoolProvider, since we do not want the pool to call Dispose.
|
||||
private static readonly ObjectPool<SimplePushedFont> Pool =
|
||||
new DefaultObjectPool<SimplePushedFont>(new DefaultPooledObjectPolicy<SimplePushedFont>());
|
||||
|
||||
private List<IDisposable>? stack;
|
||||
private ImFontPtr font;
|
||||
|
||||
/// <summary>
|
||||
/// Pushes the font, and return an instance of <see cref="SimplePushedFont"/>.
|
||||
/// </summary>
|
||||
/// <param name="stack">The <see cref="IFontHandle"/>-private stack.</param>
|
||||
/// <param name="fontPtr">The font pointer being pushed.</param>
|
||||
/// <param name="push">Whether to push.</param>
|
||||
/// <returns><c>this</c>.</returns>
|
||||
public static SimplePushedFont Rent(List<IDisposable> stack, ImFontPtr fontPtr, bool push)
|
||||
{
|
||||
push &= !fontPtr.IsNull();
|
||||
|
||||
var rented = Pool.Get();
|
||||
Debug.Assert(rented.font.IsNull(), "Rented object must not have its font set");
|
||||
rented.stack = stack;
|
||||
|
||||
if (push)
|
||||
{
|
||||
rented.font = fontPtr;
|
||||
ImGui.PushFont(fontPtr);
|
||||
}
|
||||
|
||||
return rented;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public unsafe void Dispose()
|
||||
{
|
||||
if (this.stack is null || !ReferenceEquals(this.stack[^1], this))
|
||||
{
|
||||
throw new InvalidOperationException("Tried to pop a non-pushed font.");
|
||||
}
|
||||
|
||||
this.stack.RemoveAt(this.stack.Count - 1);
|
||||
|
||||
if (!this.font.IsNull())
|
||||
{
|
||||
if (ImGui.GetFont().NativePtr == this.font.NativePtr)
|
||||
{
|
||||
ImGui.PopFont();
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Warning(
|
||||
$"{nameof(IFontHandle.Pop)}: The font currently being popped does not match the pushed font. " +
|
||||
$"Doing nothing.");
|
||||
}
|
||||
}
|
||||
|
||||
this.font = default;
|
||||
this.stack = null;
|
||||
Pool.Return(this);
|
||||
}
|
||||
}
|
||||
|
|
@ -762,9 +762,11 @@ public sealed class UiBuilder : IDisposable
|
|||
public IFontHandle.ImFontLocked Lock() =>
|
||||
this.wrapped?.Lock() ?? throw new ObjectDisposedException(nameof(FontHandleWrapper));
|
||||
|
||||
public IFontHandle.FontPopper Push() =>
|
||||
public IDisposable Push() =>
|
||||
this.wrapped?.Push() ?? throw new ObjectDisposedException(nameof(FontHandleWrapper));
|
||||
|
||||
public void Pop() => this.wrapped?.Pop();
|
||||
|
||||
public Task<IFontHandle> WaitAsync() =>
|
||||
this.wrapped?.WaitAsync().ContinueWith(_ => (IFontHandle)this) ??
|
||||
throw new ObjectDisposedException(nameof(FontHandleWrapper));
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue