diff --git a/Dalamud/Interface/Internal/InterfaceManager.cs b/Dalamud/Interface/Internal/InterfaceManager.cs index 46d37fe90..d252321db 100644 --- a/Dalamud/Interface/Internal/InterfaceManager.cs +++ b/Dalamud/Interface/Internal/InterfaceManager.cs @@ -699,35 +699,38 @@ internal class InterfaceManager : IDisposable, IServiceType { this.dalamudAtlas = fontAtlasFactory .CreateFontAtlas(nameof(InterfaceManager), FontAtlasAutoRebuildMode.Disable); - this.defaultFontHandle = (IFontHandle.IInternal)this.dalamudAtlas.NewDelegateFontHandle( - e => e.OnPreBuild(tk => tk.AddDalamudDefaultFont(DefaultFontSizePx))); - this.iconFontHandle = (IFontHandle.IInternal)this.dalamudAtlas.NewDelegateFontHandle( - e => e.OnPreBuild( - tk => tk.AddFontAwesomeIconFont( - new() - { - SizePx = DefaultFontSizePx, - GlyphMinAdvanceX = DefaultFontSizePx, - GlyphMaxAdvanceX = DefaultFontSizePx, - }))); - this.monoFontHandle = (IFontHandle.IInternal)this.dalamudAtlas.NewDelegateFontHandle( - e => e.OnPreBuild( - 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 (this.dalamudAtlas.SuppressAutoRebuild()) + { + this.defaultFontHandle = (IFontHandle.IInternal)this.dalamudAtlas.NewDelegateFontHandle( + e => e.OnPreBuild(tk => tk.AddDalamudDefaultFont(DefaultFontSizePx))); + this.iconFontHandle = (IFontHandle.IInternal)this.dalamudAtlas.NewDelegateFontHandle( + e => e.OnPreBuild( + tk => tk.AddFontAwesomeIconFont( + new() + { + SizePx = DefaultFontSizePx, + GlyphMinAdvanceX = DefaultFontSizePx, + GlyphMaxAdvanceX = DefaultFontSizePx, + }))); + this.monoFontHandle = (IFontHandle.IInternal)this.dalamudAtlas.NewDelegateFontHandle( + e => e.OnPreBuild( + 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. - // Fill missing glyphs in MonoFont from DefaultFont - tk.CopyGlyphsAcrossFonts(this.defaultFontHandle.ImFont, this.monoFontHandle.ImFont, true); + // Fill missing glyphs in MonoFont from DefaultFont + tk.CopyGlyphsAcrossFonts(this.defaultFontHandle.ImFont, this.monoFontHandle.ImFont, true); - // 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. _ = this.dalamudAtlas.BuildFontsAsync(false); diff --git a/Dalamud/Interface/ManagedFontAtlas/IFontAtlas.cs b/Dalamud/Interface/ManagedFontAtlas/IFontAtlas.cs index 0a50d6070..d32adc1eb 100644 --- a/Dalamud/Interface/ManagedFontAtlas/IFontAtlas.cs +++ b/Dalamud/Interface/ManagedFontAtlas/IFontAtlas.cs @@ -1,7 +1,6 @@ using System.Threading.Tasks; using Dalamud.Interface.GameFonts; -using Dalamud.Interface.ManagedFontAtlas.Internals; using ImGuiNET; @@ -54,25 +53,73 @@ public interface IFontAtlas : IDisposable /// bool IsGlobalScaled { get; } - /// + /// + /// Suppresses automatically rebuilding fonts for the scope. + /// + /// An instance of that will release the suppression. + /// + /// Use when you will be creating multiple new handles, and want rebuild to trigger only when you're done doing so. + /// This function will effectively do nothing, if is set to + /// . + /// + /// + /// + /// using (atlas.SuppressBuild()) { + /// this.font1 = atlas.NewGameFontHandle(...); + /// this.font2 = atlas.NewDelegateFontHandle(...); + /// } + /// + /// + public IDisposable SuppressAutoRebuild(); + + /// + /// Creates a new from game's built-in fonts. + /// + /// Font to use. + /// Handle to a font that may or may not be ready yet. public IFontHandle NewGameFontHandle(GameFontStyle style); - /// + /// + /// Creates a new IFontHandle using your own callbacks. + /// + /// Callback for . + /// Handle to a font that may or may not be ready yet. + /// + /// On initialization: + /// + /// this.fontHandle = atlas.NewDelegateFontHandle(e => e.OnPreBuild(tk => { + /// var config = new SafeFontConfig { SizePx = 16 }; + /// config.MergeFont = tk.AddFontFromFile(@"C:\Windows\Fonts\comic.ttf", config); + /// tk.AddGameSymbol(config); + /// tk.AddExtraGlyphsForDalamudLanguage(config); + /// // optionally do the following if you have to add more than one font here, + /// // to specify which font added during this delegate is the final font to use. + /// tk.Font = config.MergeFont; + /// })); + /// // or + /// this.fontHandle = atlas.NewDelegateFontHandle(e => e.OnPreBuild(tk => tk.AddDalamudDefaultFont(36))); + /// + ///
+ /// On use: + /// + /// using (this.fontHandle.Push()) + /// ImGui.TextUnformatted("Example"); + /// + ///
public IFontHandle NewDelegateFontHandle(FontAtlasBuildStepDelegate buildStepDelegate); - /// - public void FreeFontHandle(IFontHandle handle); - /// /// Queues rebuilding fonts, on the main thread.
/// Note that would not necessarily get changed from calling this function. ///
+ /// If is . void BuildFontsOnNextFrame(); /// /// Rebuilds fonts immediately, on the current thread.
/// Even the callback for will be called on the same thread. ///
+ /// If is . void BuildFontsImmediately(); /// @@ -80,5 +127,6 @@ public interface IFontAtlas : IDisposable /// /// Call on the main thread. /// The task. + /// If is . Task BuildFontsAsync(bool callPostPromotionOnMainThread = true); } diff --git a/Dalamud/Interface/ManagedFontAtlas/Internals/DelegateFontHandle.cs b/Dalamud/Interface/ManagedFontAtlas/Internals/DelegateFontHandle.cs index f9f2c0ef1..b6ec720dc 100644 --- a/Dalamud/Interface/ManagedFontAtlas/Internals/DelegateFontHandle.cs +++ b/Dalamud/Interface/ManagedFontAtlas/Internals/DelegateFontHandle.cs @@ -88,11 +88,7 @@ internal class DelegateFontHandle : IFontHandle.IInternal } } - /// - /// Creates a new IFontHandle using your own callbacks. - /// - /// Callback for . - /// Handle to a font that may or may not be ready yet. + /// public IFontHandle NewFontHandle(FontAtlasBuildStepDelegate buildStepDelegate) { var key = new DelegateFontHandle(this, buildStepDelegate); diff --git a/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.Implementation.cs b/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.Implementation.cs index 52d77b963..5656fc673 100644 --- a/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.Implementation.cs +++ b/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.Implementation.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Reactive.Disposables; using System.Threading; using System.Threading.Tasks; @@ -203,6 +204,9 @@ internal sealed partial class FontAtlasFactory private Task buildTask = EmptyTask; private FontAtlasBuiltData builtData; + private int buildSuppressionCounter; + private bool buildSuppressionSuppressed; + private int buildIndex; private bool buildQueued; private bool disposed = false; @@ -356,6 +360,19 @@ internal sealed partial class FontAtlasFactory GC.SuppressFinalize(this); } + /// + public IDisposable SuppressAutoRebuild() + { + this.buildSuppressionCounter++; + return Disposable.Create( + () => + { + this.buildSuppressionCounter--; + if (this.buildSuppressionSuppressed) + this.OnRebuildRecommend(); + }); + } + /// public IFontHandle NewGameFontHandle(GameFontStyle style) => this.gameFontHandleManager.NewFontHandle(style); @@ -363,15 +380,6 @@ internal sealed partial class FontAtlasFactory public IFontHandle NewDelegateFontHandle(FontAtlasBuildStepDelegate buildStepDelegate) => this.delegateFontHandleManager.NewFontHandle(buildStepDelegate); - /// - public void FreeFontHandle(IFontHandle handle) - { - foreach (var manager in this.fontHandleManagers) - { - manager.FreeFontHandle(handle); - } - } - /// public void BuildFontsOnNextFrame() { @@ -688,6 +696,13 @@ internal sealed partial class FontAtlasFactory if (this.disposed) return; + if (this.buildSuppressionCounter > 0) + { + this.buildSuppressionSuppressed = true; + return; + } + + this.buildSuppressionSuppressed = false; this.factory.Framework.RunOnFrameworkThread( () => { diff --git a/Dalamud/Interface/ManagedFontAtlas/Internals/GamePrebakedFontHandle.cs b/Dalamud/Interface/ManagedFontAtlas/Internals/GamePrebakedFontHandle.cs index c40302f6c..2739ed2da 100644 --- a/Dalamud/Interface/ManagedFontAtlas/Internals/GamePrebakedFontHandle.cs +++ b/Dalamud/Interface/ManagedFontAtlas/Internals/GamePrebakedFontHandle.cs @@ -157,11 +157,7 @@ internal class GamePrebakedFontHandle : IFontHandle.IInternal this.Substance = null; } - /// - /// Creates a new from game's built-in fonts. - /// - /// Font to use. - /// Handle to a font that may or may not be ready yet. + /// public IFontHandle NewFontHandle(GameFontStyle style) { var handle = new GamePrebakedFontHandle(this, style); diff --git a/Dalamud/Interface/UiBuilder.cs b/Dalamud/Interface/UiBuilder.cs index 5d0810009..a477ec09e 100644 --- a/Dalamud/Interface/UiBuilder.cs +++ b/Dalamud/Interface/UiBuilder.cs @@ -37,7 +37,6 @@ public sealed class UiBuilder : IDisposable private readonly DalamudConfiguration configuration = Service.Get(); private readonly DisposeSafety.ScopedFinalizer scopedFinalizer = new(); - private readonly IFontAtlas privateAtlas; private bool hasErrorWindow = false; private bool lastFrameUiHideState = false; @@ -61,14 +60,14 @@ public sealed class UiBuilder : IDisposable this.interfaceManager.ResizeBuffers += this.OnResizeBuffers; this.scopedFinalizer.Add(() => this.interfaceManager.ResizeBuffers -= this.OnResizeBuffers); - this.privateAtlas = + this.FontAtlas = this.scopedFinalizer .Add( Service .Get() .CreateFontAtlas(namespaceName, FontAtlasAutoRebuildMode.Disable)); - this.privateAtlas.BuildStepChange += this.PrivateAtlasOnBuildStepChange; - this.privateAtlas.RebuildRecommend += this.RebuildFonts; + this.FontAtlas.BuildStepChange += this.PrivateAtlasOnBuildStepChange; + this.FontAtlas.RebuildRecommend += this.RebuildFonts; } catch { @@ -104,7 +103,7 @@ public sealed class UiBuilder : IDisposable /// (at any time), so you should both reload your custom fonts and restore those /// pointers inside this handler. /// - [Obsolete($"Use {nameof(NewDelegateFontHandle)} instead.", false)] + [Obsolete($"Use {nameof(this.FontAtlas)} instead.", false)] public event Action? BuildFonts; /// @@ -113,7 +112,7 @@ public sealed class UiBuilder : IDisposable /// (at any time), so you should both reload your custom fonts and restore those /// pointers inside this handler. /// - [Obsolete($"Use {nameof(NewDelegateFontHandle)} instead.", false)] + [Obsolete($"Use {nameof(this.FontAtlas)} instead.", false)] public event Action? AfterBuildFonts; /// @@ -145,7 +144,7 @@ public sealed class UiBuilder : IDisposable /// /// A font handle corresponding to this font can be obtained with: /// - /// uiBuilderOrFontAtlas.NewDelegateFontHandle( + /// fontAtlas.NewDelegateFontHandle( /// e => e.OnPreBuild( /// tk => tk.AddDalamudDefaultFont(UiBuilder.DefaultFontSizePt))); /// @@ -159,7 +158,7 @@ public sealed class UiBuilder : IDisposable /// /// A font handle corresponding to this font can be obtained with: /// - /// uiBuilderOrFontAtlas.NewDelegateFontHandle( + /// fontAtlas.NewDelegateFontHandle( /// e => e.OnPreBuild( /// tk => tk.AddFontAwesomeIconFont(new() { SizePt = UiBuilder.DefaultFontSizePt }))); /// @@ -173,7 +172,7 @@ public sealed class UiBuilder : IDisposable /// /// A font handle corresponding to this font can be obtained with: /// - /// uiBuilderOrFontAtlas.NewDelegateFontHandle( + /// fontAtlas.NewDelegateFontHandle( /// e => e.OnPreBuild( /// tk => tk.AddDalamudAssetFont( /// DalamudAsset.InconsolataRegular, @@ -251,6 +250,11 @@ public sealed class UiBuilder : IDisposable /// public bool UiPrepared => Service.GetNullable() != null; + /// + /// Gets the plugin-private font atlas. + /// + public IFontAtlas FontAtlas { get; } + /// /// Gets or sets a value indicating whether statistics about UI draw time should be collected. /// @@ -418,40 +422,11 @@ public sealed class UiBuilder : IDisposable /// /// Font to get. /// Handle to the game font which may or may not be available for use yet. - [Obsolete($"Use {nameof(NewGameFontHandle)} instead.", false)] + [Obsolete($"Use {nameof(this.FontAtlas)}.{nameof(IFontAtlas.NewGameFontHandle)} instead.", false)] public GameFontHandle GetGameFontHandle(GameFontStyle style) => new( - (IFontHandle.IInternal)this.NewGameFontHandle(style), + (IFontHandle.IInternal)this.FontAtlas.NewGameFontHandle(style), Service.Get()); - /// - public IFontHandle NewGameFontHandle(GameFontStyle style) => this.privateAtlas.NewGameFontHandle(style); - - /// - /// - /// On initialization: - /// - /// this.fontHandle = uiBuilder.NewDelegateFontHandle(e => e.OnPreBuild(tk => { - /// var config = new SafeFontConfig { SizePx = 16 }; - /// config.MergeFont = tk.AddFontFromFile(@"C:\Windows\Fonts\comic.ttf", config); - /// tk.AddGameSymbol(config); - /// tk.AddExtraGlyphsForDalamudLanguage(config); - /// // optionally do the following if you have to add more than one font here, - /// // to specify which font added during this delegate is the final font to use. - /// tk.Font = config.MergeFont; - /// })); - /// // or - /// this.fontHandle = uiBuilder.NewDelegateFontHandle(e => e.OnPreBuild(tk => tk.AddDalamudDefaultFont(36))); - /// - ///
- /// On use: - /// - /// using (this.fontHandle.Push()) - /// ImGui.TextUnformatted("Example"); - /// - ///
- public IFontHandle NewDelegateFontHandle(FontAtlasBuildStepDelegate buildStepDelegate) => - this.privateAtlas.NewDelegateFontHandle(buildStepDelegate); - /// /// Call this to queue a rebuild of the font atlas.
/// This will invoke any and handlers and ensure that any @@ -461,9 +436,9 @@ public sealed class UiBuilder : IDisposable { Log.Verbose("[FONT] {0} plugin is initiating FONT REBUILD", this.namespaceName); if (this.AfterBuildFonts is null && this.BuildFonts is null) - this.privateAtlas.BuildFontsAsync(); + this.FontAtlas.BuildFontsAsync(); else - this.privateAtlas.BuildFontsOnNextFrame(); + this.FontAtlas.BuildFontsOnNextFrame(); } /// @@ -579,7 +554,7 @@ public sealed class UiBuilder : IDisposable } // just in case, if something goes wrong, prevent drawing; otherwise it probably will crash. - if (!this.privateAtlas.BuildTask.IsCompletedSuccessfully + if (!this.FontAtlas.BuildTask.IsCompletedSuccessfully && (this.BuildFonts is not null || this.AfterBuildFonts is not null)) { return;