mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-12 18:27:23 +01:00
Merge pull request #1619 from Soreepeong/feature/ifontatlas-lockable-fonts
Make `IFontHandle` lockable, and add font change event
This commit is contained in:
commit
e20daed848
18 changed files with 893 additions and 146 deletions
|
|
@ -69,6 +69,10 @@ namespace Dalamud.CorePlugin
|
|||
this.Interface.UiBuilder.Draw += this.OnDraw;
|
||||
this.Interface.UiBuilder.OpenConfigUi += this.OnOpenConfigUi;
|
||||
this.Interface.UiBuilder.OpenMainUi += this.OnOpenMainUi;
|
||||
this.Interface.UiBuilder.DefaultFontHandle.ImFontChanged += fc =>
|
||||
{
|
||||
Log.Information($"CorePlugin : DefaultFontHandle.ImFontChanged called {fc}");
|
||||
};
|
||||
|
||||
Service<CommandManager>.Get().AddHandler("/coreplug", new(this.OnCommand) { HelpMessage = "Access the plugin." });
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System.Numerics;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Dalamud.Interface.ManagedFontAtlas;
|
||||
using Dalamud.Interface.ManagedFontAtlas.Internals;
|
||||
|
|
@ -28,6 +29,13 @@ public sealed class GameFontHandle : IFontHandle
|
|||
this.fontAtlasFactory = fontAtlasFactory;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public event Action<IFontHandle> ImFontChanged
|
||||
{
|
||||
add => this.fontHandle.ImFontChanged += value;
|
||||
remove => this.fontHandle.ImFontChanged -= value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Exception? LoadException => this.fontHandle.LoadException;
|
||||
|
||||
|
|
@ -55,14 +63,20 @@ public sealed class GameFontHandle : IFontHandle
|
|||
/// <inheritdoc />
|
||||
public void Dispose() => this.fontHandle.Dispose();
|
||||
|
||||
/// <inheritdoc />
|
||||
public IFontHandle.ImFontLocked Lock() => this.fontHandle.Lock();
|
||||
|
||||
/// <summary>
|
||||
/// Pushes the font.
|
||||
/// </summary>
|
||||
/// <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();
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="GameFontLayoutPlan.Builder"/>.<br />
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ using Dalamud.Game.ClientState.Keys;
|
|||
using Dalamud.Game.Internal.DXGI;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Hooking.WndProcHook;
|
||||
using Dalamud.Interface.GameFonts;
|
||||
using Dalamud.Interface.Internal.ManagedAsserts;
|
||||
using Dalamud.Interface.Internal.Notifications;
|
||||
using Dalamud.Interface.ManagedFontAtlas;
|
||||
|
|
@ -87,9 +86,6 @@ internal class InterfaceManager : IDisposable, IServiceType
|
|||
private Hook<ResizeBuffersDelegate>? resizeBuffersHook;
|
||||
|
||||
private IFontAtlas? dalamudAtlas;
|
||||
private IFontHandle.IInternal? defaultFontHandle;
|
||||
private IFontHandle.IInternal? iconFontHandle;
|
||||
private IFontHandle.IInternal? monoFontHandle;
|
||||
|
||||
// can't access imgui IO before first present call
|
||||
private bool lastWantCapture = false;
|
||||
|
|
@ -131,19 +127,34 @@ internal class InterfaceManager : IDisposable, IServiceType
|
|||
/// Gets the default ImGui font.<br />
|
||||
/// <strong>Accessing this static property outside of the main thread is dangerous and not supported.</strong>
|
||||
/// </summary>
|
||||
public static ImFontPtr DefaultFont => WhenFontsReady().defaultFontHandle!.ImFont.OrElse(ImGui.GetIO().FontDefault);
|
||||
public static ImFontPtr DefaultFont => WhenFontsReady().DefaultFontHandle!.ImFont.OrElse(ImGui.GetIO().FontDefault);
|
||||
|
||||
/// <summary>
|
||||
/// Gets an included FontAwesome icon font.<br />
|
||||
/// <strong>Accessing this static property outside of the main thread is dangerous and not supported.</strong>
|
||||
/// </summary>
|
||||
public static ImFontPtr IconFont => WhenFontsReady().iconFontHandle!.ImFont.OrElse(ImGui.GetIO().FontDefault);
|
||||
public static ImFontPtr IconFont => WhenFontsReady().IconFontHandle!.ImFont.OrElse(ImGui.GetIO().FontDefault);
|
||||
|
||||
/// <summary>
|
||||
/// Gets an included monospaced font.<br />
|
||||
/// <strong>Accessing this static property outside of the main thread is dangerous and not supported.</strong>
|
||||
/// </summary>
|
||||
public static ImFontPtr MonoFont => WhenFontsReady().monoFontHandle!.ImFont.OrElse(ImGui.GetIO().FontDefault);
|
||||
public static ImFontPtr MonoFont => WhenFontsReady().MonoFontHandle!.ImFont.OrElse(ImGui.GetIO().FontDefault);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the default font handle.
|
||||
/// </summary>
|
||||
public IFontHandle.IInternal? DefaultFontHandle { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the icon font handle.
|
||||
/// </summary>
|
||||
public IFontHandle.IInternal? IconFontHandle { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the mono font handle.
|
||||
/// </summary>
|
||||
public IFontHandle.IInternal? MonoFontHandle { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the pointer to ImGui.IO(), when it was last used.
|
||||
|
|
@ -219,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>
|
||||
|
|
@ -636,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");
|
||||
|
||||
|
|
@ -691,9 +709,9 @@ internal class InterfaceManager : IDisposable, IServiceType
|
|||
.CreateFontAtlas(nameof(InterfaceManager), FontAtlasAutoRebuildMode.Disable);
|
||||
using (this.dalamudAtlas.SuppressAutoRebuild())
|
||||
{
|
||||
this.defaultFontHandle = (IFontHandle.IInternal)this.dalamudAtlas.NewDelegateFontHandle(
|
||||
this.DefaultFontHandle = (IFontHandle.IInternal)this.dalamudAtlas.NewDelegateFontHandle(
|
||||
e => e.OnPreBuild(tk => tk.AddDalamudDefaultFont(DefaultFontSizePx)));
|
||||
this.iconFontHandle = (IFontHandle.IInternal)this.dalamudAtlas.NewDelegateFontHandle(
|
||||
this.IconFontHandle = (IFontHandle.IInternal)this.dalamudAtlas.NewDelegateFontHandle(
|
||||
e => e.OnPreBuild(
|
||||
tk => tk.AddFontAwesomeIconFont(
|
||||
new()
|
||||
|
|
@ -702,7 +720,7 @@ internal class InterfaceManager : IDisposable, IServiceType
|
|||
GlyphMinAdvanceX = DefaultFontSizePx,
|
||||
GlyphMaxAdvanceX = DefaultFontSizePx,
|
||||
})));
|
||||
this.monoFontHandle = (IFontHandle.IInternal)this.dalamudAtlas.NewDelegateFontHandle(
|
||||
this.MonoFontHandle = (IFontHandle.IInternal)this.dalamudAtlas.NewDelegateFontHandle(
|
||||
e => e.OnPreBuild(
|
||||
tk => tk.AddDalamudAssetFont(
|
||||
DalamudAsset.InconsolataRegular,
|
||||
|
|
@ -714,13 +732,16 @@ internal class InterfaceManager : IDisposable, IServiceType
|
|||
// Do not use DefaultFont, IconFont, and MonoFont.
|
||||
// Use font handles directly.
|
||||
|
||||
using var defaultFont = this.DefaultFontHandle.Lock();
|
||||
using var monoFont = this.MonoFontHandle.Lock();
|
||||
|
||||
// Fill missing glyphs in MonoFont from DefaultFont
|
||||
tk.CopyGlyphsAcrossFonts(this.defaultFontHandle.ImFont, this.monoFontHandle.ImFont, true);
|
||||
tk.CopyGlyphsAcrossFonts(defaultFont, monoFont, true);
|
||||
|
||||
// Update default font
|
||||
unsafe
|
||||
{
|
||||
ImGui.GetIO().NativePtr->FontDefault = this.defaultFontHandle.ImFont;
|
||||
ImGui.GetIO().NativePtr->FontDefault = defaultFont;
|
||||
}
|
||||
|
||||
// Broadcast to auto-rebuilding instances
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Dalamud.Interface.GameFonts;
|
||||
using Dalamud.Interface.ManagedFontAtlas;
|
||||
|
|
@ -11,6 +13,8 @@ using Dalamud.Utility;
|
|||
|
||||
using ImGuiNET;
|
||||
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Interface.Internal.Windows.Data.Widgets;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -103,6 +107,10 @@ internal class GamePrebakedFontsTestWidget : IDataWindowWidget, IDisposable
|
|||
minCapacity: 1024);
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button("Test Lock"))
|
||||
Task.Run(this.TestLock);
|
||||
|
||||
fixed (byte* labelPtr = "Test Input"u8)
|
||||
{
|
||||
if (ImGuiNative.igInputTextMultiline(
|
||||
|
|
@ -155,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"))
|
||||
|
|
@ -180,10 +189,21 @@ internal class GamePrebakedFontsTestWidget : IDataWindowWidget, IDisposable
|
|||
{
|
||||
if (!this.useGlobalScale)
|
||||
ImGuiNative.igSetWindowFontScale(1 / ImGuiHelpers.GlobalScale);
|
||||
using var pushPop = handle.Value.Push();
|
||||
ImGuiNative.igTextUnformatted(
|
||||
this.testStringBuffer.Data,
|
||||
this.testStringBuffer.Data + this.testStringBuffer.Length);
|
||||
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
|
||||
|
|
@ -210,4 +230,49 @@ internal class GamePrebakedFontsTestWidget : IDataWindowWidget, IDisposable
|
|||
this.privateAtlas?.Dispose();
|
||||
this.privateAtlas = null;
|
||||
}
|
||||
|
||||
private async void TestLock()
|
||||
{
|
||||
if (this.fontHandles is not { } fontHandlesCopy)
|
||||
return;
|
||||
|
||||
Log.Information($"{nameof(GamePrebakedFontsTestWidget)}: {nameof(this.TestLock)} waiting for build");
|
||||
|
||||
await using var garbage = new DisposeSafety.ScopedFinalizer();
|
||||
var fonts = new List<ImFontPtr>();
|
||||
IFontHandle[] handles;
|
||||
try
|
||||
{
|
||||
handles = fontHandlesCopy.Values.SelectMany(x => x).Select(x => x.Handle.Value).ToArray();
|
||||
foreach (var handle in handles)
|
||||
{
|
||||
await handle.WaitAsync();
|
||||
var locked = handle.Lock();
|
||||
garbage.Add(locked);
|
||||
fonts.Add(locked);
|
||||
}
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
Log.Information($"{nameof(GamePrebakedFontsTestWidget)}: {nameof(this.TestLock)} cancelled");
|
||||
return;
|
||||
}
|
||||
|
||||
Log.Information($"{nameof(GamePrebakedFontsTestWidget)}: {nameof(this.TestLock)} waiting in lock");
|
||||
await Task.Delay(5000);
|
||||
|
||||
foreach (var (font, handle) in fonts.Zip(handles))
|
||||
TestSingle(font, handle);
|
||||
|
||||
return;
|
||||
|
||||
unsafe void TestSingle(ImFontPtr fontPtr, IFontHandle handle)
|
||||
{
|
||||
var dim = default(Vector2);
|
||||
var test = "Test string"u8;
|
||||
fixed (byte* pTest = test)
|
||||
ImGuiNative.ImFont_CalcTextSizeA(&dim, fontPtr, fontPtr.FontSize, float.MaxValue, 0, pTest, null, null);
|
||||
Log.Information($"{nameof(GamePrebakedFontsTestWidget)}: {handle} => {dim}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@ internal class SettingsWindow : Window
|
|||
var fontAtlasFactory = Service<FontAtlasFactory>.Get();
|
||||
|
||||
var rebuildFont = fontAtlasFactory.UseAxis != configuration.UseAxisFontsFromGame;
|
||||
rebuildFont |= !Equals(ImGui.GetIO().FontGlobalScale, configuration.GlobalUiScale);
|
||||
|
||||
ImGui.GetIO().FontGlobalScale = configuration.GlobalUiScale;
|
||||
fontAtlasFactory.UseAxisOverride = null;
|
||||
|
|
|
|||
|
|
@ -122,6 +122,10 @@ public interface IFontAtlas : IDisposable
|
|||
/// Note that <see cref="BuildTask"/> would not necessarily get changed from calling this function.
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">If <see cref="AutoRebuildMode"/> is <see cref="FontAtlasAutoRebuildMode.Async"/>.</exception>
|
||||
/// <remarks>
|
||||
/// Using this method will block the main thread on rebuilding fonts, effectively calling
|
||||
/// <see cref="BuildFontsImmediately"/> from the main thread. Consider migrating to <see cref="BuildFontsAsync"/>.
|
||||
/// </remarks>
|
||||
void BuildFontsOnNextFrame();
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -54,11 +54,11 @@ public interface IFontAtlasBuildToolkitPreBuild : IFontAtlasBuildToolkit
|
|||
|
||||
/// <summary>
|
||||
/// Adds a font from memory region allocated using <see cref="ImGuiHelpers.AllocateMemory"/>.<br />
|
||||
/// <strong>It WILL crash if you try to use a memory pointer allocated in some other way.</strong><br />
|
||||
/// <strong>
|
||||
/// <b>It WILL crash if you try to use a memory pointer allocated in some other way.</b><br />
|
||||
/// <b>
|
||||
/// Do NOT call <see cref="ImGuiNative.igMemFree"/> on the <paramref name="dataPointer"/> once this function has
|
||||
/// been called, unless <paramref name="freeOnException"/> is set and the function has thrown an error.
|
||||
/// </strong>
|
||||
/// </b>
|
||||
/// </summary>
|
||||
/// <param name="dataPointer">Memory address for the data allocated using <see cref="ImGuiHelpers.AllocateMemory"/>.</param>
|
||||
/// <param name="dataSize">The size of the font file..</param>
|
||||
|
|
@ -81,9 +81,11 @@ public interface IFontAtlasBuildToolkitPreBuild : IFontAtlasBuildToolkit
|
|||
|
||||
/// <summary>
|
||||
/// Adds a font from memory region allocated using <see cref="ImGuiHelpers.AllocateMemory"/>.<br />
|
||||
/// <strong>It WILL crash if you try to use a memory pointer allocated in some other way.</strong><br />
|
||||
/// <strong>Do NOT call <see cref="ImGuiNative.igMemFree"/> on the <paramref name="dataPointer"/> once this
|
||||
/// function has been called.</strong>
|
||||
/// <b>It WILL crash if you try to use a memory pointer allocated in some other way.</b><br />
|
||||
/// <b>
|
||||
/// Do NOT call <see cref="ImGuiNative.igMemFree"/> on the <paramref name="dataPointer"/> once this function has
|
||||
/// been called, unless <paramref name="freeOnException"/> is set and the function has thrown an error.
|
||||
/// </b>
|
||||
/// </summary>
|
||||
/// <param name="dataPointer">Memory address for the data allocated using <see cref="ImGuiHelpers.AllocateMemory"/>.</param>
|
||||
/// <param name="dataSize">The size of the font file..</param>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
using Dalamud.Utility;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Dalamud.Utility;
|
||||
|
||||
using ImGuiNET;
|
||||
|
||||
|
|
@ -9,6 +11,16 @@ namespace Dalamud.Interface.ManagedFontAtlas;
|
|||
/// </summary>
|
||||
public interface IFontHandle : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Called when the built instance of <see cref="ImFontPtr"/> has been changed.<br />
|
||||
/// This event will be invoked on the same thread with
|
||||
/// <see cref="IFontAtlas"/>.<see cref="IFontAtlas.BuildStepChange"/>,
|
||||
/// when the build step is <see cref="FontAtlasBuildStep.PostPromotion"/>.<br />
|
||||
/// See <see cref="IFontAtlas.BuildFontsOnNextFrame"/>, <see cref="IFontAtlas.BuildFontsImmediately"/>, and
|
||||
/// <see cref="IFontAtlas.BuildFontsAsync"/>.
|
||||
/// </summary>
|
||||
event Action<IFontHandle> ImFontChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a reference counting handle for fonts. Dalamud internal use only.
|
||||
/// </summary>
|
||||
|
|
@ -18,7 +30,8 @@ public interface IFontHandle : IDisposable
|
|||
/// Gets the font.<br />
|
||||
/// Use of this properly is safe only from the UI thread.<br />
|
||||
/// Use <see cref="IFontHandle.Push"/> if the intended purpose of this property is <see cref="ImGui.PushFont"/>.<br />
|
||||
/// Futures changes may make simple <see cref="ImGui.PushFont"/> not enough.
|
||||
/// Futures changes may make simple <see cref="ImGui.PushFont"/> not enough.<br />
|
||||
/// If you need to access a font outside the UI thread, consider using <see cref="IFontHandle.Lock"/>.
|
||||
/// </summary>
|
||||
ImFontPtr ImFont { get; }
|
||||
}
|
||||
|
|
@ -29,54 +42,92 @@ public interface IFontHandle : IDisposable
|
|||
Exception? LoadException { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this font is ready for use.<br />
|
||||
/// Use <see cref="Push"/> directly if you want to keep the current ImGui font if the font is not ready.
|
||||
/// Gets a value indicating whether this font is ready for use.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Once set to <c>true</c>, it will remain <c>true</c>.<br />
|
||||
/// Use <see cref="Push"/> directly if you want to keep the current ImGui font if the font is not ready.<br />
|
||||
/// Alternatively, use <see cref="WaitAsync"/> to wait for this property to become <c>true</c>.
|
||||
/// </remarks>
|
||||
bool Available { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Pushes the current font into ImGui font stack using <see cref="ImGui.PushFont"/>, if available.<br />
|
||||
/// Locks the fully constructed instance of <see cref="ImFontPtr"/> corresponding to the this
|
||||
/// <see cref="IFontHandle"/>, for use in any thread.<br />
|
||||
/// Modification of the font will exhibit undefined behavior if some other thread also uses the font.
|
||||
/// </summary>
|
||||
/// <returns>An instance of <see cref="ImFontLocked"/> that <b>must</b> be disposed after use.</returns>
|
||||
/// <remarks>
|
||||
/// Calling <see cref="IFontHandle"/>.<see cref="IDisposable.Dispose"/> will not unlock the <see cref="ImFontPtr"/>
|
||||
/// locked by this function.
|
||||
/// </remarks>
|
||||
/// <exception cref="InvalidOperationException">If <see cref="Available"/> is <c>false</c>.</exception>
|
||||
ImFontLocked Lock();
|
||||
|
||||
/// <summary>
|
||||
/// 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>
|
||||
/// The wrapper for popping fonts.
|
||||
/// Pops the font pushed to ImGui using <see cref="Push"/>, cleaning up any extra information as needed.
|
||||
/// </summary>
|
||||
public struct FontPopper : IDisposable
|
||||
void Pop();
|
||||
|
||||
/// <summary>
|
||||
/// Waits for <see cref="Available"/> to become <c>true</c>.
|
||||
/// </summary>
|
||||
/// <returns>A task containing this <see cref="IFontHandle"/>.</returns>
|
||||
Task<IFontHandle> WaitAsync();
|
||||
|
||||
/// <summary>
|
||||
/// The wrapper for <see cref="ImFontPtr"/>, guaranteeing that the associated data will be available as long as
|
||||
/// this struct is not disposed.
|
||||
/// </summary>
|
||||
public struct ImFontLocked : IDisposable
|
||||
{
|
||||
private int count;
|
||||
/// <summary>
|
||||
/// The associated <see cref="ImFontPtr"/>.
|
||||
/// </summary>
|
||||
public ImFontPtr ImFont;
|
||||
|
||||
private IRefCountable? owner;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FontPopper"/> struct.
|
||||
/// Initializes a new instance of the <see cref="ImFontLocked"/> struct,
|
||||
/// and incrase the reference count of <paramref name="owner"/>.
|
||||
/// </summary>
|
||||
/// <param name="fontPtr">The font to push.</param>
|
||||
/// <param name="push">Whether to push.</param>
|
||||
internal FontPopper(ImFontPtr fontPtr, bool push)
|
||||
/// <param name="imFont">The contained font.</param>
|
||||
/// <param name="owner">The owner.</param>
|
||||
internal ImFontLocked(ImFontPtr imFont, IRefCountable owner)
|
||||
{
|
||||
if (!push)
|
||||
return;
|
||||
|
||||
ThreadSafety.AssertMainThread();
|
||||
|
||||
this.count = 1;
|
||||
ImGui.PushFont(fontPtr);
|
||||
owner.AddRef();
|
||||
this.ImFont = imFont;
|
||||
this.owner = owner;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public static implicit operator ImFontPtr(ImFontLocked l) => l.ImFont;
|
||||
|
||||
public static unsafe implicit operator ImFont*(ImFontLocked l) => l.ImFont.NativePtr;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
ThreadSafety.AssertMainThread();
|
||||
if (this.owner is null)
|
||||
return;
|
||||
|
||||
while (this.count-- > 0)
|
||||
ImGui.PopFont();
|
||||
this.owner.Release();
|
||||
this.owner = null;
|
||||
this.ImFont = default;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,16 @@
|
|||
using System.Collections.Generic;
|
||||
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>
|
||||
|
|
@ -14,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.
|
||||
|
|
@ -27,6 +34,11 @@ internal class DelegateFontHandle : IFontHandle.IInternal
|
|||
this.CallOnBuildStepChange = callOnBuildStepChange;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event Action<IFontHandle>? ImFontChanged;
|
||||
|
||||
private event Action<IFontHandle>? Disposed;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the function to be called on build step changes.
|
||||
/// </summary>
|
||||
|
|
@ -47,12 +59,105 @@ 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);
|
||||
this.ImFontChanged = null;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IFontHandle.FontPopper Push() => new(this.ImFont, this.Available);
|
||||
public IFontHandle.ImFontLocked Lock()
|
||||
{
|
||||
IFontHandleSubstance? prevSubstance = default;
|
||||
while (true)
|
||||
{
|
||||
var substance = this.ManagerNotDisposed.Substance;
|
||||
if (substance is null)
|
||||
throw new InvalidOperationException();
|
||||
if (substance == prevSubstance)
|
||||
throw new ObjectDisposedException(nameof(DelegateFontHandle));
|
||||
|
||||
prevSubstance = substance;
|
||||
try
|
||||
{
|
||||
substance.DataRoot.AddRef();
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var fontPtr = substance.GetFontPtr(this);
|
||||
if (fontPtr.IsNull())
|
||||
continue;
|
||||
return new(fontPtr, substance.DataRoot);
|
||||
}
|
||||
finally
|
||||
{
|
||||
substance.DataRoot.Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
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()
|
||||
{
|
||||
if (this.Available)
|
||||
return Task.FromResult<IFontHandle>(this);
|
||||
|
||||
var tcs = new TaskCompletionSource<IFontHandle>();
|
||||
this.ImFontChanged += OnImFontChanged;
|
||||
this.Disposed += OnImFontChanged;
|
||||
if (this.Available)
|
||||
OnImFontChanged(this);
|
||||
return tcs.Task;
|
||||
|
||||
void OnImFontChanged(IFontHandle unused)
|
||||
{
|
||||
if (tcs.Task.IsCompletedSuccessfully)
|
||||
return;
|
||||
|
||||
this.ImFontChanged -= OnImFontChanged;
|
||||
this.Disposed -= OnImFontChanged;
|
||||
if (this.manager is null)
|
||||
tcs.SetException(new ObjectDisposedException(nameof(GamePrebakedFontHandle)));
|
||||
else
|
||||
tcs.SetResult(this);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Manager for <see cref="DelegateFontHandle"/>s.
|
||||
|
|
@ -81,11 +186,7 @@ internal class DelegateFontHandle : IFontHandle.IInternal
|
|||
public void Dispose()
|
||||
{
|
||||
lock (this.syncRoot)
|
||||
{
|
||||
this.handles.Clear();
|
||||
this.Substance?.Dispose();
|
||||
this.Substance = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IFontAtlas.NewDelegateFontHandle"/>
|
||||
|
|
@ -109,10 +210,20 @@ internal class DelegateFontHandle : IFontHandle.IInternal
|
|||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IFontHandleSubstance NewSubstance()
|
||||
public void InvokeFontHandleImFontChanged()
|
||||
{
|
||||
if (this.Substance is not HandleSubstance hs)
|
||||
return;
|
||||
|
||||
foreach (var handle in hs.RelevantHandles)
|
||||
handle.ImFontChanged?.InvokeSafely(handle);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IFontHandleSubstance NewSubstance(IRefCountable dataRoot)
|
||||
{
|
||||
lock (this.syncRoot)
|
||||
return new HandleSubstance(this, this.handles.ToArray());
|
||||
return new HandleSubstance(this, dataRoot, this.handles.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -123,9 +234,6 @@ internal class DelegateFontHandle : IFontHandle.IInternal
|
|||
{
|
||||
private static readonly ModuleLog Log = new($"{nameof(DelegateFontHandle)}.{nameof(HandleSubstance)}");
|
||||
|
||||
// Not owned by this class. Do not dispose.
|
||||
private readonly DelegateFontHandle[] relevantHandles;
|
||||
|
||||
// Owned by this class, but ImFontPtr values still do not belong to this.
|
||||
private readonly Dictionary<DelegateFontHandle, ImFontPtr> fonts = new();
|
||||
private readonly Dictionary<DelegateFontHandle, Exception?> buildExceptions = new();
|
||||
|
|
@ -134,13 +242,29 @@ internal class DelegateFontHandle : IFontHandle.IInternal
|
|||
/// Initializes a new instance of the <see cref="HandleSubstance"/> class.
|
||||
/// </summary>
|
||||
/// <param name="manager">The manager.</param>
|
||||
/// <param name="dataRoot">The data root.</param>
|
||||
/// <param name="relevantHandles">The relevant handles.</param>
|
||||
public HandleSubstance(IFontHandleManager manager, DelegateFontHandle[] relevantHandles)
|
||||
public HandleSubstance(
|
||||
IFontHandleManager manager,
|
||||
IRefCountable dataRoot,
|
||||
DelegateFontHandle[] relevantHandles)
|
||||
{
|
||||
// We do not call dataRoot.AddRef; this object is dependant on lifetime of dataRoot.
|
||||
|
||||
this.Manager = manager;
|
||||
this.relevantHandles = relevantHandles;
|
||||
this.DataRoot = dataRoot;
|
||||
this.RelevantHandles = relevantHandles;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the relevant handles.
|
||||
/// </summary>
|
||||
// Not owned by this class. Do not dispose.
|
||||
public DelegateFontHandle[] RelevantHandles { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IRefCountable DataRoot { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IFontHandleManager Manager { get; }
|
||||
|
||||
|
|
@ -171,7 +295,7 @@ internal class DelegateFontHandle : IFontHandle.IInternal
|
|||
public void OnPreBuild(IFontAtlasBuildToolkitPreBuild toolkitPreBuild)
|
||||
{
|
||||
var fontsVector = toolkitPreBuild.Fonts;
|
||||
foreach (var k in this.relevantHandles)
|
||||
foreach (var k in this.RelevantHandles)
|
||||
{
|
||||
var fontCountPrevious = fontsVector.Length;
|
||||
|
||||
|
|
@ -288,7 +412,7 @@ internal class DelegateFontHandle : IFontHandle.IInternal
|
|||
/// <inheritdoc/>
|
||||
public void OnPostBuild(IFontAtlasBuildToolkitPostBuild toolkitPostBuild)
|
||||
{
|
||||
foreach (var k in this.relevantHandles)
|
||||
foreach (var k in this.RelevantHandles)
|
||||
{
|
||||
if (!this.fonts[k].IsNotNullAndLoaded())
|
||||
continue;
|
||||
|
|
@ -315,7 +439,7 @@ internal class DelegateFontHandle : IFontHandle.IInternal
|
|||
/// <inheritdoc/>
|
||||
public void OnPostPromotion(IFontAtlasBuildToolkitPostPromotion toolkitPostPromotion)
|
||||
{
|
||||
foreach (var k in this.relevantHandles)
|
||||
foreach (var k in this.RelevantHandles)
|
||||
{
|
||||
if (!this.fonts[k].IsNotNullAndLoaded())
|
||||
continue;
|
||||
|
|
|
|||
|
|
@ -43,68 +43,67 @@ internal sealed partial class FontAtlasFactory
|
|||
|
||||
private static readonly Task<FontAtlasBuiltData> EmptyTask = Task.FromResult(default(FontAtlasBuiltData));
|
||||
|
||||
private struct FontAtlasBuiltData : IDisposable
|
||||
private class FontAtlasBuiltData : IRefCountable
|
||||
{
|
||||
public readonly DalamudFontAtlas? Owner;
|
||||
public readonly ImFontAtlasPtr Atlas;
|
||||
public readonly float Scale;
|
||||
private readonly List<IDalamudTextureWrap> wraps;
|
||||
private readonly List<IFontHandleSubstance> substances;
|
||||
|
||||
public bool IsBuildInProgress;
|
||||
private int refCount;
|
||||
|
||||
private readonly List<IDalamudTextureWrap>? wraps;
|
||||
private readonly List<IFontHandleSubstance>? substances;
|
||||
private readonly DisposeSafety.ScopedFinalizer? garbage;
|
||||
|
||||
public unsafe FontAtlasBuiltData(
|
||||
DalamudFontAtlas owner,
|
||||
IEnumerable<IFontHandleSubstance> substances,
|
||||
float scale)
|
||||
public unsafe FontAtlasBuiltData(DalamudFontAtlas owner, float scale)
|
||||
{
|
||||
this.Owner = owner;
|
||||
this.Scale = scale;
|
||||
this.garbage = new();
|
||||
this.Garbage = new();
|
||||
this.refCount = 1;
|
||||
|
||||
try
|
||||
{
|
||||
var substancesList = this.substances = new();
|
||||
foreach (var s in substances)
|
||||
substancesList.Add(this.garbage.Add(s));
|
||||
this.garbage.Add(() => substancesList.Clear());
|
||||
this.Garbage.Add(() => substancesList.Clear());
|
||||
|
||||
var wrapsCopy = this.wraps = new();
|
||||
this.garbage.Add(() => wrapsCopy.Clear());
|
||||
this.Garbage.Add(() => wrapsCopy.Clear());
|
||||
|
||||
var atlasPtr = ImGuiNative.ImFontAtlas_ImFontAtlas();
|
||||
this.Atlas = atlasPtr;
|
||||
if (this.Atlas.NativePtr is null)
|
||||
throw new OutOfMemoryException($"Failed to allocate a new {nameof(ImFontAtlas)}.");
|
||||
|
||||
this.garbage.Add(() => ImGuiNative.ImFontAtlas_destroy(atlasPtr));
|
||||
this.Garbage.Add(() => ImGuiNative.ImFontAtlas_destroy(atlasPtr));
|
||||
this.IsBuildInProgress = true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
this.garbage.Dispose();
|
||||
this.Garbage.Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly DisposeSafety.ScopedFinalizer Garbage =>
|
||||
this.garbage ?? throw new ObjectDisposedException(nameof(FontAtlasBuiltData));
|
||||
public DalamudFontAtlas? Owner { get; }
|
||||
|
||||
public readonly ImVectorWrapper<ImFontPtr> Fonts => this.Atlas.FontsWrapped();
|
||||
public ImFontAtlasPtr Atlas { get; }
|
||||
|
||||
public readonly ImVectorWrapper<ImFontConfig> ConfigData => this.Atlas.ConfigDataWrapped();
|
||||
public float Scale { get; }
|
||||
|
||||
public readonly ImVectorWrapper<ImFontAtlasTexture> ImTextures => this.Atlas.TexturesWrapped();
|
||||
public bool IsBuildInProgress { get; set; }
|
||||
|
||||
public readonly IReadOnlyList<IDalamudTextureWrap> Wraps =>
|
||||
(IReadOnlyList<IDalamudTextureWrap>?)this.wraps ?? Array.Empty<IDalamudTextureWrap>();
|
||||
public DisposeSafety.ScopedFinalizer Garbage { get; }
|
||||
|
||||
public readonly IReadOnlyList<IFontHandleSubstance> Substances =>
|
||||
(IReadOnlyList<IFontHandleSubstance>?)this.substances ?? Array.Empty<IFontHandleSubstance>();
|
||||
public ImVectorWrapper<ImFontPtr> Fonts => this.Atlas.FontsWrapped();
|
||||
|
||||
public readonly void AddExistingTexture(IDalamudTextureWrap wrap)
|
||||
public ImVectorWrapper<ImFontConfig> ConfigData => this.Atlas.ConfigDataWrapped();
|
||||
|
||||
public ImVectorWrapper<ImFontAtlasTexture> ImTextures => this.Atlas.TexturesWrapped();
|
||||
|
||||
public IReadOnlyList<IDalamudTextureWrap> Wraps => this.wraps;
|
||||
|
||||
public IReadOnlyList<IFontHandleSubstance> Substances => this.substances;
|
||||
|
||||
public void InitialAddSubstance(IFontHandleSubstance substance) =>
|
||||
this.substances.Add(this.Garbage.Add(substance));
|
||||
|
||||
public void AddExistingTexture(IDalamudTextureWrap wrap)
|
||||
{
|
||||
if (this.wraps is null)
|
||||
throw new ObjectDisposedException(nameof(FontAtlasBuiltData));
|
||||
|
|
@ -112,7 +111,7 @@ internal sealed partial class FontAtlasFactory
|
|||
this.wraps.Add(this.Garbage.Add(wrap));
|
||||
}
|
||||
|
||||
public readonly int AddNewTexture(IDalamudTextureWrap wrap, bool disposeOnError)
|
||||
public int AddNewTexture(IDalamudTextureWrap wrap, bool disposeOnError)
|
||||
{
|
||||
if (this.wraps is null)
|
||||
throw new ObjectDisposedException(nameof(FontAtlasBuiltData));
|
||||
|
|
@ -160,27 +159,47 @@ internal sealed partial class FontAtlasFactory
|
|||
return index;
|
||||
}
|
||||
|
||||
public unsafe void Dispose()
|
||||
public int AddRef() => IRefCountable.AlterRefCount(1, ref this.refCount, out var newRefCount) switch
|
||||
{
|
||||
if (this.garbage is null)
|
||||
return;
|
||||
IRefCountable.RefCountResult.StillAlive => newRefCount,
|
||||
IRefCountable.RefCountResult.AlreadyDisposed =>
|
||||
throw new ObjectDisposedException(nameof(FontAtlasBuiltData)),
|
||||
IRefCountable.RefCountResult.FinalRelease => throw new InvalidOperationException(),
|
||||
_ => throw new InvalidOperationException(),
|
||||
};
|
||||
|
||||
if (this.IsBuildInProgress)
|
||||
public unsafe int Release()
|
||||
{
|
||||
switch (IRefCountable.AlterRefCount(-1, ref this.refCount, out var newRefCount))
|
||||
{
|
||||
Log.Error(
|
||||
"[{name}] 0x{ptr:X}: Trying to dispose while build is in progress; waiting for build.\n" +
|
||||
"Stack:\n{trace}",
|
||||
this.Owner?.Name ?? "<?>",
|
||||
(nint)this.Atlas.NativePtr,
|
||||
new StackTrace());
|
||||
while (this.IsBuildInProgress)
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
case IRefCountable.RefCountResult.StillAlive:
|
||||
return newRefCount;
|
||||
|
||||
case IRefCountable.RefCountResult.FinalRelease:
|
||||
if (this.IsBuildInProgress)
|
||||
{
|
||||
Log.Error(
|
||||
"[{name}] 0x{ptr:X}: Trying to dispose while build is in progress; waiting for build.\n" +
|
||||
"Stack:\n{trace}",
|
||||
this.Owner?.Name ?? "<?>",
|
||||
(nint)this.Atlas.NativePtr,
|
||||
new StackTrace());
|
||||
while (this.IsBuildInProgress)
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
|
||||
#if VeryVerboseLog
|
||||
Log.Verbose("[{name}] 0x{ptr:X}: Disposing", this.Owner?.Name ?? "<?>", (nint)this.Atlas.NativePtr);
|
||||
Log.Verbose("[{name}] 0x{ptr:X}: Disposing", this.Owner?.Name ?? "<?>", (nint)this.Atlas.NativePtr);
|
||||
#endif
|
||||
this.garbage.Dispose();
|
||||
this.Garbage.Dispose();
|
||||
return newRefCount;
|
||||
|
||||
case IRefCountable.RefCountResult.AlreadyDisposed:
|
||||
throw new ObjectDisposedException(nameof(FontAtlasBuiltData));
|
||||
|
||||
default:
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
public BuildToolkit CreateToolkit(FontAtlasFactory factory, bool isAsync)
|
||||
|
|
@ -201,8 +220,8 @@ internal sealed partial class FontAtlasFactory
|
|||
private readonly object syncRootPostPromotion = new();
|
||||
private readonly object syncRoot = new();
|
||||
|
||||
private Task<FontAtlasBuiltData> buildTask = EmptyTask;
|
||||
private FontAtlasBuiltData builtData;
|
||||
private Task<FontAtlasBuiltData?> buildTask = EmptyTask;
|
||||
private FontAtlasBuiltData? builtData;
|
||||
|
||||
private int buildSuppressionCounter;
|
||||
private bool buildSuppressionSuppressed;
|
||||
|
|
@ -275,7 +294,8 @@ internal sealed partial class FontAtlasFactory
|
|||
lock (this.syncRoot)
|
||||
{
|
||||
this.buildTask.ToDisposableIgnoreExceptions().Dispose();
|
||||
this.builtData.Dispose();
|
||||
this.builtData?.Release();
|
||||
this.builtData = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -303,7 +323,7 @@ internal sealed partial class FontAtlasFactory
|
|||
get
|
||||
{
|
||||
lock (this.syncRoot)
|
||||
return this.builtData.Atlas;
|
||||
return this.builtData?.Atlas ?? default;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -311,7 +331,7 @@ internal sealed partial class FontAtlasFactory
|
|||
public Task BuildTask => this.buildTask;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool HasBuiltAtlas => !this.builtData.Atlas.IsNull();
|
||||
public bool HasBuiltAtlas => !(this.builtData?.Atlas.IsNull() ?? true);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsGlobalScaled { get; }
|
||||
|
|
@ -474,13 +494,13 @@ internal sealed partial class FontAtlasFactory
|
|||
var rebuildIndex = ++this.buildIndex;
|
||||
return this.buildTask = this.buildTask.ContinueWith(BuildInner).Unwrap();
|
||||
|
||||
async Task<FontAtlasBuiltData> BuildInner(Task<FontAtlasBuiltData> unused)
|
||||
async Task<FontAtlasBuiltData?> BuildInner(Task<FontAtlasBuiltData> unused)
|
||||
{
|
||||
Log.Verbose("[{name}] Building from {source}.", this.Name, nameof(this.BuildFontsAsync));
|
||||
lock (this.syncRoot)
|
||||
{
|
||||
if (this.buildIndex != rebuildIndex)
|
||||
return default;
|
||||
return null;
|
||||
}
|
||||
|
||||
var res = await this.RebuildFontsPrivate(true, scale);
|
||||
|
|
@ -512,8 +532,10 @@ internal sealed partial class FontAtlasFactory
|
|||
return;
|
||||
}
|
||||
|
||||
this.builtData.ExplicitDisposeIgnoreExceptions();
|
||||
var prevBuiltData = this.builtData;
|
||||
this.builtData = data;
|
||||
prevBuiltData.ExplicitDisposeIgnoreExceptions();
|
||||
|
||||
this.buildTask = EmptyTask;
|
||||
foreach (var substance in data.Substances)
|
||||
substance.Manager.Substance = substance;
|
||||
|
|
@ -570,6 +592,9 @@ internal sealed partial class FontAtlasFactory
|
|||
}
|
||||
}
|
||||
|
||||
foreach (var substance in data.Substances)
|
||||
substance.Manager.InvokeFontHandleImFontChanged();
|
||||
|
||||
#if VeryVerboseLog
|
||||
Log.Verbose("[{name}] Built from {source}.", this.Name, source);
|
||||
#endif
|
||||
|
|
@ -610,12 +635,14 @@ internal sealed partial class FontAtlasFactory
|
|||
var sw = new Stopwatch();
|
||||
sw.Start();
|
||||
|
||||
var res = default(FontAtlasBuiltData);
|
||||
FontAtlasBuiltData? res = null;
|
||||
nint atlasPtr = 0;
|
||||
BuildToolkit? toolkit = null;
|
||||
try
|
||||
{
|
||||
res = new(this, this.fontHandleManagers.Select(x => x.NewSubstance()), scale);
|
||||
res = new(this, scale);
|
||||
foreach (var fhm in this.fontHandleManagers)
|
||||
res.InitialAddSubstance(fhm.NewSubstance(res));
|
||||
unsafe
|
||||
{
|
||||
atlasPtr = (nint)res.Atlas.NativePtr;
|
||||
|
|
@ -646,9 +673,11 @@ internal sealed partial class FontAtlasFactory
|
|||
|
||||
res.IsBuildInProgress = false;
|
||||
toolkit.Dispose();
|
||||
res.Dispose();
|
||||
res.Release();
|
||||
|
||||
res = new(this, this.fontHandleManagers.Select(x => x.NewSubstance()), scale);
|
||||
res = new(this, scale);
|
||||
foreach (var fhm in this.fontHandleManagers)
|
||||
res.InitialAddSubstance(fhm.NewSubstance(res));
|
||||
unsafe
|
||||
{
|
||||
atlasPtr = (nint)res.Atlas.NativePtr;
|
||||
|
|
@ -715,8 +744,12 @@ internal sealed partial class FontAtlasFactory
|
|||
nameof(this.RebuildFontsPrivateReal),
|
||||
atlasPtr,
|
||||
sw.ElapsedMilliseconds);
|
||||
res.IsBuildInProgress = false;
|
||||
res.Dispose();
|
||||
if (res is not null)
|
||||
{
|
||||
res.IsBuildInProgress = false;
|
||||
res.Release();
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ using System.Collections.Generic;
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Dalamud.Game.Text;
|
||||
using Dalamud.Interface.GameFonts;
|
||||
|
|
@ -15,6 +16,8 @@ using ImGuiNET;
|
|||
|
||||
using Lumina.Data.Files;
|
||||
|
||||
using Serilog;
|
||||
|
||||
using Vector4 = System.Numerics.Vector4;
|
||||
|
||||
namespace Dalamud.Interface.ManagedFontAtlas.Internals;
|
||||
|
|
@ -34,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.
|
||||
|
|
@ -53,6 +59,11 @@ internal class GamePrebakedFontHandle : IFontHandle.IInternal
|
|||
this.FontStyle = style;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event Action<IFontHandle>? ImFontChanged;
|
||||
|
||||
private event Action<IFontHandle>? Disposed;
|
||||
|
||||
/// <summary>
|
||||
/// Provider for <see cref="IDalamudTextureWrap"/> for `common/font/fontNN.tex`.
|
||||
/// </summary>
|
||||
|
|
@ -113,10 +124,104 @@ internal class GamePrebakedFontHandle : IFontHandle.IInternal
|
|||
{
|
||||
this.manager?.FreeFontHandle(this);
|
||||
this.manager = null;
|
||||
this.Disposed?.InvokeSafely(this);
|
||||
this.ImFontChanged = null;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IFontHandle.FontPopper Push() => new(this.ImFont, this.Available);
|
||||
public IFontHandle.ImFontLocked Lock()
|
||||
{
|
||||
IFontHandleSubstance? prevSubstance = default;
|
||||
while (true)
|
||||
{
|
||||
var substance = this.ManagerNotDisposed.Substance;
|
||||
if (substance is null)
|
||||
throw new InvalidOperationException();
|
||||
if (substance == prevSubstance)
|
||||
throw new ObjectDisposedException(nameof(DelegateFontHandle));
|
||||
|
||||
prevSubstance = substance;
|
||||
try
|
||||
{
|
||||
substance.DataRoot.AddRef();
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var fontPtr = substance.GetFontPtr(this);
|
||||
if (fontPtr.IsNull())
|
||||
continue;
|
||||
return new(fontPtr, substance.DataRoot);
|
||||
}
|
||||
finally
|
||||
{
|
||||
substance.DataRoot.Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
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()
|
||||
{
|
||||
if (this.Available)
|
||||
return Task.FromResult<IFontHandle>(this);
|
||||
|
||||
var tcs = new TaskCompletionSource<IFontHandle>();
|
||||
this.ImFontChanged += OnImFontChanged;
|
||||
this.Disposed += OnImFontChanged;
|
||||
if (this.Available)
|
||||
OnImFontChanged(this);
|
||||
return tcs.Task;
|
||||
|
||||
void OnImFontChanged(IFontHandle unused)
|
||||
{
|
||||
if (tcs.Task.IsCompletedSuccessfully)
|
||||
return;
|
||||
|
||||
this.ImFontChanged -= OnImFontChanged;
|
||||
this.Disposed -= OnImFontChanged;
|
||||
if (this.manager is null)
|
||||
tcs.SetException(new ObjectDisposedException(nameof(GamePrebakedFontHandle)));
|
||||
else
|
||||
tcs.SetResult(this);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString() => $"{nameof(GamePrebakedFontHandle)}({this.FontStyle})";
|
||||
|
||||
/// <summary>
|
||||
/// Manager for <see cref="GamePrebakedFontHandle"/>s.
|
||||
|
|
@ -124,6 +229,7 @@ internal class GamePrebakedFontHandle : IFontHandle.IInternal
|
|||
internal sealed class HandleManager : IFontHandleManager
|
||||
{
|
||||
private readonly Dictionary<GameFontStyle, int> gameFontsRc = new();
|
||||
private readonly HashSet<GamePrebakedFontHandle> handles = new();
|
||||
private readonly object syncRoot = new();
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -154,8 +260,7 @@ internal class GamePrebakedFontHandle : IFontHandle.IInternal
|
|||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
this.Substance?.Dispose();
|
||||
this.Substance = null;
|
||||
// empty
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IFontAtlas.NewGameFontHandle"/>
|
||||
|
|
@ -165,6 +270,7 @@ internal class GamePrebakedFontHandle : IFontHandle.IInternal
|
|||
bool suggestRebuild;
|
||||
lock (this.syncRoot)
|
||||
{
|
||||
this.handles.Add(handle);
|
||||
this.gameFontsRc[style] = this.gameFontsRc.GetValueOrDefault(style, 0) + 1;
|
||||
suggestRebuild = this.Substance?.GetFontPtr(handle).IsNotNullAndLoaded() is not true;
|
||||
}
|
||||
|
|
@ -183,6 +289,7 @@ internal class GamePrebakedFontHandle : IFontHandle.IInternal
|
|||
|
||||
lock (this.syncRoot)
|
||||
{
|
||||
this.handles.Remove(ggfh);
|
||||
if (!this.gameFontsRc.ContainsKey(ggfh.FontStyle))
|
||||
return;
|
||||
|
||||
|
|
@ -192,10 +299,20 @@ internal class GamePrebakedFontHandle : IFontHandle.IInternal
|
|||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IFontHandleSubstance NewSubstance()
|
||||
public void InvokeFontHandleImFontChanged()
|
||||
{
|
||||
if (this.Substance is not HandleSubstance hs)
|
||||
return;
|
||||
|
||||
foreach (var handle in hs.RelevantHandles)
|
||||
handle.ImFontChanged?.InvokeSafely(handle);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IFontHandleSubstance NewSubstance(IRefCountable dataRoot)
|
||||
{
|
||||
lock (this.syncRoot)
|
||||
return new HandleSubstance(this, this.gameFontsRc.Keys);
|
||||
return new HandleSubstance(this, dataRoot, this.handles.ToArray(), this.gameFontsRc.Keys);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -218,14 +335,32 @@ internal class GamePrebakedFontHandle : IFontHandle.IInternal
|
|||
/// Initializes a new instance of the <see cref="HandleSubstance"/> class.
|
||||
/// </summary>
|
||||
/// <param name="manager">The manager.</param>
|
||||
/// <param name="dataRoot">The data root.</param>
|
||||
/// <param name="relevantHandles">The relevant handles.</param>
|
||||
/// <param name="gameFontStyles">The game font styles.</param>
|
||||
public HandleSubstance(HandleManager manager, IEnumerable<GameFontStyle> gameFontStyles)
|
||||
public HandleSubstance(
|
||||
HandleManager manager,
|
||||
IRefCountable dataRoot,
|
||||
GamePrebakedFontHandle[] relevantHandles,
|
||||
IEnumerable<GameFontStyle> gameFontStyles)
|
||||
{
|
||||
// We do not call dataRoot.AddRef; this object is dependant on lifetime of dataRoot.
|
||||
|
||||
this.handleManager = manager;
|
||||
Service<InterfaceManager>.Get();
|
||||
this.DataRoot = dataRoot;
|
||||
this.RelevantHandles = relevantHandles;
|
||||
this.gameFontStyles = new(gameFontStyles);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the relevant handles.
|
||||
/// </summary>
|
||||
// Not owned by this class. Do not dispose.
|
||||
public GamePrebakedFontHandle[] RelevantHandles { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IRefCountable DataRoot { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IFontHandleManager Manager => this.handleManager;
|
||||
|
||||
|
|
@ -240,6 +375,7 @@ internal class GamePrebakedFontHandle : IFontHandle.IInternal
|
|||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
// empty
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
using Dalamud.Utility;
|
||||
|
||||
namespace Dalamud.Interface.ManagedFontAtlas.Internals;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -27,6 +29,12 @@ internal interface IFontHandleManager : IDisposable
|
|||
/// <summary>
|
||||
/// Creates a new substance of the font atlas.
|
||||
/// </summary>
|
||||
/// <param name="dataRoot">The data root.</param>
|
||||
/// <returns>The new substance.</returns>
|
||||
IFontHandleSubstance NewSubstance();
|
||||
IFontHandleSubstance NewSubstance(IRefCountable dataRoot);
|
||||
|
||||
/// <summary>
|
||||
/// Invokes <see cref="IFontHandle.ImFontChanged"/>.
|
||||
/// </summary>
|
||||
void InvokeFontHandleImFontChanged();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,11 @@ namespace Dalamud.Interface.ManagedFontAtlas.Internals;
|
|||
/// </summary>
|
||||
internal interface IFontHandleSubstance : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the data root relevant to this instance of <see cref="IFontHandleSubstance"/>.
|
||||
/// </summary>
|
||||
IRefCountable DataRoot { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the manager relevant to this instance of <see cref="IFontHandleSubstance"/>.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -13,6 +13,7 @@ using Dalamud.Interface.Internal.ManagedAsserts;
|
|||
using Dalamud.Interface.Internal.Notifications;
|
||||
using Dalamud.Interface.ManagedFontAtlas;
|
||||
using Dalamud.Interface.ManagedFontAtlas.Internals;
|
||||
using Dalamud.Plugin.Internal.Types;
|
||||
using Dalamud.Utility;
|
||||
using ImGuiNET;
|
||||
using ImGuiScene;
|
||||
|
|
@ -41,6 +42,10 @@ public sealed class UiBuilder : IDisposable
|
|||
private bool hasErrorWindow = false;
|
||||
private bool lastFrameUiHideState = false;
|
||||
|
||||
private IFontHandle? defaultFontHandle;
|
||||
private IFontHandle? iconFontHandle;
|
||||
private IFontHandle? monoFontHandle;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="UiBuilder"/> class and registers it.
|
||||
/// You do not have to call this manually.
|
||||
|
|
@ -99,21 +104,57 @@ public sealed class UiBuilder : IDisposable
|
|||
|
||||
/// <summary>
|
||||
/// Gets or sets an action that is called any time ImGui fonts need to be rebuilt.<br/>
|
||||
/// Any ImFontPtr objects that you store <strong>can be invalidated</strong> when fonts are rebuilt
|
||||
/// Any ImFontPtr objects that you store <b>can be invalidated</b> when fonts are rebuilt
|
||||
/// (at any time), so you should both reload your custom fonts and restore those
|
||||
/// pointers inside this handler.
|
||||
/// </summary>
|
||||
[Obsolete($"Use {nameof(this.FontAtlas)} instead.", false)]
|
||||
/// <remarks>
|
||||
/// To add your custom font, use <see cref="FontAtlas"/>.<see cref="IFontAtlas.NewDelegateFontHandle"/> or
|
||||
/// <see cref="IFontAtlas.NewGameFontHandle"/>.<br />
|
||||
/// To be notified on font changes after fonts are built, use
|
||||
/// <see cref="DefaultFontHandle"/>.<see cref="IFontHandle.ImFontChanged"/>.<br />
|
||||
/// For all other purposes, use <see cref="FontAtlas"/>.<see cref="IFontAtlas.BuildStepChange"/>.<br />
|
||||
/// <br />
|
||||
/// Note that you will be calling above functions once, instead of every time inside a build step change callback.
|
||||
/// For example, you can make all font handles from your plugin constructor, and then use the created handles during
|
||||
/// <see cref="Draw"/> event, by using <see cref="IFontHandle.Push"/> in a scope.<br />
|
||||
/// You may dispose your font handle anytime, as long as it's not in use in <see cref="Draw"/>.
|
||||
/// Font handles may be constructed anytime, as long as the owner <see cref="IFontAtlas"/> or
|
||||
/// <see cref="UiBuilder"/> is not disposed.<br />
|
||||
/// <br />
|
||||
/// If you were storing <see cref="ImFontPtr"/>, consider if the job can be achieved solely by using
|
||||
/// <see cref="IFontHandle"/> without directly using an instance of <see cref="ImFontPtr"/>.<br />
|
||||
/// If you do need it, evaluate if you need to access fonts outside the main thread.<br />
|
||||
/// If it is the case, use <see cref="IFontHandle.Lock"/> to obtain a safe-to-access instance of
|
||||
/// <see cref="ImFontPtr"/>, once <see cref="IFontHandle.WaitAsync"/> resolves.<br />
|
||||
/// Otherwise, use <see cref="IFontHandle.Push"/>, and obtain the instance of <see cref="ImFontPtr"/> via
|
||||
/// <see cref="ImGui.GetFont"/>. Do not let the <see cref="ImFontPtr"/> escape the <c>using</c> scope.<br />
|
||||
/// <br />
|
||||
/// If your plugin sets <see cref="PluginManifest.LoadRequiredState"/> to a non-default value, then
|
||||
/// <see cref="DefaultFontHandle"/> should be accessed using
|
||||
/// <see cref="RunWhenUiPrepared{T}(System.Func{T},bool)"/>, as the font handle member variables are only available
|
||||
/// once drawing facilities are available.<br />
|
||||
/// <br />
|
||||
/// <b>Examples:</b><br />
|
||||
/// * <see cref="InterfaceManager.ContinueConstruction"/>.<br />
|
||||
/// * <see cref="Interface.Internal.Windows.Data.Widgets.GamePrebakedFontsTestWidget"/>.<br />
|
||||
/// * <see cref="Interface.Internal.Windows.TitleScreenMenuWindow"/> ctor.<br />
|
||||
/// * <see cref="Interface.Internal.Windows.Settings.Tabs.SettingsTabAbout"/>:
|
||||
/// note how the construction of a new instance of <see cref="IFontAtlas"/> and
|
||||
/// call of <see cref="IFontAtlas.NewGameFontHandle"/> are done in different functions,
|
||||
/// without having to manually initiate font rebuild process.
|
||||
/// </remarks>
|
||||
[Obsolete("See remarks.", false)]
|
||||
[Api10ToDo(Api10ToDoAttribute.DeleteCompatBehavior)]
|
||||
public event Action? BuildFonts;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets an action that is called any time right after ImGui fonts are rebuilt.<br/>
|
||||
/// Any ImFontPtr objects that you store <strong>can be invalidated</strong> when fonts are rebuilt
|
||||
/// Any ImFontPtr objects that you store <b>can be invalidated</b> when fonts are rebuilt
|
||||
/// (at any time), so you should both reload your custom fonts and restore those
|
||||
/// pointers inside this handler.
|
||||
/// </summary>
|
||||
[Obsolete($"Use {nameof(this.FontAtlas)} instead.", false)]
|
||||
[Obsolete($"See remarks for {nameof(BuildFonts)}.", false)]
|
||||
[Api10ToDo(Api10ToDoAttribute.DeleteCompatBehavior)]
|
||||
public event Action? AfterBuildFonts;
|
||||
|
||||
|
|
@ -143,6 +184,23 @@ public sealed class UiBuilder : IDisposable
|
|||
/// Gets the default Dalamud font - supporting all game languages and icons.<br />
|
||||
/// <strong>Accessing this static property outside of <see cref="Draw"/> is dangerous and not supported.</strong>
|
||||
/// </summary>
|
||||
public static ImFontPtr DefaultFont => InterfaceManager.DefaultFont;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the default Dalamud icon font based on FontAwesome 5 Free solid.<br />
|
||||
/// <strong>Accessing this static property outside of <see cref="Draw"/> is dangerous and not supported.</strong>
|
||||
/// </summary>
|
||||
public static ImFontPtr IconFont => InterfaceManager.IconFont;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the default Dalamud monospaced font based on Inconsolata Regular.<br />
|
||||
/// <strong>Accessing this static property outside of <see cref="Draw"/> is dangerous and not supported.</strong>
|
||||
/// </summary>
|
||||
public static ImFontPtr MonoFont => InterfaceManager.MonoFont;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the handle to the default Dalamud font - supporting all game languages and icons.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A font handle corresponding to this font can be obtained with:
|
||||
/// <code>
|
||||
|
|
@ -151,11 +209,15 @@ public sealed class UiBuilder : IDisposable
|
|||
/// tk => tk.AddDalamudDefaultFont(UiBuilder.DefaultFontSizePt)));
|
||||
/// </code>
|
||||
/// </remarks>
|
||||
public static ImFontPtr DefaultFont => InterfaceManager.DefaultFont;
|
||||
public IFontHandle DefaultFontHandle =>
|
||||
this.defaultFontHandle ??=
|
||||
this.scopedFinalizer.Add(
|
||||
new FontHandleWrapper(
|
||||
this.InterfaceManagerWithScene?.DefaultFontHandle
|
||||
?? throw new InvalidOperationException("Scene is not yet ready.")));
|
||||
|
||||
/// <summary>
|
||||
/// Gets the default Dalamud icon font based on FontAwesome 5 Free solid.<br />
|
||||
/// <strong>Accessing this static property outside of <see cref="Draw"/> is dangerous and not supported.</strong>
|
||||
/// Gets the default Dalamud icon font based on FontAwesome 5 Free solid.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A font handle corresponding to this font can be obtained with:
|
||||
|
|
@ -165,11 +227,15 @@ public sealed class UiBuilder : IDisposable
|
|||
/// tk => tk.AddFontAwesomeIconFont(new() { SizePt = UiBuilder.DefaultFontSizePt })));
|
||||
/// </code>
|
||||
/// </remarks>
|
||||
public static ImFontPtr IconFont => InterfaceManager.IconFont;
|
||||
public IFontHandle IconFontHandle =>
|
||||
this.iconFontHandle ??=
|
||||
this.scopedFinalizer.Add(
|
||||
new FontHandleWrapper(
|
||||
this.InterfaceManagerWithScene?.IconFontHandle
|
||||
?? throw new InvalidOperationException("Scene is not yet ready.")));
|
||||
|
||||
/// <summary>
|
||||
/// Gets the default Dalamud monospaced font based on Inconsolata Regular.<br />
|
||||
/// <strong>Accessing this static property outside of <see cref="Draw"/> is dangerous and not supported.</strong>
|
||||
/// Gets the default Dalamud monospaced font based on Inconsolata Regular.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A font handle corresponding to this font can be obtained with:
|
||||
|
|
@ -181,7 +247,12 @@ public sealed class UiBuilder : IDisposable
|
|||
/// new() { SizePt = UiBuilder.DefaultFontSizePt })));
|
||||
/// </code>
|
||||
/// </remarks>
|
||||
public static ImFontPtr MonoFont => InterfaceManager.MonoFont;
|
||||
public IFontHandle MonoFontHandle =>
|
||||
this.monoFontHandle ??=
|
||||
this.scopedFinalizer.Add(
|
||||
new FontHandleWrapper(
|
||||
this.InterfaceManagerWithScene?.MonoFontHandle
|
||||
?? throw new InvalidOperationException("Scene is not yet ready.")));
|
||||
|
||||
/// <summary>
|
||||
/// Gets the game's active Direct3D device.
|
||||
|
|
@ -660,4 +731,48 @@ public sealed class UiBuilder : IDisposable
|
|||
{
|
||||
this.ResizeBuffers?.InvokeSafely();
|
||||
}
|
||||
|
||||
private class FontHandleWrapper : IFontHandle
|
||||
{
|
||||
private IFontHandle? wrapped;
|
||||
|
||||
public FontHandleWrapper(IFontHandle wrapped)
|
||||
{
|
||||
this.wrapped = wrapped;
|
||||
this.wrapped.ImFontChanged += this.WrappedOnImFontChanged;
|
||||
}
|
||||
|
||||
public event Action<IFontHandle>? ImFontChanged;
|
||||
|
||||
public Exception? LoadException =>
|
||||
this.wrapped!.LoadException ?? new ObjectDisposedException(nameof(FontHandleWrapper));
|
||||
|
||||
public bool Available => this.wrapped?.Available ?? false;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (this.wrapped is not { } w)
|
||||
return;
|
||||
|
||||
this.wrapped = null;
|
||||
w.ImFontChanged -= this.WrappedOnImFontChanged;
|
||||
// Note: do not dispose w; we do not own it
|
||||
}
|
||||
|
||||
public IFontHandle.ImFontLocked Lock() =>
|
||||
this.wrapped?.Lock() ?? throw new ObjectDisposedException(nameof(FontHandleWrapper));
|
||||
|
||||
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));
|
||||
|
||||
public override string ToString() => $"{nameof(FontHandleWrapper)}({this.wrapped})";
|
||||
|
||||
private void WrappedOnImFontChanged(IFontHandle obj) => this.ImFontChanged.InvokeSafely(this);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,6 +69,14 @@ internal sealed class DalamudAssetManager : IServiceType, IDisposable, IDalamudA
|
|||
.Select(x => x.ToContentDisposedTask()))
|
||||
.ContinueWith(_ => loadTimings.Dispose()),
|
||||
"Prevent Dalamud from loading more stuff, until we've ensured that all required assets are available.");
|
||||
|
||||
Task.WhenAll(
|
||||
Enum.GetValues<DalamudAsset>()
|
||||
.Where(x => x is not DalamudAsset.Empty4X4)
|
||||
.Where(x => x.GetAttribute<DalamudAssetAttribute>()?.Required is false)
|
||||
.Select(this.CreateStreamAsync)
|
||||
.Select(x => x.ToContentDisposedTask()))
|
||||
.ContinueWith(r => Log.Verbose($"Optional assets load state: {r}"));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
|
|
|||
77
Dalamud/Utility/IRefCountable.cs
Normal file
77
Dalamud/Utility/IRefCountable.cs
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
|
||||
namespace Dalamud.Utility;
|
||||
|
||||
/// <summary>
|
||||
/// Interface for reference counting.
|
||||
/// </summary>
|
||||
internal interface IRefCountable : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Result for <see cref="IRefCountable.AlterRefCount"/>.
|
||||
/// </summary>
|
||||
public enum RefCountResult
|
||||
{
|
||||
/// <summary>
|
||||
/// The object still has remaining references. No futher action should be done.
|
||||
/// </summary>
|
||||
StillAlive = 1,
|
||||
|
||||
/// <summary>
|
||||
/// The last reference to the object has been released. The object should be fully released.
|
||||
/// </summary>
|
||||
FinalRelease = 2,
|
||||
|
||||
/// <summary>
|
||||
/// The object already has been disposed. <see cref="ObjectDisposedException"/> may be thrown.
|
||||
/// </summary>
|
||||
AlreadyDisposed = 3,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a reference to this reference counted object.
|
||||
/// </summary>
|
||||
/// <returns>The new number of references.</returns>
|
||||
int AddRef();
|
||||
|
||||
/// <summary>
|
||||
/// Releases a reference from this reference counted object.<br />
|
||||
/// When all references are released, the object will be fully disposed.
|
||||
/// </summary>
|
||||
/// <returns>The new number of references.</returns>
|
||||
int Release();
|
||||
|
||||
/// <summary>
|
||||
/// Alias for <see cref="Release()"/>.
|
||||
/// </summary>
|
||||
void IDisposable.Dispose() => this.Release();
|
||||
|
||||
/// <summary>
|
||||
/// Alters <paramref name="refCount"/> by <paramref name="delta"/>.
|
||||
/// </summary>
|
||||
/// <param name="delta">The delta to the reference count.</param>
|
||||
/// <param name="refCount">The reference to the reference count.</param>
|
||||
/// <param name="newRefCount">The new reference count.</param>
|
||||
/// <returns>The followup action that should be done.</returns>
|
||||
public static RefCountResult AlterRefCount(int delta, ref int refCount, out int newRefCount)
|
||||
{
|
||||
Debug.Assert(delta is 1 or -1, "delta must be 1 or -1");
|
||||
|
||||
while (true)
|
||||
{
|
||||
var refCountCopy = refCount;
|
||||
if (refCountCopy <= 0)
|
||||
{
|
||||
newRefCount = refCountCopy;
|
||||
return RefCountResult.AlreadyDisposed;
|
||||
}
|
||||
|
||||
newRefCount = refCountCopy + delta;
|
||||
if (refCountCopy != Interlocked.CompareExchange(ref refCount, newRefCount, refCountCopy))
|
||||
continue;
|
||||
|
||||
return newRefCount == 0 ? RefCountResult.FinalRelease : RefCountResult.StillAlive;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue