diff --git a/Dalamud/Game/Command/CommandManager.cs b/Dalamud/Game/Command/CommandManager.cs index 09cd7877d..b72238abe 100644 --- a/Dalamud/Game/Command/CommandManager.cs +++ b/Dalamud/Game/Command/CommandManager.cs @@ -241,12 +241,12 @@ internal sealed unsafe class CommandManager : IInternalDisposableService, IComma public class CommandEventArgs : EventArgs { /// - /// Gets the command string + /// Gets the command string. /// public string Command { get; init; } /// - /// Gets the command info + /// Gets the command info. /// public IReadOnlyCommandInfo CommandInfo { get; init; } } diff --git a/Dalamud/Game/Internal/Completion.cs b/Dalamud/Game/Internal/Completion.cs index 05fae3514..56ff0d854 100644 --- a/Dalamud/Game/Internal/Completion.cs +++ b/Dalamud/Game/Internal/Completion.cs @@ -31,10 +31,11 @@ internal sealed unsafe class Completion : IInternalDisposableService [ServiceManager.ServiceDependency] private readonly Framework framework = Service.Get(); - private readonly EntryStrings dalamudCategory = new("【Dalamud】"); private readonly Dictionary cachedCommands = []; private readonly ConcurrentQueue addedCommands = []; + private EntryStrings? dalamudCategory; + private Hook? getSelection; // This is marked volatile since we set and check it from different threads. Instead of using a synchronization @@ -148,6 +149,9 @@ internal sealed unsafe class Completion : IInternalDisposableService this.framework.Update -= this.OnUpdate; this.commandManager.CommandAdded -= this.OnCommandAdded; this.commandManager.CommandRemoved -= this.OnCommandRemoved; + + this.dalamudCategory?.Dispose(); + this.ClearCachedCommands(); } this.disposed = true; @@ -176,6 +180,11 @@ internal sealed unsafe class Completion : IInternalDisposableService // but that's the same as making a typo, really if (textInput->CompletionDepth > 0) return; + // Create the category for Dalamud commands. + // This needs to be done here, since we cannot create Utf8Strings before the game + // has initialized (no allocator set up yet). + this.dalamudCategory ??= new EntryStrings("【Dalamud】"); + this.LoadCommands(textInput->CompletionModule); } @@ -202,12 +211,25 @@ internal sealed unsafe class Completion : IInternalDisposableService // Create the category since we don't have one var categoryData = (CategoryData*)Memory.MemoryHelper.GameAllocateDefault((ulong)sizeof(CategoryData)); categoryData->Ctor(GroupNumber, 0xFF); - module->AddCategoryData(GroupNumber, this.dalamudCategory.Display->StringPtr, + module->AddCategoryData(GroupNumber, this.dalamudCategory!.Display->StringPtr, this.dalamudCategory.Match->StringPtr, categoryData); return categoryData; } + private void ClearCachedCommands() + { + if (this.cachedCommands.Count == 0) + return; + + foreach (var entry in this.cachedCommands.Values) + { + entry.Dispose(); + } + + this.cachedCommands.Clear(); + } + private void LoadCommands(CompletionModule* completionModule) { if (completionModule == null) return; @@ -217,7 +239,7 @@ internal sealed unsafe class Completion : IInternalDisposableService { this.needsClear = false; completionModule->ClearCompletionData(); - this.cachedCommands.Clear(); + this.ClearCachedCommands(); return; } @@ -277,17 +299,17 @@ internal sealed unsafe class Completion : IInternalDisposableService return ret; } - private class EntryStrings(string command) + private class EntryStrings(string command) : IDisposable { - ~EntryStrings() - { - this.Display->Dtor(true); - this.Match->Dtor(true); - } - public Utf8String* Display { get; } = Utf8String.FromSequence(new SeStringBuilder().AddUiForeground(command, 539).Encode()); public Utf8String* Match { get; } = Utf8String.FromString(command); + + public void Dispose() + { + this.Display->Dtor(true); + this.Match->Dtor(true); + } } }