Completion: Don't create Utf8String before the game has initialized

This commit is contained in:
goaaats 2025-05-29 21:08:03 +02:00
parent 84d121c7bc
commit bf0dbde55f
2 changed files with 34 additions and 12 deletions

View file

@ -241,12 +241,12 @@ internal sealed unsafe class CommandManager : IInternalDisposableService, IComma
public class CommandEventArgs : EventArgs public class CommandEventArgs : EventArgs
{ {
/// <summary> /// <summary>
/// Gets the command string /// Gets the command string.
/// </summary> /// </summary>
public string Command { get; init; } public string Command { get; init; }
/// <summary> /// <summary>
/// Gets the command info /// Gets the command info.
/// </summary> /// </summary>
public IReadOnlyCommandInfo CommandInfo { get; init; } public IReadOnlyCommandInfo CommandInfo { get; init; }
} }

View file

@ -31,10 +31,11 @@ internal sealed unsafe class Completion : IInternalDisposableService
[ServiceManager.ServiceDependency] [ServiceManager.ServiceDependency]
private readonly Framework framework = Service<Framework>.Get(); private readonly Framework framework = Service<Framework>.Get();
private readonly EntryStrings dalamudCategory = new("【Dalamud】");
private readonly Dictionary<string, EntryStrings> cachedCommands = []; private readonly Dictionary<string, EntryStrings> cachedCommands = [];
private readonly ConcurrentQueue<string> addedCommands = []; private readonly ConcurrentQueue<string> addedCommands = [];
private EntryStrings? dalamudCategory;
private Hook<CompletionModule.Delegates.GetSelection>? getSelection; private Hook<CompletionModule.Delegates.GetSelection>? getSelection;
// This is marked volatile since we set and check it from different threads. Instead of using a synchronization // 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.framework.Update -= this.OnUpdate;
this.commandManager.CommandAdded -= this.OnCommandAdded; this.commandManager.CommandAdded -= this.OnCommandAdded;
this.commandManager.CommandRemoved -= this.OnCommandRemoved; this.commandManager.CommandRemoved -= this.OnCommandRemoved;
this.dalamudCategory?.Dispose();
this.ClearCachedCommands();
} }
this.disposed = true; this.disposed = true;
@ -176,6 +180,11 @@ internal sealed unsafe class Completion : IInternalDisposableService
// but that's the same as making a typo, really // but that's the same as making a typo, really
if (textInput->CompletionDepth > 0) return; 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); this.LoadCommands(textInput->CompletionModule);
} }
@ -202,12 +211,25 @@ internal sealed unsafe class Completion : IInternalDisposableService
// Create the category since we don't have one // Create the category since we don't have one
var categoryData = (CategoryData*)Memory.MemoryHelper.GameAllocateDefault((ulong)sizeof(CategoryData)); var categoryData = (CategoryData*)Memory.MemoryHelper.GameAllocateDefault((ulong)sizeof(CategoryData));
categoryData->Ctor(GroupNumber, 0xFF); categoryData->Ctor(GroupNumber, 0xFF);
module->AddCategoryData(GroupNumber, this.dalamudCategory.Display->StringPtr, module->AddCategoryData(GroupNumber, this.dalamudCategory!.Display->StringPtr,
this.dalamudCategory.Match->StringPtr, categoryData); this.dalamudCategory.Match->StringPtr, categoryData);
return 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) private void LoadCommands(CompletionModule* completionModule)
{ {
if (completionModule == null) return; if (completionModule == null) return;
@ -217,7 +239,7 @@ internal sealed unsafe class Completion : IInternalDisposableService
{ {
this.needsClear = false; this.needsClear = false;
completionModule->ClearCompletionData(); completionModule->ClearCompletionData();
this.cachedCommands.Clear(); this.ClearCachedCommands();
return; return;
} }
@ -277,17 +299,17 @@ internal sealed unsafe class Completion : IInternalDisposableService
return ret; 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; } = public Utf8String* Display { get; } =
Utf8String.FromSequence(new SeStringBuilder().AddUiForeground(command, 539).Encode()); Utf8String.FromSequence(new SeStringBuilder().AddUiForeground(command, 539).Encode());
public Utf8String* Match { get; } = Utf8String.FromString(command); public Utf8String* Match { get; } = Utf8String.FromString(command);
public void Dispose()
{
this.Display->Dtor(true);
this.Match->Dtor(true);
}
} }
} }