From f29bdee010e828ca714a41917f8d49b4410fc0bb Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 4 Feb 2023 14:58:07 +0100 Subject: [PATCH] Try to improve launch times somewhat. --- Penumbra.GameData/Actors/ActorManager.Data.cs | 91 ++++++++----- .../Actors/ActorManager.Identifiers.cs | 15 +-- Penumbra.GameData/Data/DataSharer.cs | 16 +++ .../Data/ObjectIdentification.cs | 123 ++++++++---------- Penumbra/Mods/Manager/Mod.Manager.Root.cs | 19 ++- 5 files changed, 147 insertions(+), 117 deletions(-) diff --git a/Penumbra.GameData/Actors/ActorManager.Data.cs b/Penumbra.GameData/Actors/ActorManager.Data.cs index 0bcaa792..7889f679 100644 --- a/Penumbra.GameData/Actors/ActorManager.Data.cs +++ b/Penumbra.GameData/Actors/ActorManager.Data.cs @@ -11,7 +11,6 @@ using Dalamud.Game.Gui; using Dalamud.Plugin; using Dalamud.Utility; using Dalamud.Utility.Signatures; -using FFXIVClientStructs.FFXIV.Client.Game.Group; using FFXIVClientStructs.FFXIV.Client.Game.Object; using FFXIVClientStructs.FFXIV.Client.System.Framework; using FFXIVClientStructs.FFXIV.Client.UI.Agent; @@ -51,12 +50,19 @@ public sealed partial class ActorManager : IDisposable public ActorManagerData(DalamudPluginInterface pluginInterface, DataManager gameData, ClientLanguage language) : base(pluginInterface, language, 1) { - Worlds = TryCatchData("Worlds", () => CreateWorldData(gameData)); - Mounts = TryCatchData("Mounts", () => CreateMountData(gameData)); - Companions = TryCatchData("Companions", () => CreateCompanionData(gameData)); - Ornaments = TryCatchData("Ornaments", () => CreateOrnamentData(gameData)); - BNpcs = TryCatchData("BNpcs", () => CreateBNpcData(gameData)); - ENpcs = TryCatchData("ENpcs", () => CreateENpcData(gameData)); + var worldTask = TryCatchDataAsync("Worlds", CreateWorldData(gameData)); + var mountsTask = TryCatchDataAsync("Mounts", CreateMountData(gameData)); + var companionsTask = TryCatchDataAsync("Companions", CreateCompanionData(gameData)); + var ornamentsTask = TryCatchDataAsync("Ornaments", CreateOrnamentData(gameData)); + var bNpcsTask = TryCatchDataAsync("BNpcs", CreateBNpcData(gameData)); + var eNpcsTask = TryCatchDataAsync("ENpcs", CreateENpcData(gameData)); + + Worlds = worldTask.Result; + Mounts = mountsTask.Result; + Companions = companionsTask.Result; + Ornaments = ornamentsTask.Result; + BNpcs = bNpcsTask.Result; + ENpcs = eNpcsTask.Result; } /// @@ -109,40 +115,53 @@ public sealed partial class ActorManager : IDisposable DisposeTag("ENpcs"); } - private IReadOnlyDictionary CreateWorldData(DataManager gameData) - => gameData.GetExcelSheet(Language)! - .Where(w => w.IsPublic && !w.Name.RawData.IsEmpty) - .ToDictionary(w => (ushort)w.RowId, w => w.Name.ToString()); + private Action> CreateWorldData(DataManager gameData) + => d => + { + foreach (var w in gameData.GetExcelSheet(Language)!.Where(w => w.IsPublic && !w.Name.RawData.IsEmpty)) + d.TryAdd((ushort)w.RowId, string.Intern(w.Name.ToDalamudString().TextValue)); + }; - private IReadOnlyDictionary CreateMountData(DataManager gameData) - => gameData.GetExcelSheet(Language)! - .Where(m => m.Singular.RawData.Length > 0 && m.Order >= 0) - .ToDictionary(m => m.RowId, m => ToTitleCaseExtended(m.Singular, m.Article)); + private Action> CreateMountData(DataManager gameData) + => d => + { + foreach (var m in gameData.GetExcelSheet(Language)!.Where(m => m.Singular.RawData.Length > 0 && m.Order >= 0)) + d.TryAdd(m.RowId, ToTitleCaseExtended(m.Singular, m.Article)); + }; - private IReadOnlyDictionary CreateCompanionData(DataManager gameData) - => gameData.GetExcelSheet(Language)! - .Where(c => c.Singular.RawData.Length > 0 && c.Order < ushort.MaxValue) - .ToDictionary(c => c.RowId, c => ToTitleCaseExtended(c.Singular, c.Article)); + private Action> CreateCompanionData(DataManager gameData) + => d => + { + foreach (var c in gameData.GetExcelSheet(Language)!.Where(c + => c.Singular.RawData.Length > 0 && c.Order < ushort.MaxValue)) + d.TryAdd(c.RowId, ToTitleCaseExtended(c.Singular, c.Article)); + }; - private IReadOnlyDictionary CreateOrnamentData(DataManager gameData) - => gameData.GetExcelSheet(Language)! - .Where(o => o.Singular.RawData.Length > 0) - .ToDictionary(o => o.RowId, o => ToTitleCaseExtended(o.Singular, o.Article)); + private Action> CreateOrnamentData(DataManager gameData) + => d => + { + foreach (var o in gameData.GetExcelSheet(Language)!.Where(o => o.Singular.RawData.Length > 0)) + d.TryAdd(o.RowId, ToTitleCaseExtended(o.Singular, o.Article)); + }; - private IReadOnlyDictionary CreateBNpcData(DataManager gameData) - => gameData.GetExcelSheet(Language)! - .Where(n => n.Singular.RawData.Length > 0) - .ToDictionary(n => n.RowId, n => ToTitleCaseExtended(n.Singular, n.Article)); + private Action> CreateBNpcData(DataManager gameData) + => d => + { + foreach (var n in gameData.GetExcelSheet(Language)!.Where(n => n.Singular.RawData.Length > 0)) + d.TryAdd(n.RowId, ToTitleCaseExtended(n.Singular, n.Article)); + }; - private IReadOnlyDictionary CreateENpcData(DataManager gameData) - => gameData.GetExcelSheet(Language)! - .Where(e => e.Singular.RawData.Length > 0) - .ToDictionary(e => e.RowId, e => ToTitleCaseExtended(e.Singular, e.Article)); + private Action> CreateENpcData(DataManager gameData) + => d => + { + foreach (var n in gameData.GetExcelSheet(Language)!.Where(e => e.Singular.RawData.Length > 0)) + d.TryAdd(n.RowId, ToTitleCaseExtended(n.Singular, n.Article)); + }; private static string ToTitleCaseExtended(SeString s, sbyte article) { if (article == 1) - return s.ToDalamudString().ToString(); + return string.Intern(s.ToDalamudString().ToString()); var sb = new StringBuilder(s.ToDalamudString().ToString()); var lastSpace = true; @@ -159,18 +178,20 @@ public sealed partial class ActorManager : IDisposable } } - return sb.ToString(); + return string.Intern(sb.ToString()); } } public readonly ActorManagerData Data; - public ActorManager(DalamudPluginInterface pluginInterface, ObjectTable objects, ClientState state, Dalamud.Game.Framework framework, DataManager gameData, GameGui gameGui, + public ActorManager(DalamudPluginInterface pluginInterface, ObjectTable objects, ClientState state, Dalamud.Game.Framework framework, + DataManager gameData, GameGui gameGui, Func toParentIdx) : this(pluginInterface, objects, state, framework, gameData, gameGui, gameData.Language, toParentIdx) { } - public ActorManager(DalamudPluginInterface pluginInterface, ObjectTable objects, ClientState state, Dalamud.Game.Framework framework, DataManager gameData, GameGui gameGui, + public ActorManager(DalamudPluginInterface pluginInterface, ObjectTable objects, ClientState state, Dalamud.Game.Framework framework, + DataManager gameData, GameGui gameGui, ClientLanguage language, Func toParentIdx) { _framework = framework; diff --git a/Penumbra.GameData/Actors/ActorManager.Identifiers.cs b/Penumbra.GameData/Actors/ActorManager.Identifiers.cs index 500bbc98..15c12714 100644 --- a/Penumbra.GameData/Actors/ActorManager.Identifiers.cs +++ b/Penumbra.GameData/Actors/ActorManager.Identifiers.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; using Dalamud.Game.ClientState.Objects.Enums; using Dalamud.Game.ClientState.Objects.Types; -using Dalamud.Logging; using Newtonsoft.Json.Linq; using Penumbra.String; using Character = FFXIVClientStructs.FFXIV.Client.Game.Character.Character; @@ -124,18 +123,18 @@ public partial class ActorManager if (split.Length < 2) throw new IdentifierParseError($"The identifier string {userString} does not contain a type and a value."); - var type = IdentifierType.Invalid; - var playerName = ByteString.Empty; - ushort worldId = 0; - var kind = ObjectKind.Player; - var objectId = 0u; + IdentifierType type; + var playerName = ByteString.Empty; + ushort worldId = 0; + var kind = ObjectKind.Player; + var objectId = 0u; (ByteString, ushort) ParsePlayer(string player) { var parts = player.Split('@', 2, StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries); if (!VerifyPlayerName(parts[0])) throw new IdentifierParseError($"{parts[0]} is not a valid player name."); - if (!ByteString.FromString(parts[0], out var p, false)) + if (!ByteString.FromString(parts[0], out var p)) throw new IdentifierParseError($"The player string {parts[0]} contains invalid symbols."); var world = parts.Length == 2 @@ -214,7 +213,7 @@ public partial class ActorManager type = IdentifierType.Retainer; if (!VerifyRetainerName(split[1])) throw new IdentifierParseError($"{split[1]} is not a valid player name."); - if (!ByteString.FromString(split[1], out playerName, false)) + if (!ByteString.FromString(split[1], out playerName)) throw new IdentifierParseError($"The retainer string {split[1]} contains invalid symbols."); break; diff --git a/Penumbra.GameData/Data/DataSharer.cs b/Penumbra.GameData/Data/DataSharer.cs index 57b11bea..ce5fc0c3 100644 --- a/Penumbra.GameData/Data/DataSharer.cs +++ b/Penumbra.GameData/Data/DataSharer.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Threading.Tasks; using Dalamud; using Dalamud.Logging; using Dalamud.Plugin; @@ -55,6 +57,20 @@ public abstract class DataSharer : IDisposable } } + protected Task TryCatchDataAsync(string tag, Action fill) where T : class, new() + { + tag = GetVersionedTag(tag, Language, Version); + if (PluginInterface.TryGetData(tag, out var data)) + return Task.FromResult(data); + + T ret = new(); + return Task.Run(() => + { + fill(ret); + return ret; + }); + } + public static void DisposeTag(DalamudPluginInterface pi, string tag, ClientLanguage language, int version) => pi.RelinquishData(GetVersionedTag(tag, language, version)); diff --git a/Penumbra.GameData/Data/ObjectIdentification.cs b/Penumbra.GameData/Data/ObjectIdentification.cs index 9a45dcfc..53c71730 100644 --- a/Penumbra.GameData/Data/ObjectIdentification.cs +++ b/Penumbra.GameData/Data/ObjectIdentification.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using Dalamud; using Dalamud.Data; using Lumina.Excel.GeneratedSheets; @@ -7,13 +8,15 @@ using Penumbra.GameData.Structs; using System.Collections.Generic; using System.ComponentModel; using System.Linq; +using System.Threading.Tasks; using Dalamud.Game.ClientState.Objects.Enums; using Dalamud.Plugin; using Dalamud.Utility; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using Penumbra.GameData.Actors; using Action = Lumina.Excel.GeneratedSheets.Action; -using ObjectType = Penumbra.GameData.Enums.ObjectType; +using ObjectType = Penumbra.GameData.Enums.ObjectType; + namespace Penumbra.GameData.Data; internal sealed class ObjectIdentification : DataSharer, IObjectIdentifier @@ -38,7 +41,6 @@ internal sealed class ObjectIdentification : DataSharer, IObjectIdentifier _equipment = new EquipmentIdentificationList(pluginInterface, language, dataManager); _weapons = new WeaponIdentificationList(pluginInterface, language, dataManager); Actions = TryCatchData("Actions", () => CreateActionList(dataManager)); - _equipment = new EquipmentIdentificationList(pluginInterface, language, dataManager); _modelIdentifierToModelChara = new ModelIdentificationList(pluginInterface, language, dataManager); BnpcNames = TryCatchData("BNpcNames", NpcNames.CreateNames); @@ -86,19 +88,10 @@ internal sealed class ObjectIdentification : DataSharer, IObjectIdentifier DisposeTag("ModelObjects"); } - private static bool Add(IDictionary> dict, ulong key, Item item) - { - if (dict.TryGetValue(key, out var list)) - return list.Add(item); - - dict[key] = new HashSet { item }; - return true; - } - private IReadOnlyDictionary> CreateActionList(DataManager gameData) { var sheet = gameData.GetExcelSheet(Language)!; - var storage = new Dictionary>((int)sheet.RowCount); + var storage = new ConcurrentDictionary>(); void AddAction(string? key, Action action) { @@ -109,10 +102,15 @@ internal sealed class ObjectIdentification : DataSharer, IObjectIdentifier if (storage.TryGetValue(key, out var actions)) actions.Add(action); else - storage[key] = new HashSet { action }; + storage[key] = new ConcurrentBag { action }; } - foreach (var action in sheet.Where(a => !a.Name.RawData.IsEmpty)) + var options = new ParallelOptions + { + MaxDegreeOfParallelism = Environment.ProcessorCount, + }; + + Parallel.ForEach(sheet.Where(a => !a.Name.RawData.IsEmpty), options, action => { var startKey = action.AnimationStart?.Value?.Name?.Value?.Key.ToDalamudString().ToString(); var endKey = action.AnimationEnd?.Value?.Key.ToDalamudString().ToString(); @@ -120,39 +118,11 @@ internal sealed class ObjectIdentification : DataSharer, IObjectIdentifier AddAction(startKey, action); AddAction(endKey, action); AddAction(hitKey, action); - } + }); return storage.ToDictionary(kvp => kvp.Key, kvp => (IReadOnlyList)kvp.Value.ToArray()); } - private class Comparer : IComparer<(ulong, IReadOnlyList)> - { - public int Compare((ulong, IReadOnlyList) x, (ulong, IReadOnlyList) y) - => x.Item1.CompareTo(y.Item1); - } - - private static readonly Comparer _arrayComparer = new(); - - - private static (int, int) FindIndexRange(List<(ulong, IReadOnlyList)> list, ulong key, ulong mask) - { - var maskedKey = key & mask; - var idx = list.BinarySearch(0, list.Count, (key, null!), _arrayComparer); - if (idx < 0) - { - if (~idx == list.Count || maskedKey != (list[~idx].Item1 & mask)) - return (-1, -1); - - idx = ~idx; - } - - var endIdx = idx + 1; - while (endIdx < list.Count && maskedKey == (list[endIdx].Item1 & mask)) - ++endIdx; - - return (idx, endIdx); - } - private void FindEquipment(IDictionary set, GameObjectInfo info) { var items = _equipment.Between(info.PrimaryId, info.EquipSlot, info.Variant); @@ -282,21 +252,15 @@ internal sealed class ObjectIdentification : DataSharer, IObjectIdentifier } private IReadOnlyList> CreateModelObjects(ActorManager.ActorManagerData actors, - DataManager gameData, - ClientLanguage language) + DataManager gameData, ClientLanguage language) { - var modelSheet = gameData.GetExcelSheet(language)!; - var bnpcSheet = gameData.GetExcelSheet(language)!; - var enpcSheet = gameData.GetExcelSheet(language)!; - var ornamentSheet = gameData.GetExcelSheet(language)!; - var mountSheet = gameData.GetExcelSheet(language)!; - var companionSheet = gameData.GetExcelSheet(language)!; - var ret = new List>((int)modelSheet.RowCount); + var modelSheet = gameData.GetExcelSheet(language)!; + var ret = new List>((int)modelSheet.RowCount); for (var i = -1; i < modelSheet.Last().RowId; ++i) - ret.Add(new HashSet<(string Name, ObjectKind Kind)>()); + ret.Add(new ConcurrentBag<(string Name, ObjectKind Kind)>()); - void Add(int modelChara, ObjectKind kind, uint dataId) + void AddChara(int modelChara, ObjectKind kind, uint dataId) { if (modelChara == 0 || modelChara >= ret.Count) return; @@ -305,23 +269,42 @@ internal sealed class ObjectIdentification : DataSharer, IObjectIdentifier ret[modelChara].Add((name, kind)); } - foreach (var ornament in ornamentSheet) - Add(ornament.Model, (ObjectKind)15, ornament.RowId); - - foreach (var mount in mountSheet) - Add((int)mount.ModelChara.Row, ObjectKind.MountType, mount.RowId); - - foreach (var companion in companionSheet) - Add((int)companion.Model.Row, ObjectKind.Companion, companion.RowId); - - foreach (var enpc in enpcSheet) - Add((int)enpc.ModelChara.Row, ObjectKind.EventNpc, enpc.RowId); - - foreach (var bnpc in bnpcSheet.Where(b => b.RowId < BnpcNames.Count)) + var oTask = Task.Run(() => { - foreach (var name in BnpcNames[(int)bnpc.RowId]) - Add((int)bnpc.ModelChara.Row, ObjectKind.BattleNpc, name); - } + foreach (var ornament in gameData.GetExcelSheet(language)!) + AddChara(ornament.Model, (ObjectKind)15, ornament.RowId); + }); + + var mTask = Task.Run(() => + { + foreach (var mount in gameData.GetExcelSheet(language)!) + AddChara((int)mount.ModelChara.Row, ObjectKind.MountType, mount.RowId); + }); + + var cTask = Task.Run(() => + { + foreach (var companion in gameData.GetExcelSheet(language)!) + AddChara((int)companion.Model.Row, ObjectKind.Companion, companion.RowId); + }); + + var eTask = Task.Run(() => + { + foreach (var eNpc in gameData.GetExcelSheet(language)!) + AddChara((int)eNpc.ModelChara.Row, ObjectKind.EventNpc, eNpc.RowId); + }); + + var options = new ParallelOptions() + { + MaxDegreeOfParallelism = Environment.ProcessorCount / 2, + }; + + Parallel.ForEach(gameData.GetExcelSheet(language)!.Where(b => b.RowId < BnpcNames.Count), options, bNpc => + { + foreach (var name in BnpcNames[(int)bNpc.RowId]) + AddChara((int)bNpc.ModelChara.Row, ObjectKind.BattleNpc, name); + }); + + Task.WaitAll(oTask, mTask, cTask, eTask); return ret.Select(s => s.Count > 0 ? s.ToArray() diff --git a/Penumbra/Mods/Manager/Mod.Manager.Root.cs b/Penumbra/Mods/Manager/Mod.Manager.Root.cs index a82fb88e..dace9f57 100644 --- a/Penumbra/Mods/Manager/Mod.Manager.Root.cs +++ b/Penumbra/Mods/Manager/Mod.Manager.Root.cs @@ -1,5 +1,8 @@ +using Penumbra.UI.Classes; using System; +using System.Collections.Concurrent; using System.IO; +using System.Threading.Tasks; namespace Penumbra.Mods; @@ -87,14 +90,22 @@ public sealed partial class Mod if( Valid && BasePath.Exists ) { - foreach( var modFolder in BasePath.EnumerateDirectories() ) + var options = new ParallelOptions() { - var mod = LoadMod( modFolder, false ); - if( mod == null ) + MaxDegreeOfParallelism = Environment.ProcessorCount / 2, + }; + var queue = new ConcurrentQueue< Mod >(); + Parallel.ForEach( BasePath.EnumerateDirectories(), options, dir => + { + var mod = LoadMod( dir, false ); + if( mod != null ) { - continue; + queue.Enqueue( mod ); } + } ); + foreach( var mod in queue ) + { mod.Index = _mods.Count; _mods.Add( mod ); }