diff --git a/Dalamud/Interface/Internal/InterfaceManager.cs b/Dalamud/Interface/Internal/InterfaceManager.cs
index 25baa5e29..93050d67a 100644
--- a/Dalamud/Interface/Internal/InterfaceManager.cs
+++ b/Dalamud/Interface/Internal/InterfaceManager.cs
@@ -717,28 +717,28 @@ internal class InterfaceManager : IDisposable, IServiceType
tk => tk.AddDalamudAssetFont(
DalamudAsset.InconsolataRegular,
new() { SizePx = DefaultFontSizePx })));
- this.dalamudAtlas.BuildStepChange += e => e.OnPostPromotion(
- tk =>
- {
- // Note: the first call of this function is done outside the main thread; this is expected.
- // 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(defaultFont, monoFont, true);
-
- // Update default font
- unsafe
+ this.dalamudAtlas.BuildStepChange += e => e
+ .OnPostBuild(
+ tk =>
{
- ImGui.GetIO().NativePtr->FontDefault = defaultFont;
- }
+ // Fill missing glyphs in MonoFont from DefaultFont.
+ tk.CopyGlyphsAcrossFonts(
+ tk.GetFont(this.DefaultFontHandle),
+ tk.GetFont(this.MonoFontHandle),
+ missingOnly: true);
+ })
+ .OnPostPromotion(
+ tk =>
+ {
+ // Update the ImGui default font.
+ unsafe
+ {
+ ImGui.GetIO().NativePtr->FontDefault = tk.GetFont(this.DefaultFontHandle);
+ }
- // Broadcast to auto-rebuilding instances
- this.AfterBuildFonts?.Invoke();
- });
+ // Broadcast to auto-rebuilding instances.
+ this.AfterBuildFonts?.Invoke();
+ });
}
// This will wait for scene on its own. We just wait for this.dalamudAtlas.BuildTask in this.InitScene.
diff --git a/Dalamud/Interface/ManagedFontAtlas/FontAtlasBuildStep.cs b/Dalamud/Interface/ManagedFontAtlas/FontAtlasBuildStep.cs
index 345ab729d..ba94db435 100644
--- a/Dalamud/Interface/ManagedFontAtlas/FontAtlasBuildStep.cs
+++ b/Dalamud/Interface/ManagedFontAtlas/FontAtlasBuildStep.cs
@@ -7,32 +7,35 @@ namespace Dalamud.Interface.ManagedFontAtlas;
///
public enum FontAtlasBuildStep
{
- ///
- /// An invalid value. This should never be passed through event callbacks.
- ///
- Invalid,
+ // Note: leave 0 alone; make default(FontAtlasBuildStep) not have a valid value
///
/// Called before calling .
- /// Expect to be passed.
+ /// Expect to be passed.
+ /// When called from , this will be called before the delegates
+ /// passed to .
///
- PreBuild,
+ PreBuild = 1,
///
/// Called after calling .
/// Expect to be passed.
+ /// When called from , this will be called after the delegates
+ /// passed to ; you can do cross-font operations here.
///
/// This callback is not guaranteed to happen after ,
/// but it will never happen on its own.
///
- PostBuild,
+ PostBuild = 2,
///
/// Called after promoting staging font atlas to the actual atlas for .
/// Expect to be passed.
+ /// When called from , this will be called after the delegates
+ /// passed to ; you should not make modifications to fonts.
///
/// This callback is not guaranteed to happen after ,
/// but it will never happen on its own.
///
- PostPromotion,
+ PostPromotion = 3,
}
diff --git a/Dalamud/Interface/ManagedFontAtlas/IFontAtlasBuildToolkit.cs b/Dalamud/Interface/ManagedFontAtlas/IFontAtlasBuildToolkit.cs
index a997c48c1..f75ed4686 100644
--- a/Dalamud/Interface/ManagedFontAtlas/IFontAtlasBuildToolkit.cs
+++ b/Dalamud/Interface/ManagedFontAtlas/IFontAtlasBuildToolkit.cs
@@ -80,4 +80,12 @@ public interface IFontAtlasBuildToolkit
///
/// The action to run on dispose.
void DisposeWithAtlas(Action action);
+
+ ///
+ /// Gets the instance of corresponding to
+ /// from .
+ ///
+ /// The font handle.
+ /// The corresonding , or default if not found.
+ ImFontPtr GetFont(IFontHandle fontHandle);
}
diff --git a/Dalamud/Interface/ManagedFontAtlas/IFontAtlasBuildToolkitPostBuild.cs b/Dalamud/Interface/ManagedFontAtlas/IFontAtlasBuildToolkitPostBuild.cs
index 3c14197e0..eb7c7e08c 100644
--- a/Dalamud/Interface/ManagedFontAtlas/IFontAtlasBuildToolkitPostBuild.cs
+++ b/Dalamud/Interface/ManagedFontAtlas/IFontAtlasBuildToolkitPostBuild.cs
@@ -23,4 +23,28 @@ public interface IFontAtlasBuildToolkitPostBuild : IFontAtlasBuildToolkit
/// Dispose the wrap on error.
/// The texture index.
int StoreTexture(IDalamudTextureWrap textureWrap, bool disposeOnError);
+
+ ///
+ /// Copies glyphs across fonts, in a safer way.
+ /// If the font does not belong to the current atlas, this function is a no-op.
+ ///
+ /// Source font.
+ /// Target font.
+ /// Whether to copy missing glyphs only.
+ /// Whether to call target.BuildLookupTable().
+ /// Low codepoint range to copy.
+ /// High codepoing range to copy.
+ void CopyGlyphsAcrossFonts(
+ ImFontPtr source,
+ ImFontPtr target,
+ bool missingOnly,
+ bool rebuildLookupTable = true,
+ char rangeLow = ' ',
+ char rangeHigh = '\uFFFE');
+
+ ///
+ /// Calls , with some fixups.
+ ///
+ /// The font.
+ void BuildLookupTable(ImFontPtr font);
}
diff --git a/Dalamud/Interface/ManagedFontAtlas/IFontAtlasBuildToolkitPostPromotion.cs b/Dalamud/Interface/ManagedFontAtlas/IFontAtlasBuildToolkitPostPromotion.cs
index 8c3c91624..930851fc7 100644
--- a/Dalamud/Interface/ManagedFontAtlas/IFontAtlasBuildToolkitPostPromotion.cs
+++ b/Dalamud/Interface/ManagedFontAtlas/IFontAtlasBuildToolkitPostPromotion.cs
@@ -1,5 +1,3 @@
-using ImGuiNET;
-
namespace Dalamud.Interface.ManagedFontAtlas;
///
@@ -7,27 +5,5 @@ namespace Dalamud.Interface.ManagedFontAtlas;
///
public interface IFontAtlasBuildToolkitPostPromotion : IFontAtlasBuildToolkit
{
- ///
- /// Copies glyphs across fonts, in a safer way.
- /// If the font does not belong to the current atlas, this function is a no-op.
- ///
- /// Source font.
- /// Target font.
- /// Whether to copy missing glyphs only.
- /// Whether to call target.BuildLookupTable().
- /// Low codepoint range to copy.
- /// High codepoing range to copy.
- void CopyGlyphsAcrossFonts(
- ImFontPtr source,
- ImFontPtr target,
- bool missingOnly,
- bool rebuildLookupTable = true,
- char rangeLow = ' ',
- char rangeHigh = '\uFFFE');
-
- ///
- /// Calls , with some fixups.
- ///
- /// The font.
- void BuildLookupTable(ImFontPtr font);
+ // empty
}
diff --git a/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.BuildToolkit.cs b/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.BuildToolkit.cs
index fde115c9e..3addfabe8 100644
--- a/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.BuildToolkit.cs
+++ b/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.BuildToolkit.cs
@@ -135,6 +135,19 @@ internal sealed partial class FontAtlasFactory
}
}
+ ///
+ public ImFontPtr GetFont(IFontHandle fontHandle)
+ {
+ foreach (var s in this.data.Substances)
+ {
+ var f = s.GetFontPtr(fontHandle);
+ if (!f.IsNull())
+ return f;
+ }
+
+ return default;
+ }
+
///
public ImFontPtr IgnoreGlobalScale(ImFontPtr fontPtr)
{
@@ -608,49 +621,6 @@ internal sealed partial class FontAtlasFactory
ArrayPool.Shared.Return(buf);
}
}
- }
-
- ///
- /// Implementations for .
- ///
- private class BuildToolkitPostPromotion : IFontAtlasBuildToolkitPostPromotion
- {
- private readonly FontAtlasBuiltData builtData;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The built data.
- public BuildToolkitPostPromotion(FontAtlasBuiltData builtData) => this.builtData = builtData;
-
- ///
- public ImFontPtr Font { get; set; }
-
- ///
- public float Scale => this.builtData.Scale;
-
- ///
- public bool IsAsyncBuildOperation => true;
-
- ///
- public FontAtlasBuildStep BuildStep => FontAtlasBuildStep.PostPromotion;
-
- ///
- public ImFontAtlasPtr NewImAtlas => this.builtData.Atlas;
-
- ///
- public unsafe ImVectorWrapper Fonts => new(
- &this.NewImAtlas.NativePtr->Fonts,
- x => ImGuiNative.ImFont_destroy(x->NativePtr));
-
- ///
- public T DisposeWithAtlas(T disposable) where T : IDisposable => this.builtData.Garbage.Add(disposable);
-
- ///
- public GCHandle DisposeWithAtlas(GCHandle gcHandle) => this.builtData.Garbage.Add(gcHandle);
-
- ///
- public void DisposeWithAtlas(Action action) => this.builtData.Garbage.Add(action);
///
public unsafe void CopyGlyphsAcrossFonts(
@@ -707,4 +677,60 @@ internal sealed partial class FontAtlasFactory
}
}
}
+
+ ///
+ /// Implementations for .
+ ///
+ private class BuildToolkitPostPromotion : IFontAtlasBuildToolkitPostPromotion
+ {
+ private readonly FontAtlasBuiltData builtData;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The built data.
+ public BuildToolkitPostPromotion(FontAtlasBuiltData builtData) => this.builtData = builtData;
+
+ ///
+ public ImFontPtr Font { get; set; }
+
+ ///
+ public float Scale => this.builtData.Scale;
+
+ ///
+ public bool IsAsyncBuildOperation => true;
+
+ ///
+ public FontAtlasBuildStep BuildStep => FontAtlasBuildStep.PostPromotion;
+
+ ///
+ public ImFontAtlasPtr NewImAtlas => this.builtData.Atlas;
+
+ ///
+ public unsafe ImVectorWrapper Fonts => new(
+ &this.NewImAtlas.NativePtr->Fonts,
+ x => ImGuiNative.ImFont_destroy(x->NativePtr));
+
+ ///
+ public T DisposeWithAtlas(T disposable) where T : IDisposable => this.builtData.Garbage.Add(disposable);
+
+ ///
+ public GCHandle DisposeWithAtlas(GCHandle gcHandle) => this.builtData.Garbage.Add(gcHandle);
+
+ ///
+ public void DisposeWithAtlas(Action action) => this.builtData.Garbage.Add(action);
+
+ ///
+ public ImFontPtr GetFont(IFontHandle fontHandle)
+ {
+ foreach (var s in this.builtData.Substances)
+ {
+ var f = s.GetFontPtr(fontHandle);
+ if (!f.IsNull())
+ return f;
+ }
+
+ return default;
+ }
+ }
}
diff --git a/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.Implementation.cs b/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.Implementation.cs
index 99ce8dab9..85f7219b2 100644
--- a/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.Implementation.cs
+++ b/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.Implementation.cs
@@ -4,7 +4,6 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reactive.Disposables;
-using System.Threading;
using System.Threading.Tasks;
using Dalamud.Interface.GameFonts;
@@ -168,7 +167,7 @@ internal sealed partial class FontAtlasFactory
_ => throw new InvalidOperationException(),
};
- public unsafe int Release()
+ public int Release()
{
switch (IRefCountable.AlterRefCount(-1, ref this.refCount, out var newRefCount))
{
@@ -176,22 +175,35 @@ internal sealed partial class FontAtlasFactory
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);
#endif
- this.Garbage.Dispose();
+
+ if (this.IsBuildInProgress)
+ {
+ unsafe
+ {
+ Log.Error(
+ "[{name}] 0x{ptr:X}: Trying to dispose while build is in progress; disposing later.\n" +
+ "Stack:\n{trace}",
+ this.Owner?.Name ?? ">",
+ (nint)this.Atlas.NativePtr,
+ new StackTrace());
+ }
+
+ Task.Run(
+ async () =>
+ {
+ while (this.IsBuildInProgress)
+ await Task.Delay(100);
+ this.Garbage.Dispose();
+ });
+ }
+ else
+ {
+ this.Garbage.Dispose();
+ }
+
return newRefCount;
case IRefCountable.RefCountResult.AlreadyDisposed:
@@ -549,20 +561,10 @@ internal sealed partial class FontAtlasFactory
return;
}
- var toolkit = new BuildToolkitPostPromotion(data);
+ foreach (var substance in data.Substances)
+ substance.Manager.InvokeFontHandleImFontChanged();
- try
- {
- this.BuildStepChange?.Invoke(toolkit);
- }
- catch (Exception e)
- {
- Log.Error(
- e,
- "[{name}] {delegateName} PostPromotion error",
- this.Name,
- nameof(FontAtlasBuildStepDelegate));
- }
+ var toolkit = new BuildToolkitPostPromotion(data);
foreach (var substance in data.Substances)
{
@@ -580,20 +582,18 @@ internal sealed partial class FontAtlasFactory
}
}
- foreach (var font in toolkit.Fonts)
+ try
{
- try
- {
- toolkit.BuildLookupTable(font);
- }
- catch (Exception e)
- {
- Log.Error(e, "[{name}] BuildLookupTable error", this.Name);
- }
+ this.BuildStepChange?.Invoke(toolkit);
+ }
+ catch (Exception e)
+ {
+ Log.Error(
+ e,
+ "[{name}] {delegateName} PostPromotion error",
+ this.Name,
+ nameof(FontAtlasBuildStepDelegate));
}
-
- foreach (var substance in data.Substances)
- substance.Manager.InvokeFontHandleImFontChanged();
#if VeryVerboseLog
Log.Verbose("[{name}] Built from {source}.", this.Name, source);
@@ -709,6 +709,9 @@ internal sealed partial class FontAtlasFactory
toolkit.PostBuildSubstances();
this.BuildStepChange?.Invoke(toolkit);
+ foreach (var font in toolkit.Fonts)
+ toolkit.BuildLookupTable(font);
+
if (this.factory.SceneTask is { IsCompleted: false } sceneTask)
{
Log.Verbose(
@@ -754,6 +757,8 @@ internal sealed partial class FontAtlasFactory
}
finally
{
+ // RS is being dumb
+ // ReSharper disable once ConstantConditionalAccessQualifier
toolkit?.Dispose();
this.buildQueued = false;
}
diff --git a/Dalamud/Interface/UiBuilder.cs b/Dalamud/Interface/UiBuilder.cs
index ea3803f35..af4cc39c2 100644
--- a/Dalamud/Interface/UiBuilder.cs
+++ b/Dalamud/Interface/UiBuilder.cs
@@ -700,6 +700,8 @@ public sealed class UiBuilder : IDisposable
if (e.IsAsyncBuildOperation)
return;
+ ThreadSafety.AssertMainThread();
+
if (this.BuildFonts is not null)
{
e.OnPreBuild(