Skip to content

Commit

Permalink
Try to improve launch times somewhat.
Browse files Browse the repository at this point in the history
  • Loading branch information
Ottermandias committed Feb 4, 2023
1 parent 98bc148 commit f29bdee
Show file tree
Hide file tree
Showing 5 changed files with 143 additions and 113 deletions.
91 changes: 56 additions & 35 deletions Penumbra.GameData/Actors/ActorManager.Data.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
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;
Expand Down Expand Up @@ -51,12 +50,19 @@ public sealed class ActorManagerData : DataSharer
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;
}

/// <summary>
Expand Down Expand Up @@ -109,40 +115,53 @@ protected override void DisposeInternal()
DisposeTag("ENpcs");
}

private IReadOnlyDictionary<ushort, string> CreateWorldData(DataManager gameData)
=> gameData.GetExcelSheet<World>(Language)!
.Where(w => w.IsPublic && !w.Name.RawData.IsEmpty)
.ToDictionary(w => (ushort)w.RowId, w => w.Name.ToString());
private Action<Dictionary<ushort, string>> CreateWorldData(DataManager gameData)
=> d =>
{
foreach (var w in gameData.GetExcelSheet<World>(Language)!.Where(w => w.IsPublic && !w.Name.RawData.IsEmpty))
d.TryAdd((ushort)w.RowId, string.Intern(w.Name.ToDalamudString().TextValue));
};

private IReadOnlyDictionary<uint, string> CreateMountData(DataManager gameData)
=> gameData.GetExcelSheet<Mount>(Language)!
.Where(m => m.Singular.RawData.Length > 0 && m.Order >= 0)
.ToDictionary(m => m.RowId, m => ToTitleCaseExtended(m.Singular, m.Article));
private Action<Dictionary<uint, string>> CreateMountData(DataManager gameData)
=> d =>
{
foreach (var m in gameData.GetExcelSheet<Mount>(Language)!.Where(m => m.Singular.RawData.Length > 0 && m.Order >= 0))
d.TryAdd(m.RowId, ToTitleCaseExtended(m.Singular, m.Article));
};

private IReadOnlyDictionary<uint, string> CreateCompanionData(DataManager gameData)
=> gameData.GetExcelSheet<Companion>(Language)!
.Where(c => c.Singular.RawData.Length > 0 && c.Order < ushort.MaxValue)
.ToDictionary(c => c.RowId, c => ToTitleCaseExtended(c.Singular, c.Article));
private Action<Dictionary<uint, string>> CreateCompanionData(DataManager gameData)
=> d =>
{
foreach (var c in gameData.GetExcelSheet<Companion>(Language)!.Where(c
=> c.Singular.RawData.Length > 0 && c.Order < ushort.MaxValue))
d.TryAdd(c.RowId, ToTitleCaseExtended(c.Singular, c.Article));
};

private IReadOnlyDictionary<uint, string> CreateOrnamentData(DataManager gameData)
=> gameData.GetExcelSheet<Ornament>(Language)!
.Where(o => o.Singular.RawData.Length > 0)
.ToDictionary(o => o.RowId, o => ToTitleCaseExtended(o.Singular, o.Article));
private Action<Dictionary<uint, string>> CreateOrnamentData(DataManager gameData)
=> d =>
{
foreach (var o in gameData.GetExcelSheet<Ornament>(Language)!.Where(o => o.Singular.RawData.Length > 0))
d.TryAdd(o.RowId, ToTitleCaseExtended(o.Singular, o.Article));
};

private IReadOnlyDictionary<uint, string> CreateBNpcData(DataManager gameData)
=> gameData.GetExcelSheet<BNpcName>(Language)!
.Where(n => n.Singular.RawData.Length > 0)
.ToDictionary(n => n.RowId, n => ToTitleCaseExtended(n.Singular, n.Article));
private Action<Dictionary<uint, string>> CreateBNpcData(DataManager gameData)
=> d =>
{
foreach (var n in gameData.GetExcelSheet<BNpcName>(Language)!.Where(n => n.Singular.RawData.Length > 0))
d.TryAdd(n.RowId, ToTitleCaseExtended(n.Singular, n.Article));
};

private IReadOnlyDictionary<uint, string> CreateENpcData(DataManager gameData)
=> gameData.GetExcelSheet<ENpcResident>(Language)!
.Where(e => e.Singular.RawData.Length > 0)
.ToDictionary(e => e.RowId, e => ToTitleCaseExtended(e.Singular, e.Article));
private Action<Dictionary<uint, string>> CreateENpcData(DataManager gameData)
=> d =>
{
foreach (var n in gameData.GetExcelSheet<ENpcResident>(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;
Expand All @@ -159,18 +178,20 @@ private static string ToTitleCaseExtended(SeString s, sbyte article)
}
}

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<ushort, short> 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<ushort, short> toParentIdx)
{
_framework = framework;
Expand Down
15 changes: 7 additions & 8 deletions Penumbra.GameData/Actors/ActorManager.Identifiers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
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;
Expand Down Expand Up @@ -124,18 +123,18 @@ public ActorIdentifier FromUserString(string userString)
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
Expand Down Expand Up @@ -214,7 +213,7 @@ static bool FindDataId(string name, IReadOnlyDictionary<uint, string> data, out
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;
Expand Down
16 changes: 16 additions & 0 deletions Penumbra.GameData/Data/DataSharer.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Dalamud;
using Dalamud.Logging;
using Dalamud.Plugin;
Expand Down Expand Up @@ -55,6 +57,20 @@ protected T TryCatchData<T>(string tag, Func<T> func) where T : class
}
}

protected Task<T> TryCatchDataAsync<T>(string tag, Action<T> fill) where T : class, new()
{
tag = GetVersionedTag(tag, Language, Version);
if (PluginInterface.TryGetData<T>(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));

Expand Down
115 changes: 49 additions & 66 deletions Penumbra.GameData/Data/ObjectIdentification.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Concurrent;
using Dalamud;
using Dalamud.Data;
using Lumina.Excel.GeneratedSheets;
Expand All @@ -7,13 +8,15 @@
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
Expand All @@ -38,7 +41,6 @@ public ObjectIdentification(DalamudPluginInterface pluginInterface, DataManager
_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);
Expand Down Expand Up @@ -86,19 +88,10 @@ protected override void DisposeInternal()
DisposeTag("ModelObjects");
}

private static bool Add(IDictionary<ulong, HashSet<Item>> dict, ulong key, Item item)
{
if (dict.TryGetValue(key, out var list))
return list.Add(item);

dict[key] = new HashSet<Item> { item };
return true;
}

private IReadOnlyDictionary<string, IReadOnlyList<Action>> CreateActionList(DataManager gameData)
{
var sheet = gameData.GetExcelSheet<Action>(Language)!;
var storage = new Dictionary<string, HashSet<Action>>((int)sheet.RowCount);
var storage = new ConcurrentDictionary<string, ConcurrentBag<Action>>();

void AddAction(string? key, Action action)
{
Expand All @@ -109,50 +102,27 @@ void AddAction(string? key, Action action)
if (storage.TryGetValue(key, out var actions))
actions.Add(action);
else
storage[key] = new HashSet<Action> { action };
storage[key] = new ConcurrentBag<Action> { 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();
var hitKey = action.ActionTimelineHit?.Value?.Key.ToDalamudString().ToString();
AddAction(startKey, action);
AddAction(endKey, action);
AddAction(hitKey, action);
}
});

return storage.ToDictionary(kvp => kvp.Key, kvp => (IReadOnlyList<Action>)kvp.Value.ToArray());
}

private class Comparer : IComparer<(ulong, IReadOnlyList<Item>)>
{
public int Compare((ulong, IReadOnlyList<Item>) x, (ulong, IReadOnlyList<Item>) y)
=> x.Item1.CompareTo(y.Item1);
}

private static readonly Comparer _arrayComparer = new();


private static (int, int) FindIndexRange(List<(ulong, IReadOnlyList<Item>)> 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<string, object?> set, GameObjectInfo info)
{
var items = _equipment.Between(info.PrimaryId, info.EquipSlot, info.Variant);
Expand Down Expand Up @@ -282,21 +252,15 @@ private void IdentifyVfx(IDictionary<string, object?> set, string path)
}

private IReadOnlyList<IReadOnlyList<(string Name, ObjectKind Kind)>> CreateModelObjects(ActorManager.ActorManagerData actors,
DataManager gameData,
ClientLanguage language)
DataManager gameData, ClientLanguage language)
{
var modelSheet = gameData.GetExcelSheet<ModelChara>(language)!;
var bnpcSheet = gameData.GetExcelSheet<BNpcBase>(language)!;
var enpcSheet = gameData.GetExcelSheet<ENpcBase>(language)!;
var ornamentSheet = gameData.GetExcelSheet<Ornament>(language)!;
var mountSheet = gameData.GetExcelSheet<Mount>(language)!;
var companionSheet = gameData.GetExcelSheet<Companion>(language)!;
var ret = new List<HashSet<(string Name, ObjectKind Kind)>>((int)modelSheet.RowCount);
var modelSheet = gameData.GetExcelSheet<ModelChara>(language)!;
var ret = new List<ConcurrentBag<(string Name, ObjectKind Kind)>>((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;
Expand All @@ -305,23 +269,42 @@ void Add(int modelChara, ObjectKind kind, uint dataId)
ret[modelChara].Add((name, kind));
}

foreach (var ornament in ornamentSheet)
Add(ornament.Model, (ObjectKind)15, ornament.RowId);
var oTask = Task.Run(() =>
{
foreach (var ornament in gameData.GetExcelSheet<Ornament>(language)!)
AddChara(ornament.Model, (ObjectKind)15, ornament.RowId);
});

var mTask = Task.Run(() =>
{
foreach (var mount in gameData.GetExcelSheet<Mount>(language)!)
AddChara((int)mount.ModelChara.Row, ObjectKind.MountType, mount.RowId);
});

foreach (var mount in mountSheet)
Add((int)mount.ModelChara.Row, ObjectKind.MountType, mount.RowId);
var cTask = Task.Run(() =>
{
foreach (var companion in gameData.GetExcelSheet<Companion>(language)!)
AddChara((int)companion.Model.Row, ObjectKind.Companion, companion.RowId);
});

foreach (var companion in companionSheet)
Add((int)companion.Model.Row, ObjectKind.Companion, companion.RowId);
var eTask = Task.Run(() =>
{
foreach (var eNpc in gameData.GetExcelSheet<ENpcBase>(language)!)
AddChara((int)eNpc.ModelChara.Row, ObjectKind.EventNpc, eNpc.RowId);
});

foreach (var enpc in enpcSheet)
Add((int)enpc.ModelChara.Row, ObjectKind.EventNpc, enpc.RowId);
var options = new ParallelOptions()
{
MaxDegreeOfParallelism = Environment.ProcessorCount / 2,
};

foreach (var bnpc in bnpcSheet.Where(b => b.RowId < BnpcNames.Count))
Parallel.ForEach(gameData.GetExcelSheet<BNpcBase>(language)!.Where(b => b.RowId < BnpcNames.Count), options, bNpc =>
{
foreach (var name in BnpcNames[(int)bnpc.RowId])
Add((int)bnpc.ModelChara.Row, ObjectKind.BattleNpc, name);
}
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()
Expand Down
Loading

0 comments on commit f29bdee

Please sign in to comment.