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);
+ }
}
}