mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-13 12:14:16 +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.Draw += this.OnDraw;
|
||||||
this.Interface.UiBuilder.OpenConfigUi += this.OnOpenConfigUi;
|
this.Interface.UiBuilder.OpenConfigUi += this.OnOpenConfigUi;
|
||||||
this.Interface.UiBuilder.OpenMainUi += this.OnOpenMainUi;
|
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." });
|
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="JetBrains.Annotations" Version="2021.2.0" />
|
||||||
<PackageReference Include="Lumina" Version="3.15.2" />
|
<PackageReference Include="Lumina" Version="3.15.2" />
|
||||||
<PackageReference Include="Lumina.Excel" Version="6.5.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">
|
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.46-beta">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
using Dalamud.Interface.ManagedFontAtlas;
|
using Dalamud.Interface.ManagedFontAtlas;
|
||||||
using Dalamud.Interface.ManagedFontAtlas.Internals;
|
using Dalamud.Interface.ManagedFontAtlas.Internals;
|
||||||
|
|
@ -28,6 +29,13 @@ public sealed class GameFontHandle : IFontHandle
|
||||||
this.fontAtlasFactory = fontAtlasFactory;
|
this.fontAtlasFactory = fontAtlasFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public event Action<IFontHandle> ImFontChanged
|
||||||
|
{
|
||||||
|
add => this.fontHandle.ImFontChanged += value;
|
||||||
|
remove => this.fontHandle.ImFontChanged -= value;
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public Exception? LoadException => this.fontHandle.LoadException;
|
public Exception? LoadException => this.fontHandle.LoadException;
|
||||||
|
|
||||||
|
|
@ -55,14 +63,20 @@ public sealed class GameFontHandle : IFontHandle
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void Dispose() => this.fontHandle.Dispose();
|
public void Dispose() => this.fontHandle.Dispose();
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IFontHandle.ImFontLocked Lock() => this.fontHandle.Lock();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Pushes the font.
|
/// Pushes the font.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>An <see cref="IDisposable"/> that can be used to pop the font on dispose.</returns>
|
/// <returns>An <see cref="IDisposable"/> that can be used to pop the font on dispose.</returns>
|
||||||
public IDisposable Push() => this.fontHandle.Push();
|
public IDisposable Push() => this.fontHandle.Push();
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc />
|
||||||
IFontHandle.FontPopper IFontHandle.Push() => this.fontHandle.Push();
|
public void Pop() => this.fontHandle.Pop();
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Task<IFontHandle> WaitAsync() => this.fontHandle.WaitAsync();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new <see cref="GameFontLayoutPlan.Builder"/>.<br />
|
/// Creates a new <see cref="GameFontLayoutPlan.Builder"/>.<br />
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@ using Dalamud.Game.ClientState.Keys;
|
||||||
using Dalamud.Game.Internal.DXGI;
|
using Dalamud.Game.Internal.DXGI;
|
||||||
using Dalamud.Hooking;
|
using Dalamud.Hooking;
|
||||||
using Dalamud.Hooking.WndProcHook;
|
using Dalamud.Hooking.WndProcHook;
|
||||||
using Dalamud.Interface.GameFonts;
|
|
||||||
using Dalamud.Interface.Internal.ManagedAsserts;
|
using Dalamud.Interface.Internal.ManagedAsserts;
|
||||||
using Dalamud.Interface.Internal.Notifications;
|
using Dalamud.Interface.Internal.Notifications;
|
||||||
using Dalamud.Interface.ManagedFontAtlas;
|
using Dalamud.Interface.ManagedFontAtlas;
|
||||||
|
|
@ -87,9 +86,6 @@ internal class InterfaceManager : IDisposable, IServiceType
|
||||||
private Hook<ResizeBuffersDelegate>? resizeBuffersHook;
|
private Hook<ResizeBuffersDelegate>? resizeBuffersHook;
|
||||||
|
|
||||||
private IFontAtlas? dalamudAtlas;
|
private IFontAtlas? dalamudAtlas;
|
||||||
private IFontHandle.IInternal? defaultFontHandle;
|
|
||||||
private IFontHandle.IInternal? iconFontHandle;
|
|
||||||
private IFontHandle.IInternal? monoFontHandle;
|
|
||||||
|
|
||||||
// can't access imgui IO before first present call
|
// can't access imgui IO before first present call
|
||||||
private bool lastWantCapture = false;
|
private bool lastWantCapture = false;
|
||||||
|
|
@ -131,19 +127,34 @@ internal class InterfaceManager : IDisposable, IServiceType
|
||||||
/// Gets the default ImGui font.<br />
|
/// Gets the default ImGui font.<br />
|
||||||
/// <strong>Accessing this static property outside of the main thread is dangerous and not supported.</strong>
|
/// <strong>Accessing this static property outside of the main thread is dangerous and not supported.</strong>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static ImFontPtr DefaultFont => WhenFontsReady().defaultFontHandle!.ImFont.OrElse(ImGui.GetIO().FontDefault);
|
public static ImFontPtr DefaultFont => WhenFontsReady().DefaultFontHandle!.ImFont.OrElse(ImGui.GetIO().FontDefault);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets an included FontAwesome icon font.<br />
|
/// Gets an included FontAwesome icon font.<br />
|
||||||
/// <strong>Accessing this static property outside of the main thread is dangerous and not supported.</strong>
|
/// <strong>Accessing this static property outside of the main thread is dangerous and not supported.</strong>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static ImFontPtr IconFont => WhenFontsReady().iconFontHandle!.ImFont.OrElse(ImGui.GetIO().FontDefault);
|
public static ImFontPtr IconFont => WhenFontsReady().IconFontHandle!.ImFont.OrElse(ImGui.GetIO().FontDefault);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets an included monospaced font.<br />
|
/// Gets an included monospaced font.<br />
|
||||||
/// <strong>Accessing this static property outside of the main thread is dangerous and not supported.</strong>
|
/// <strong>Accessing this static property outside of the main thread is dangerous and not supported.</strong>
|
||||||
/// </summary>
|
/// </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>
|
/// <summary>
|
||||||
/// Gets or sets the pointer to ImGui.IO(), when it was last used.
|
/// Gets or sets the pointer to ImGui.IO(), when it was last used.
|
||||||
|
|
@ -219,6 +230,11 @@ internal class InterfaceManager : IDisposable, IServiceType
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Task FontBuildTask => WhenFontsReady().dalamudAtlas!.BuildTask;
|
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>
|
/// <summary>
|
||||||
/// Dispose of managed and unmanaged resources.
|
/// Dispose of managed and unmanaged resources.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -636,6 +652,8 @@ internal class InterfaceManager : IDisposable, IServiceType
|
||||||
*/
|
*/
|
||||||
private IntPtr PresentDetour(IntPtr swapChain, uint syncInterval, uint presentFlags)
|
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.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");
|
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);
|
.CreateFontAtlas(nameof(InterfaceManager), FontAtlasAutoRebuildMode.Disable);
|
||||||
using (this.dalamudAtlas.SuppressAutoRebuild())
|
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)));
|
e => e.OnPreBuild(tk => tk.AddDalamudDefaultFont(DefaultFontSizePx)));
|
||||||
this.iconFontHandle = (IFontHandle.IInternal)this.dalamudAtlas.NewDelegateFontHandle(
|
this.IconFontHandle = (IFontHandle.IInternal)this.dalamudAtlas.NewDelegateFontHandle(
|
||||||
e => e.OnPreBuild(
|
e => e.OnPreBuild(
|
||||||
tk => tk.AddFontAwesomeIconFont(
|
tk => tk.AddFontAwesomeIconFont(
|
||||||
new()
|
new()
|
||||||
|
|
@ -702,7 +720,7 @@ internal class InterfaceManager : IDisposable, IServiceType
|
||||||
GlyphMinAdvanceX = DefaultFontSizePx,
|
GlyphMinAdvanceX = DefaultFontSizePx,
|
||||||
GlyphMaxAdvanceX = DefaultFontSizePx,
|
GlyphMaxAdvanceX = DefaultFontSizePx,
|
||||||
})));
|
})));
|
||||||
this.monoFontHandle = (IFontHandle.IInternal)this.dalamudAtlas.NewDelegateFontHandle(
|
this.MonoFontHandle = (IFontHandle.IInternal)this.dalamudAtlas.NewDelegateFontHandle(
|
||||||
e => e.OnPreBuild(
|
e => e.OnPreBuild(
|
||||||
tk => tk.AddDalamudAssetFont(
|
tk => tk.AddDalamudAssetFont(
|
||||||
DalamudAsset.InconsolataRegular,
|
DalamudAsset.InconsolataRegular,
|
||||||
|
|
@ -714,13 +732,16 @@ internal class InterfaceManager : IDisposable, IServiceType
|
||||||
// Do not use DefaultFont, IconFont, and MonoFont.
|
// Do not use DefaultFont, IconFont, and MonoFont.
|
||||||
// Use font handles directly.
|
// Use font handles directly.
|
||||||
|
|
||||||
|
using var defaultFont = this.DefaultFontHandle.Lock();
|
||||||
|
using var monoFont = this.MonoFontHandle.Lock();
|
||||||
|
|
||||||
// Fill missing glyphs in MonoFont from DefaultFont
|
// Fill missing glyphs in MonoFont from DefaultFont
|
||||||
tk.CopyGlyphsAcrossFonts(this.defaultFontHandle.ImFont, this.monoFontHandle.ImFont, true);
|
tk.CopyGlyphsAcrossFonts(defaultFont, monoFont, true);
|
||||||
|
|
||||||
// Update default font
|
// Update default font
|
||||||
unsafe
|
unsafe
|
||||||
{
|
{
|
||||||
ImGui.GetIO().NativePtr->FontDefault = this.defaultFontHandle.ImFont;
|
ImGui.GetIO().NativePtr->FontDefault = defaultFont;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Broadcast to auto-rebuilding instances
|
// Broadcast to auto-rebuilding instances
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
using Dalamud.Interface.GameFonts;
|
using Dalamud.Interface.GameFonts;
|
||||||
using Dalamud.Interface.ManagedFontAtlas;
|
using Dalamud.Interface.ManagedFontAtlas;
|
||||||
|
|
@ -11,6 +13,8 @@ using Dalamud.Utility;
|
||||||
|
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
|
|
||||||
|
using Serilog;
|
||||||
|
|
||||||
namespace Dalamud.Interface.Internal.Windows.Data.Widgets;
|
namespace Dalamud.Interface.Internal.Windows.Data.Widgets;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -103,6 +107,10 @@ internal class GamePrebakedFontsTestWidget : IDataWindowWidget, IDisposable
|
||||||
minCapacity: 1024);
|
minCapacity: 1024);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
if (ImGui.Button("Test Lock"))
|
||||||
|
Task.Run(this.TestLock);
|
||||||
|
|
||||||
fixed (byte* labelPtr = "Test Input"u8)
|
fixed (byte* labelPtr = "Test Input"u8)
|
||||||
{
|
{
|
||||||
if (ImGuiNative.igInputTextMultiline(
|
if (ImGuiNative.igInputTextMultiline(
|
||||||
|
|
@ -155,6 +163,7 @@ internal class GamePrebakedFontsTestWidget : IDataWindowWidget, IDisposable
|
||||||
.ToArray());
|
.ToArray());
|
||||||
|
|
||||||
var offsetX = ImGui.CalcTextSize("99.9pt").X + (ImGui.GetStyle().FramePadding.X * 2);
|
var offsetX = ImGui.CalcTextSize("99.9pt").X + (ImGui.GetStyle().FramePadding.X * 2);
|
||||||
|
var counter = 0;
|
||||||
foreach (var (family, items) in this.fontHandles)
|
foreach (var (family, items) in this.fontHandles)
|
||||||
{
|
{
|
||||||
if (!ImGui.CollapsingHeader($"{family} Family"))
|
if (!ImGui.CollapsingHeader($"{family} Family"))
|
||||||
|
|
@ -180,10 +189,21 @@ internal class GamePrebakedFontsTestWidget : IDataWindowWidget, IDisposable
|
||||||
{
|
{
|
||||||
if (!this.useGlobalScale)
|
if (!this.useGlobalScale)
|
||||||
ImGuiNative.igSetWindowFontScale(1 / ImGuiHelpers.GlobalScale);
|
ImGuiNative.igSetWindowFontScale(1 / ImGuiHelpers.GlobalScale);
|
||||||
using var pushPop = handle.Value.Push();
|
if (counter++ % 2 == 0)
|
||||||
ImGuiNative.igTextUnformatted(
|
{
|
||||||
this.testStringBuffer.Data,
|
using var pushPop = handle.Value.Push();
|
||||||
this.testStringBuffer.Data + this.testStringBuffer.Length);
|
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
|
finally
|
||||||
|
|
@ -210,4 +230,49 @@ internal class GamePrebakedFontsTestWidget : IDataWindowWidget, IDisposable
|
||||||
this.privateAtlas?.Dispose();
|
this.privateAtlas?.Dispose();
|
||||||
this.privateAtlas = null;
|
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 fontAtlasFactory = Service<FontAtlasFactory>.Get();
|
||||||
|
|
||||||
var rebuildFont = fontAtlasFactory.UseAxis != configuration.UseAxisFontsFromGame;
|
var rebuildFont = fontAtlasFactory.UseAxis != configuration.UseAxisFontsFromGame;
|
||||||
|
rebuildFont |= !Equals(ImGui.GetIO().FontGlobalScale, configuration.GlobalUiScale);
|
||||||
|
|
||||||
ImGui.GetIO().FontGlobalScale = configuration.GlobalUiScale;
|
ImGui.GetIO().FontGlobalScale = configuration.GlobalUiScale;
|
||||||
fontAtlasFactory.UseAxisOverride = null;
|
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.
|
/// Note that <see cref="BuildTask"/> would not necessarily get changed from calling this function.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <exception cref="InvalidOperationException">If <see cref="AutoRebuildMode"/> is <see cref="FontAtlasAutoRebuildMode.Async"/>.</exception>
|
/// <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();
|
void BuildFontsOnNextFrame();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -54,11 +54,11 @@ public interface IFontAtlasBuildToolkitPreBuild : IFontAtlasBuildToolkit
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds a font from memory region allocated using <see cref="ImGuiHelpers.AllocateMemory"/>.<br />
|
/// 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 />
|
/// <b>It WILL crash if you try to use a memory pointer allocated in some other way.</b><br />
|
||||||
/// <strong>
|
/// <b>
|
||||||
/// Do NOT call <see cref="ImGuiNative.igMemFree"/> on the <paramref name="dataPointer"/> once this function has
|
/// 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.
|
/// been called, unless <paramref name="freeOnException"/> is set and the function has thrown an error.
|
||||||
/// </strong>
|
/// </b>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="dataPointer">Memory address for the data allocated using <see cref="ImGuiHelpers.AllocateMemory"/>.</param>
|
/// <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>
|
/// <param name="dataSize">The size of the font file..</param>
|
||||||
|
|
@ -81,9 +81,11 @@ public interface IFontAtlasBuildToolkitPreBuild : IFontAtlasBuildToolkit
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds a font from memory region allocated using <see cref="ImGuiHelpers.AllocateMemory"/>.<br />
|
/// 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 />
|
/// <b>It WILL crash if you try to use a memory pointer allocated in some other way.</b><br />
|
||||||
/// <strong>Do NOT call <see cref="ImGuiNative.igMemFree"/> on the <paramref name="dataPointer"/> once this
|
/// <b>
|
||||||
/// function has been called.</strong>
|
/// 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>
|
/// </summary>
|
||||||
/// <param name="dataPointer">Memory address for the data allocated using <see cref="ImGuiHelpers.AllocateMemory"/>.</param>
|
/// <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>
|
/// <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;
|
using ImGuiNET;
|
||||||
|
|
||||||
|
|
@ -9,6 +11,16 @@ namespace Dalamud.Interface.ManagedFontAtlas;
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IFontHandle : IDisposable
|
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>
|
/// <summary>
|
||||||
/// Represents a reference counting handle for fonts. Dalamud internal use only.
|
/// Represents a reference counting handle for fonts. Dalamud internal use only.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -18,7 +30,8 @@ public interface IFontHandle : IDisposable
|
||||||
/// Gets the font.<br />
|
/// Gets the font.<br />
|
||||||
/// Use of this properly is safe only from the UI thread.<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 />
|
/// 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>
|
/// </summary>
|
||||||
ImFontPtr ImFont { get; }
|
ImFontPtr ImFont { get; }
|
||||||
}
|
}
|
||||||
|
|
@ -29,54 +42,92 @@ public interface IFontHandle : IDisposable
|
||||||
Exception? LoadException { get; }
|
Exception? LoadException { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether this font is ready for use.<br />
|
/// Gets a value indicating whether this font is ready for use.
|
||||||
/// Use <see cref="Push"/> directly if you want to keep the current ImGui font if the font is not ready.
|
|
||||||
/// </summary>
|
/// </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; }
|
bool Available { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <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 />
|
/// Use <see cref="ImGui.GetFont"/> to access the current font.<br />
|
||||||
/// You may not access the font once you dispose this object.
|
/// You may not access the font once you dispose this object.
|
||||||
/// </summary>
|
/// </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>
|
/// <exception cref="InvalidOperationException">If called outside of the main thread.</exception>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// Only intended for use with <c>using</c> keywords, such as <c>using (handle.Push())</c>.<br />
|
/// This function uses <see cref="ImGui.PushFont"/>, and may do extra things.
|
||||||
/// Should you store or transfer the return value to somewhere else, use <see cref="IDisposable"/> as the type.
|
/// Use <see cref="IDisposable.Dispose"/> or <see cref="Pop"/> to undo this operation.
|
||||||
|
/// Do not use <see cref="ImGui.PopFont"/>.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
FontPopper Push();
|
IDisposable Push();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The wrapper for popping fonts.
|
/// Pops the font pushed to ImGui using <see cref="Push"/>, cleaning up any extra information as needed.
|
||||||
/// </summary>
|
/// </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>
|
/// <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>
|
/// </summary>
|
||||||
/// <param name="fontPtr">The font to push.</param>
|
/// <param name="imFont">The contained font.</param>
|
||||||
/// <param name="push">Whether to push.</param>
|
/// <param name="owner">The owner.</param>
|
||||||
internal FontPopper(ImFontPtr fontPtr, bool push)
|
internal ImFontLocked(ImFontPtr imFont, IRefCountable owner)
|
||||||
{
|
{
|
||||||
if (!push)
|
owner.AddRef();
|
||||||
return;
|
this.ImFont = imFont;
|
||||||
|
this.owner = owner;
|
||||||
ThreadSafety.AssertMainThread();
|
|
||||||
|
|
||||||
this.count = 1;
|
|
||||||
ImGui.PushFont(fontPtr);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <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()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
ThreadSafety.AssertMainThread();
|
if (this.owner is null)
|
||||||
|
return;
|
||||||
|
|
||||||
while (this.count-- > 0)
|
this.owner.Release();
|
||||||
ImGui.PopFont();
|
this.owner = null;
|
||||||
|
this.ImFont = default;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,16 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
using Dalamud.Interface.Internal;
|
||||||
using Dalamud.Interface.Utility;
|
using Dalamud.Interface.Utility;
|
||||||
using Dalamud.Logging.Internal;
|
using Dalamud.Logging.Internal;
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
|
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
|
|
||||||
|
using Serilog;
|
||||||
|
|
||||||
namespace Dalamud.Interface.ManagedFontAtlas.Internals;
|
namespace Dalamud.Interface.ManagedFontAtlas.Internals;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -14,7 +18,10 @@ namespace Dalamud.Interface.ManagedFontAtlas.Internals;
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal class DelegateFontHandle : IFontHandle.IInternal
|
internal class DelegateFontHandle : IFontHandle.IInternal
|
||||||
{
|
{
|
||||||
|
private readonly List<IDisposable> pushedFonts = new(8);
|
||||||
|
|
||||||
private IFontHandleManager? manager;
|
private IFontHandleManager? manager;
|
||||||
|
private long lastCumulativePresentCalls;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="DelegateFontHandle"/> class.
|
/// Initializes a new instance of the <see cref="DelegateFontHandle"/> class.
|
||||||
|
|
@ -27,6 +34,11 @@ internal class DelegateFontHandle : IFontHandle.IInternal
|
||||||
this.CallOnBuildStepChange = callOnBuildStepChange;
|
this.CallOnBuildStepChange = callOnBuildStepChange;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public event Action<IFontHandle>? ImFontChanged;
|
||||||
|
|
||||||
|
private event Action<IFontHandle>? Disposed;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the function to be called on build step changes.
|
/// Gets the function to be called on build step changes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -47,12 +59,105 @@ internal class DelegateFontHandle : IFontHandle.IInternal
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Dispose()
|
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?.FreeFontHandle(this);
|
||||||
this.manager = null;
|
this.manager = null;
|
||||||
|
this.Disposed?.InvokeSafely(this);
|
||||||
|
this.ImFontChanged = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <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>
|
/// <summary>
|
||||||
/// Manager for <see cref="DelegateFontHandle"/>s.
|
/// Manager for <see cref="DelegateFontHandle"/>s.
|
||||||
|
|
@ -81,11 +186,7 @@ internal class DelegateFontHandle : IFontHandle.IInternal
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
lock (this.syncRoot)
|
lock (this.syncRoot)
|
||||||
{
|
|
||||||
this.handles.Clear();
|
this.handles.Clear();
|
||||||
this.Substance?.Dispose();
|
|
||||||
this.Substance = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc cref="IFontAtlas.NewDelegateFontHandle"/>
|
/// <inheritdoc cref="IFontAtlas.NewDelegateFontHandle"/>
|
||||||
|
|
@ -109,10 +210,20 @@ internal class DelegateFontHandle : IFontHandle.IInternal
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <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)
|
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)}");
|
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.
|
// Owned by this class, but ImFontPtr values still do not belong to this.
|
||||||
private readonly Dictionary<DelegateFontHandle, ImFontPtr> fonts = new();
|
private readonly Dictionary<DelegateFontHandle, ImFontPtr> fonts = new();
|
||||||
private readonly Dictionary<DelegateFontHandle, Exception?> buildExceptions = 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.
|
/// Initializes a new instance of the <see cref="HandleSubstance"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="manager">The manager.</param>
|
/// <param name="manager">The manager.</param>
|
||||||
|
/// <param name="dataRoot">The data root.</param>
|
||||||
/// <param name="relevantHandles">The relevant handles.</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.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/>
|
/// <inheritdoc/>
|
||||||
public IFontHandleManager Manager { get; }
|
public IFontHandleManager Manager { get; }
|
||||||
|
|
||||||
|
|
@ -171,7 +295,7 @@ internal class DelegateFontHandle : IFontHandle.IInternal
|
||||||
public void OnPreBuild(IFontAtlasBuildToolkitPreBuild toolkitPreBuild)
|
public void OnPreBuild(IFontAtlasBuildToolkitPreBuild toolkitPreBuild)
|
||||||
{
|
{
|
||||||
var fontsVector = toolkitPreBuild.Fonts;
|
var fontsVector = toolkitPreBuild.Fonts;
|
||||||
foreach (var k in this.relevantHandles)
|
foreach (var k in this.RelevantHandles)
|
||||||
{
|
{
|
||||||
var fontCountPrevious = fontsVector.Length;
|
var fontCountPrevious = fontsVector.Length;
|
||||||
|
|
||||||
|
|
@ -288,7 +412,7 @@ internal class DelegateFontHandle : IFontHandle.IInternal
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void OnPostBuild(IFontAtlasBuildToolkitPostBuild toolkitPostBuild)
|
public void OnPostBuild(IFontAtlasBuildToolkitPostBuild toolkitPostBuild)
|
||||||
{
|
{
|
||||||
foreach (var k in this.relevantHandles)
|
foreach (var k in this.RelevantHandles)
|
||||||
{
|
{
|
||||||
if (!this.fonts[k].IsNotNullAndLoaded())
|
if (!this.fonts[k].IsNotNullAndLoaded())
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -315,7 +439,7 @@ internal class DelegateFontHandle : IFontHandle.IInternal
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void OnPostPromotion(IFontAtlasBuildToolkitPostPromotion toolkitPostPromotion)
|
public void OnPostPromotion(IFontAtlasBuildToolkitPostPromotion toolkitPostPromotion)
|
||||||
{
|
{
|
||||||
foreach (var k in this.relevantHandles)
|
foreach (var k in this.RelevantHandles)
|
||||||
{
|
{
|
||||||
if (!this.fonts[k].IsNotNullAndLoaded())
|
if (!this.fonts[k].IsNotNullAndLoaded())
|
||||||
continue;
|
continue;
|
||||||
|
|
|
||||||
|
|
@ -43,68 +43,67 @@ internal sealed partial class FontAtlasFactory
|
||||||
|
|
||||||
private static readonly Task<FontAtlasBuiltData> EmptyTask = Task.FromResult(default(FontAtlasBuiltData));
|
private static readonly Task<FontAtlasBuiltData> EmptyTask = Task.FromResult(default(FontAtlasBuiltData));
|
||||||
|
|
||||||
private struct FontAtlasBuiltData : IDisposable
|
private class FontAtlasBuiltData : IRefCountable
|
||||||
{
|
{
|
||||||
public readonly DalamudFontAtlas? Owner;
|
private readonly List<IDalamudTextureWrap> wraps;
|
||||||
public readonly ImFontAtlasPtr Atlas;
|
private readonly List<IFontHandleSubstance> substances;
|
||||||
public readonly float Scale;
|
|
||||||
|
|
||||||
public bool IsBuildInProgress;
|
private int refCount;
|
||||||
|
|
||||||
private readonly List<IDalamudTextureWrap>? wraps;
|
public unsafe FontAtlasBuiltData(DalamudFontAtlas owner, float scale)
|
||||||
private readonly List<IFontHandleSubstance>? substances;
|
|
||||||
private readonly DisposeSafety.ScopedFinalizer? garbage;
|
|
||||||
|
|
||||||
public unsafe FontAtlasBuiltData(
|
|
||||||
DalamudFontAtlas owner,
|
|
||||||
IEnumerable<IFontHandleSubstance> substances,
|
|
||||||
float scale)
|
|
||||||
{
|
{
|
||||||
this.Owner = owner;
|
this.Owner = owner;
|
||||||
this.Scale = scale;
|
this.Scale = scale;
|
||||||
this.garbage = new();
|
this.Garbage = new();
|
||||||
|
this.refCount = 1;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var substancesList = this.substances = new();
|
var substancesList = this.substances = new();
|
||||||
foreach (var s in substances)
|
this.Garbage.Add(() => substancesList.Clear());
|
||||||
substancesList.Add(this.garbage.Add(s));
|
|
||||||
this.garbage.Add(() => substancesList.Clear());
|
|
||||||
|
|
||||||
var wrapsCopy = this.wraps = new();
|
var wrapsCopy = this.wraps = new();
|
||||||
this.garbage.Add(() => wrapsCopy.Clear());
|
this.Garbage.Add(() => wrapsCopy.Clear());
|
||||||
|
|
||||||
var atlasPtr = ImGuiNative.ImFontAtlas_ImFontAtlas();
|
var atlasPtr = ImGuiNative.ImFontAtlas_ImFontAtlas();
|
||||||
this.Atlas = atlasPtr;
|
this.Atlas = atlasPtr;
|
||||||
if (this.Atlas.NativePtr is null)
|
if (this.Atlas.NativePtr is null)
|
||||||
throw new OutOfMemoryException($"Failed to allocate a new {nameof(ImFontAtlas)}.");
|
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;
|
this.IsBuildInProgress = true;
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
this.garbage.Dispose();
|
this.Garbage.Dispose();
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly DisposeSafety.ScopedFinalizer Garbage =>
|
public DalamudFontAtlas? Owner { get; }
|
||||||
this.garbage ?? throw new ObjectDisposedException(nameof(FontAtlasBuiltData));
|
|
||||||
|
|
||||||
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 =>
|
public DisposeSafety.ScopedFinalizer Garbage { get; }
|
||||||
(IReadOnlyList<IDalamudTextureWrap>?)this.wraps ?? Array.Empty<IDalamudTextureWrap>();
|
|
||||||
|
|
||||||
public readonly IReadOnlyList<IFontHandleSubstance> Substances =>
|
public ImVectorWrapper<ImFontPtr> Fonts => this.Atlas.FontsWrapped();
|
||||||
(IReadOnlyList<IFontHandleSubstance>?)this.substances ?? Array.Empty<IFontHandleSubstance>();
|
|
||||||
|
|
||||||
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)
|
if (this.wraps is null)
|
||||||
throw new ObjectDisposedException(nameof(FontAtlasBuiltData));
|
throw new ObjectDisposedException(nameof(FontAtlasBuiltData));
|
||||||
|
|
@ -112,7 +111,7 @@ internal sealed partial class FontAtlasFactory
|
||||||
this.wraps.Add(this.Garbage.Add(wrap));
|
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)
|
if (this.wraps is null)
|
||||||
throw new ObjectDisposedException(nameof(FontAtlasBuiltData));
|
throw new ObjectDisposedException(nameof(FontAtlasBuiltData));
|
||||||
|
|
@ -160,27 +159,47 @@ internal sealed partial class FontAtlasFactory
|
||||||
return index;
|
return index;
|
||||||
}
|
}
|
||||||
|
|
||||||
public unsafe void Dispose()
|
public int AddRef() => IRefCountable.AlterRefCount(1, ref this.refCount, out var newRefCount) switch
|
||||||
{
|
{
|
||||||
if (this.garbage is null)
|
IRefCountable.RefCountResult.StillAlive => newRefCount,
|
||||||
return;
|
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(
|
case IRefCountable.RefCountResult.StillAlive:
|
||||||
"[{name}] 0x{ptr:X}: Trying to dispose while build is in progress; waiting for build.\n" +
|
return newRefCount;
|
||||||
"Stack:\n{trace}",
|
|
||||||
this.Owner?.Name ?? "<?>",
|
case IRefCountable.RefCountResult.FinalRelease:
|
||||||
(nint)this.Atlas.NativePtr,
|
if (this.IsBuildInProgress)
|
||||||
new StackTrace());
|
{
|
||||||
while (this.IsBuildInProgress)
|
Log.Error(
|
||||||
Thread.Sleep(100);
|
"[{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
|
#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
|
#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)
|
public BuildToolkit CreateToolkit(FontAtlasFactory factory, bool isAsync)
|
||||||
|
|
@ -201,8 +220,8 @@ internal sealed partial class FontAtlasFactory
|
||||||
private readonly object syncRootPostPromotion = new();
|
private readonly object syncRootPostPromotion = new();
|
||||||
private readonly object syncRoot = new();
|
private readonly object syncRoot = new();
|
||||||
|
|
||||||
private Task<FontAtlasBuiltData> buildTask = EmptyTask;
|
private Task<FontAtlasBuiltData?> buildTask = EmptyTask;
|
||||||
private FontAtlasBuiltData builtData;
|
private FontAtlasBuiltData? builtData;
|
||||||
|
|
||||||
private int buildSuppressionCounter;
|
private int buildSuppressionCounter;
|
||||||
private bool buildSuppressionSuppressed;
|
private bool buildSuppressionSuppressed;
|
||||||
|
|
@ -275,7 +294,8 @@ internal sealed partial class FontAtlasFactory
|
||||||
lock (this.syncRoot)
|
lock (this.syncRoot)
|
||||||
{
|
{
|
||||||
this.buildTask.ToDisposableIgnoreExceptions().Dispose();
|
this.buildTask.ToDisposableIgnoreExceptions().Dispose();
|
||||||
this.builtData.Dispose();
|
this.builtData?.Release();
|
||||||
|
this.builtData = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -303,7 +323,7 @@ internal sealed partial class FontAtlasFactory
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
lock (this.syncRoot)
|
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;
|
public Task BuildTask => this.buildTask;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public bool HasBuiltAtlas => !this.builtData.Atlas.IsNull();
|
public bool HasBuiltAtlas => !(this.builtData?.Atlas.IsNull() ?? true);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public bool IsGlobalScaled { get; }
|
public bool IsGlobalScaled { get; }
|
||||||
|
|
@ -474,13 +494,13 @@ internal sealed partial class FontAtlasFactory
|
||||||
var rebuildIndex = ++this.buildIndex;
|
var rebuildIndex = ++this.buildIndex;
|
||||||
return this.buildTask = this.buildTask.ContinueWith(BuildInner).Unwrap();
|
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));
|
Log.Verbose("[{name}] Building from {source}.", this.Name, nameof(this.BuildFontsAsync));
|
||||||
lock (this.syncRoot)
|
lock (this.syncRoot)
|
||||||
{
|
{
|
||||||
if (this.buildIndex != rebuildIndex)
|
if (this.buildIndex != rebuildIndex)
|
||||||
return default;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var res = await this.RebuildFontsPrivate(true, scale);
|
var res = await this.RebuildFontsPrivate(true, scale);
|
||||||
|
|
@ -512,8 +532,10 @@ internal sealed partial class FontAtlasFactory
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.builtData.ExplicitDisposeIgnoreExceptions();
|
var prevBuiltData = this.builtData;
|
||||||
this.builtData = data;
|
this.builtData = data;
|
||||||
|
prevBuiltData.ExplicitDisposeIgnoreExceptions();
|
||||||
|
|
||||||
this.buildTask = EmptyTask;
|
this.buildTask = EmptyTask;
|
||||||
foreach (var substance in data.Substances)
|
foreach (var substance in data.Substances)
|
||||||
substance.Manager.Substance = substance;
|
substance.Manager.Substance = substance;
|
||||||
|
|
@ -570,6 +592,9 @@ internal sealed partial class FontAtlasFactory
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach (var substance in data.Substances)
|
||||||
|
substance.Manager.InvokeFontHandleImFontChanged();
|
||||||
|
|
||||||
#if VeryVerboseLog
|
#if VeryVerboseLog
|
||||||
Log.Verbose("[{name}] Built from {source}.", this.Name, source);
|
Log.Verbose("[{name}] Built from {source}.", this.Name, source);
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -610,12 +635,14 @@ internal sealed partial class FontAtlasFactory
|
||||||
var sw = new Stopwatch();
|
var sw = new Stopwatch();
|
||||||
sw.Start();
|
sw.Start();
|
||||||
|
|
||||||
var res = default(FontAtlasBuiltData);
|
FontAtlasBuiltData? res = null;
|
||||||
nint atlasPtr = 0;
|
nint atlasPtr = 0;
|
||||||
BuildToolkit? toolkit = null;
|
BuildToolkit? toolkit = null;
|
||||||
try
|
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
|
unsafe
|
||||||
{
|
{
|
||||||
atlasPtr = (nint)res.Atlas.NativePtr;
|
atlasPtr = (nint)res.Atlas.NativePtr;
|
||||||
|
|
@ -646,9 +673,11 @@ internal sealed partial class FontAtlasFactory
|
||||||
|
|
||||||
res.IsBuildInProgress = false;
|
res.IsBuildInProgress = false;
|
||||||
toolkit.Dispose();
|
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
|
unsafe
|
||||||
{
|
{
|
||||||
atlasPtr = (nint)res.Atlas.NativePtr;
|
atlasPtr = (nint)res.Atlas.NativePtr;
|
||||||
|
|
@ -715,8 +744,12 @@ internal sealed partial class FontAtlasFactory
|
||||||
nameof(this.RebuildFontsPrivateReal),
|
nameof(this.RebuildFontsPrivateReal),
|
||||||
atlasPtr,
|
atlasPtr,
|
||||||
sw.ElapsedMilliseconds);
|
sw.ElapsedMilliseconds);
|
||||||
res.IsBuildInProgress = false;
|
if (res is not null)
|
||||||
res.Dispose();
|
{
|
||||||
|
res.IsBuildInProgress = false;
|
||||||
|
res.Release();
|
||||||
|
}
|
||||||
|
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ using System.Collections.Generic;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reactive.Disposables;
|
using System.Reactive.Disposables;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
using Dalamud.Game.Text;
|
using Dalamud.Game.Text;
|
||||||
using Dalamud.Interface.GameFonts;
|
using Dalamud.Interface.GameFonts;
|
||||||
|
|
@ -15,6 +16,8 @@ using ImGuiNET;
|
||||||
|
|
||||||
using Lumina.Data.Files;
|
using Lumina.Data.Files;
|
||||||
|
|
||||||
|
using Serilog;
|
||||||
|
|
||||||
using Vector4 = System.Numerics.Vector4;
|
using Vector4 = System.Numerics.Vector4;
|
||||||
|
|
||||||
namespace Dalamud.Interface.ManagedFontAtlas.Internals;
|
namespace Dalamud.Interface.ManagedFontAtlas.Internals;
|
||||||
|
|
@ -34,7 +37,10 @@ internal class GamePrebakedFontHandle : IFontHandle.IInternal
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly char SeIconCharMax = (char)Enum.GetValues<SeIconChar>().Max();
|
public static readonly char SeIconCharMax = (char)Enum.GetValues<SeIconChar>().Max();
|
||||||
|
|
||||||
|
private readonly List<IDisposable> pushedFonts = new(8);
|
||||||
|
|
||||||
private IFontHandleManager? manager;
|
private IFontHandleManager? manager;
|
||||||
|
private long lastCumulativePresentCalls;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="GamePrebakedFontHandle"/> class.
|
/// Initializes a new instance of the <see cref="GamePrebakedFontHandle"/> class.
|
||||||
|
|
@ -53,6 +59,11 @@ internal class GamePrebakedFontHandle : IFontHandle.IInternal
|
||||||
this.FontStyle = style;
|
this.FontStyle = style;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public event Action<IFontHandle>? ImFontChanged;
|
||||||
|
|
||||||
|
private event Action<IFontHandle>? Disposed;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Provider for <see cref="IDalamudTextureWrap"/> for `common/font/fontNN.tex`.
|
/// Provider for <see cref="IDalamudTextureWrap"/> for `common/font/fontNN.tex`.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -113,10 +124,104 @@ internal class GamePrebakedFontHandle : IFontHandle.IInternal
|
||||||
{
|
{
|
||||||
this.manager?.FreeFontHandle(this);
|
this.manager?.FreeFontHandle(this);
|
||||||
this.manager = null;
|
this.manager = null;
|
||||||
|
this.Disposed?.InvokeSafely(this);
|
||||||
|
this.ImFontChanged = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <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>
|
/// <summary>
|
||||||
/// Manager for <see cref="GamePrebakedFontHandle"/>s.
|
/// Manager for <see cref="GamePrebakedFontHandle"/>s.
|
||||||
|
|
@ -124,6 +229,7 @@ internal class GamePrebakedFontHandle : IFontHandle.IInternal
|
||||||
internal sealed class HandleManager : IFontHandleManager
|
internal sealed class HandleManager : IFontHandleManager
|
||||||
{
|
{
|
||||||
private readonly Dictionary<GameFontStyle, int> gameFontsRc = new();
|
private readonly Dictionary<GameFontStyle, int> gameFontsRc = new();
|
||||||
|
private readonly HashSet<GamePrebakedFontHandle> handles = new();
|
||||||
private readonly object syncRoot = new();
|
private readonly object syncRoot = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -154,8 +260,7 @@ internal class GamePrebakedFontHandle : IFontHandle.IInternal
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
this.Substance?.Dispose();
|
// empty
|
||||||
this.Substance = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc cref="IFontAtlas.NewGameFontHandle"/>
|
/// <inheritdoc cref="IFontAtlas.NewGameFontHandle"/>
|
||||||
|
|
@ -165,6 +270,7 @@ internal class GamePrebakedFontHandle : IFontHandle.IInternal
|
||||||
bool suggestRebuild;
|
bool suggestRebuild;
|
||||||
lock (this.syncRoot)
|
lock (this.syncRoot)
|
||||||
{
|
{
|
||||||
|
this.handles.Add(handle);
|
||||||
this.gameFontsRc[style] = this.gameFontsRc.GetValueOrDefault(style, 0) + 1;
|
this.gameFontsRc[style] = this.gameFontsRc.GetValueOrDefault(style, 0) + 1;
|
||||||
suggestRebuild = this.Substance?.GetFontPtr(handle).IsNotNullAndLoaded() is not true;
|
suggestRebuild = this.Substance?.GetFontPtr(handle).IsNotNullAndLoaded() is not true;
|
||||||
}
|
}
|
||||||
|
|
@ -183,6 +289,7 @@ internal class GamePrebakedFontHandle : IFontHandle.IInternal
|
||||||
|
|
||||||
lock (this.syncRoot)
|
lock (this.syncRoot)
|
||||||
{
|
{
|
||||||
|
this.handles.Remove(ggfh);
|
||||||
if (!this.gameFontsRc.ContainsKey(ggfh.FontStyle))
|
if (!this.gameFontsRc.ContainsKey(ggfh.FontStyle))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
@ -192,10 +299,20 @@ internal class GamePrebakedFontHandle : IFontHandle.IInternal
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <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)
|
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.
|
/// Initializes a new instance of the <see cref="HandleSubstance"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="manager">The manager.</param>
|
/// <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>
|
/// <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;
|
this.handleManager = manager;
|
||||||
Service<InterfaceManager>.Get();
|
this.DataRoot = dataRoot;
|
||||||
|
this.RelevantHandles = relevantHandles;
|
||||||
this.gameFontStyles = new(gameFontStyles);
|
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/>
|
/// <inheritdoc/>
|
||||||
public IFontHandleManager Manager => this.handleManager;
|
public IFontHandleManager Manager => this.handleManager;
|
||||||
|
|
||||||
|
|
@ -240,6 +375,7 @@ internal class GamePrebakedFontHandle : IFontHandle.IInternal
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
|
// empty
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
using Dalamud.Utility;
|
||||||
|
|
||||||
namespace Dalamud.Interface.ManagedFontAtlas.Internals;
|
namespace Dalamud.Interface.ManagedFontAtlas.Internals;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -27,6 +29,12 @@ internal interface IFontHandleManager : IDisposable
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new substance of the font atlas.
|
/// Creates a new substance of the font atlas.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="dataRoot">The data root.</param>
|
||||||
/// <returns>The new substance.</returns>
|
/// <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>
|
/// </summary>
|
||||||
internal interface IFontHandleSubstance : IDisposable
|
internal interface IFontHandleSubstance : IDisposable
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the data root relevant to this instance of <see cref="IFontHandleSubstance"/>.
|
||||||
|
/// </summary>
|
||||||
|
IRefCountable DataRoot { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the manager relevant to this instance of <see cref="IFontHandleSubstance"/>.
|
/// Gets the manager relevant to this instance of <see cref="IFontHandleSubstance"/>.
|
||||||
/// </summary>
|
/// </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.Internal.Notifications;
|
||||||
using Dalamud.Interface.ManagedFontAtlas;
|
using Dalamud.Interface.ManagedFontAtlas;
|
||||||
using Dalamud.Interface.ManagedFontAtlas.Internals;
|
using Dalamud.Interface.ManagedFontAtlas.Internals;
|
||||||
|
using Dalamud.Plugin.Internal.Types;
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using ImGuiScene;
|
using ImGuiScene;
|
||||||
|
|
@ -41,6 +42,10 @@ public sealed class UiBuilder : IDisposable
|
||||||
private bool hasErrorWindow = false;
|
private bool hasErrorWindow = false;
|
||||||
private bool lastFrameUiHideState = false;
|
private bool lastFrameUiHideState = false;
|
||||||
|
|
||||||
|
private IFontHandle? defaultFontHandle;
|
||||||
|
private IFontHandle? iconFontHandle;
|
||||||
|
private IFontHandle? monoFontHandle;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="UiBuilder"/> class and registers it.
|
/// Initializes a new instance of the <see cref="UiBuilder"/> class and registers it.
|
||||||
/// You do not have to call this manually.
|
/// You do not have to call this manually.
|
||||||
|
|
@ -99,21 +104,57 @@ public sealed class UiBuilder : IDisposable
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets an action that is called any time ImGui fonts need to be rebuilt.<br/>
|
/// 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
|
/// (at any time), so you should both reload your custom fonts and restore those
|
||||||
/// pointers inside this handler.
|
/// pointers inside this handler.
|
||||||
/// </summary>
|
/// </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)]
|
[Api10ToDo(Api10ToDoAttribute.DeleteCompatBehavior)]
|
||||||
public event Action? BuildFonts;
|
public event Action? BuildFonts;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets an action that is called any time right after ImGui fonts are rebuilt.<br/>
|
/// 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
|
/// (at any time), so you should both reload your custom fonts and restore those
|
||||||
/// pointers inside this handler.
|
/// pointers inside this handler.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Obsolete($"Use {nameof(this.FontAtlas)} instead.", false)]
|
[Obsolete($"See remarks for {nameof(BuildFonts)}.", false)]
|
||||||
[Api10ToDo(Api10ToDoAttribute.DeleteCompatBehavior)]
|
[Api10ToDo(Api10ToDoAttribute.DeleteCompatBehavior)]
|
||||||
public event Action? AfterBuildFonts;
|
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 />
|
/// 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>
|
/// <strong>Accessing this static property outside of <see cref="Draw"/> is dangerous and not supported.</strong>
|
||||||
/// </summary>
|
/// </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>
|
/// <remarks>
|
||||||
/// A font handle corresponding to this font can be obtained with:
|
/// A font handle corresponding to this font can be obtained with:
|
||||||
/// <code>
|
/// <code>
|
||||||
|
|
@ -151,11 +209,15 @@ public sealed class UiBuilder : IDisposable
|
||||||
/// tk => tk.AddDalamudDefaultFont(UiBuilder.DefaultFontSizePt)));
|
/// tk => tk.AddDalamudDefaultFont(UiBuilder.DefaultFontSizePt)));
|
||||||
/// </code>
|
/// </code>
|
||||||
/// </remarks>
|
/// </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>
|
/// <summary>
|
||||||
/// Gets the default Dalamud icon font based on FontAwesome 5 Free solid.<br />
|
/// Gets the default Dalamud icon font based on FontAwesome 5 Free solid.
|
||||||
/// <strong>Accessing this static property outside of <see cref="Draw"/> is dangerous and not supported.</strong>
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// A font handle corresponding to this font can be obtained with:
|
/// 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 })));
|
/// tk => tk.AddFontAwesomeIconFont(new() { SizePt = UiBuilder.DefaultFontSizePt })));
|
||||||
/// </code>
|
/// </code>
|
||||||
/// </remarks>
|
/// </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>
|
/// <summary>
|
||||||
/// Gets the default Dalamud monospaced font based on Inconsolata Regular.<br />
|
/// Gets the default Dalamud monospaced font based on Inconsolata Regular.
|
||||||
/// <strong>Accessing this static property outside of <see cref="Draw"/> is dangerous and not supported.</strong>
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// A font handle corresponding to this font can be obtained with:
|
/// A font handle corresponding to this font can be obtained with:
|
||||||
|
|
@ -181,7 +247,12 @@ public sealed class UiBuilder : IDisposable
|
||||||
/// new() { SizePt = UiBuilder.DefaultFontSizePt })));
|
/// new() { SizePt = UiBuilder.DefaultFontSizePt })));
|
||||||
/// </code>
|
/// </code>
|
||||||
/// </remarks>
|
/// </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>
|
/// <summary>
|
||||||
/// Gets the game's active Direct3D device.
|
/// Gets the game's active Direct3D device.
|
||||||
|
|
@ -660,4 +731,48 @@ public sealed class UiBuilder : IDisposable
|
||||||
{
|
{
|
||||||
this.ResizeBuffers?.InvokeSafely();
|
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()))
|
.Select(x => x.ToContentDisposedTask()))
|
||||||
.ContinueWith(_ => loadTimings.Dispose()),
|
.ContinueWith(_ => loadTimings.Dispose()),
|
||||||
"Prevent Dalamud from loading more stuff, until we've ensured that all required assets are available.");
|
"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/>
|
/// <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