mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-12 18:27:23 +01:00
Fix async plugin load and show full profiler (#898)
This commit is contained in:
parent
86b5eec493
commit
c7dc8c81f4
20 changed files with 404 additions and 248 deletions
|
|
@ -1,7 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Reflection;
|
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
@ -9,24 +7,15 @@ using Dalamud.Configuration.Internal;
|
||||||
using Dalamud.Data;
|
using Dalamud.Data;
|
||||||
using Dalamud.Game;
|
using Dalamud.Game;
|
||||||
using Dalamud.Game.ClientState;
|
using Dalamud.Game.ClientState;
|
||||||
using Dalamud.Game.Command;
|
|
||||||
using Dalamud.Game.Gui;
|
|
||||||
using Dalamud.Game.Gui.Internal;
|
using Dalamud.Game.Gui.Internal;
|
||||||
using Dalamud.Game.Internal;
|
using Dalamud.Game.Internal;
|
||||||
using Dalamud.Game.Network;
|
|
||||||
using Dalamud.Game.Network.Internal;
|
using Dalamud.Game.Network.Internal;
|
||||||
using Dalamud.Game.Text.SeStringHandling;
|
|
||||||
using Dalamud.Hooking.Internal;
|
using Dalamud.Hooking.Internal;
|
||||||
using Dalamud.Interface;
|
|
||||||
using Dalamud.Interface.GameFonts;
|
|
||||||
using Dalamud.Interface.Internal;
|
using Dalamud.Interface.Internal;
|
||||||
using Dalamud.IoC.Internal;
|
|
||||||
using Dalamud.Logging.Internal;
|
using Dalamud.Logging.Internal;
|
||||||
using Dalamud.Plugin.Internal;
|
using Dalamud.Plugin.Internal;
|
||||||
using Dalamud.Plugin.Ipc.Internal;
|
|
||||||
using Dalamud.Support;
|
using Dalamud.Support;
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
using Dalamud.Utility.Timing;
|
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using Serilog.Core;
|
using Serilog.Core;
|
||||||
using Serilog.Events;
|
using Serilog.Events;
|
||||||
|
|
@ -74,9 +63,7 @@ namespace Dalamud
|
||||||
|
|
||||||
SerilogEventSink.Instance.LogLine += SerilogOnLogLine;
|
SerilogEventSink.Instance.LogLine += SerilogOnLogLine;
|
||||||
|
|
||||||
Service<Dalamud>.Provide(this);
|
ServiceManager.InitializeProvidedServicesAndClientStructs(this, info, configuration);
|
||||||
Service<DalamudStartInfo>.Provide(info);
|
|
||||||
Service<DalamudConfiguration>.Provide(configuration);
|
|
||||||
|
|
||||||
if (!configuration.IsResumeGameAfterPluginLoad)
|
if (!configuration.IsResumeGameAfterPluginLoad)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -26,9 +26,9 @@ namespace Dalamud.Game.Gui.Dtr
|
||||||
private uint runningNodeIds = BaseNodeId;
|
private uint runningNodeIds = BaseNodeId;
|
||||||
|
|
||||||
[ServiceManager.ServiceConstructor]
|
[ServiceManager.ServiceConstructor]
|
||||||
private DtrBar(DalamudConfiguration configuration)
|
private DtrBar(DalamudConfiguration configuration, Framework framework)
|
||||||
{
|
{
|
||||||
Service<Framework>.Get().Update += this.Update;
|
framework.Update += this.Update;
|
||||||
|
|
||||||
configuration.DtrOrder ??= new List<string>();
|
configuration.DtrOrder ??= new List<string>();
|
||||||
configuration.DtrIgnore ??= new List<string>();
|
configuration.DtrIgnore ??= new List<string>();
|
||||||
|
|
|
||||||
|
|
@ -423,13 +423,13 @@ namespace Dalamud.Game.Gui
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Enable()
|
public void Enable()
|
||||||
{
|
{
|
||||||
Service<ChatGui>.Get().Enable();
|
Service<ChatGui>.GetAsync().ContinueWith(x => x.Result.Enable());
|
||||||
Service<ToastGui>.Get().Enable();
|
Service<ToastGui>.GetAsync().ContinueWith(x => x.Result.Enable());
|
||||||
Service<FlyTextGui>.Get().Enable();
|
Service<FlyTextGui>.GetAsync().ContinueWith(x => x.Result.Enable());
|
||||||
Service<PartyFinderGui>.Get().Enable();
|
Service<PartyFinderGui>.GetAsync().ContinueWith(x => x.Result.Enable());
|
||||||
|
|
||||||
if (EnvironmentConfiguration.DalamudDoContextMenu)
|
if (EnvironmentConfiguration.DalamudDoContextMenu)
|
||||||
Service<ContextMenu>.Get().Enable();
|
Service<ContextMenu>.GetAsync().ContinueWith(x => x.Result.Enable());
|
||||||
|
|
||||||
this.setGlobalBgmHook.Enable();
|
this.setGlobalBgmHook.Enable();
|
||||||
this.handleItemHoverHook.Enable();
|
this.handleItemHoverHook.Enable();
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ using System.Runtime.InteropServices;
|
||||||
|
|
||||||
using Dalamud.IoC;
|
using Dalamud.IoC;
|
||||||
using Dalamud.IoC.Internal;
|
using Dalamud.IoC.Internal;
|
||||||
using Dalamud.Utility.Timing;
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
|
|
@ -20,7 +19,6 @@ namespace Dalamud.Game
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[PluginInterface]
|
[PluginInterface]
|
||||||
[InterfaceVersion("1.0")]
|
[InterfaceVersion("1.0")]
|
||||||
[ServiceManager.BlockingEarlyLoadedService]
|
|
||||||
public class SigScanner : IDisposable
|
public class SigScanner : IDisposable
|
||||||
{
|
{
|
||||||
private readonly FileInfo? cacheFile;
|
private readonly FileInfo? cacheFile;
|
||||||
|
|
@ -30,38 +28,6 @@ namespace Dalamud.Game
|
||||||
|
|
||||||
private ConcurrentDictionary<string, long>? textCache;
|
private ConcurrentDictionary<string, long>? textCache;
|
||||||
|
|
||||||
[ServiceManager.ServiceConstructor]
|
|
||||||
private SigScanner(DalamudStartInfo startInfo)
|
|
||||||
{
|
|
||||||
// Initialize the process information.
|
|
||||||
var cacheDir = new DirectoryInfo(Path.Combine(startInfo.WorkingDirectory!, "cachedSigs"));
|
|
||||||
if (!cacheDir.Exists)
|
|
||||||
cacheDir.Create();
|
|
||||||
|
|
||||||
this.cacheFile = new FileInfo(Path.Combine(cacheDir.FullName, $"{startInfo.GameVersion}.json"));
|
|
||||||
this.Module = Process.GetCurrentProcess().MainModule!;
|
|
||||||
this.Is32BitProcess = !Environment.Is64BitProcess;
|
|
||||||
this.IsCopy = true;
|
|
||||||
|
|
||||||
// Limit the search space to .text section.
|
|
||||||
this.SetupSearchSpace(this.Module);
|
|
||||||
|
|
||||||
if (this.IsCopy)
|
|
||||||
this.SetupCopiedSegments();
|
|
||||||
|
|
||||||
Log.Verbose($"Module base: 0x{this.TextSectionBase.ToInt64():X}");
|
|
||||||
Log.Verbose($"Module size: 0x{this.TextSectionSize:X}");
|
|
||||||
|
|
||||||
if (this.cacheFile != null)
|
|
||||||
this.Load();
|
|
||||||
|
|
||||||
// Initialize FFXIVClientStructs function resolver
|
|
||||||
using (Timings.Start("CS Resolver Init"))
|
|
||||||
{
|
|
||||||
FFXIVClientStructs.Resolver.InitializeParallel(new FileInfo(Path.Combine(cacheDir.FullName, $"{startInfo.GameVersion}_cs.json")));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="SigScanner"/> class using the main module of the current process.
|
/// Initializes a new instance of the <see cref="SigScanner"/> class using the main module of the current process.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -31,12 +31,12 @@ namespace Dalamud.Hooking.Internal
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a static dictionary of original code for a hooked address.
|
/// Gets a static dictionary of original code for a hooked address.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static Dictionary<IntPtr, byte[]> Originals { get; } = new();
|
internal static ConcurrentDictionary<IntPtr, byte[]> Originals { get; } = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a static dictionary of the number of hooks on a given address.
|
/// Gets a static dictionary of the number of hooks on a given address.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static Dictionary<IntPtr, List<IDalamudHook?>> MultiHookTracker { get; } = new();
|
internal static ConcurrentDictionary<IntPtr, List<IDalamudHook?>> MultiHookTracker { get; } = new();
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ namespace Dalamud.Interface.GameFonts
|
||||||
.Select(x => x.Glyphs.Select(y => y.TextureFileIndex).Max())
|
.Select(x => x.Glyphs.Select(y => y.TextureFileIndex).Max())
|
||||||
.Max())
|
.Max())
|
||||||
.Select(x => dataManager.GetFile<TexFile>($"common/font/font{x}.tex")!)
|
.Select(x => dataManager.GetFile<TexFile>($"common/font/font{x}.tex")!)
|
||||||
.Select(x => new Task<byte[]>(() => x.ImageData!))
|
.Select(x => new Task<byte[]>(Timings.AttachTimingHandle(() => x.ImageData!)))
|
||||||
.ToArray();
|
.ToArray();
|
||||||
foreach (var task in texTasks)
|
foreach (var task in texTasks)
|
||||||
task.Start();
|
task.Start();
|
||||||
|
|
|
||||||
|
|
@ -166,7 +166,7 @@ namespace Dalamud.Interface.Internal
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Service<PluginManager>.Get().ReloadAllPlugins();
|
Service<PluginManager>.Get().ReloadAllPluginsAsync().Wait();
|
||||||
chatGui.Print("OK");
|
chatGui.Print("OK");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ namespace Dalamud.Interface.Internal
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This plugin implements all of the Dalamud interface separately, to allow for reloading of the interface and rapid prototyping.
|
/// This plugin implements all of the Dalamud interface separately, to allow for reloading of the interface and rapid prototyping.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ServiceManager.EarlyLoadedService]
|
[ServiceManager.AfterDrawingEarlyLoadedService]
|
||||||
internal class DalamudInterface : IDisposable
|
internal class DalamudInterface : IDisposable
|
||||||
{
|
{
|
||||||
private static readonly ModuleLog Log = new("DUI");
|
private static readonly ModuleLog Log = new("DUI");
|
||||||
|
|
@ -714,7 +714,7 @@ namespace Dalamud.Interface.Internal
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
pluginManager.ReloadAllPlugins();
|
pluginManager.ReloadAllPluginsAsync().Wait();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -60,8 +60,6 @@ namespace Dalamud.Interface.Internal.Windows
|
||||||
var dalamud = Service<Dalamud>.Get();
|
var dalamud = Service<Dalamud>.Get();
|
||||||
var interfaceManager = Service<InterfaceManager>.Get();
|
var interfaceManager = Service<InterfaceManager>.Get();
|
||||||
|
|
||||||
interfaceManager.SceneInitializeTask.Wait();
|
|
||||||
|
|
||||||
this.DefaultIcon = interfaceManager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "defaultIcon.png"))!;
|
this.DefaultIcon = interfaceManager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "defaultIcon.png"))!;
|
||||||
this.TroubleIcon = interfaceManager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "troubleIcon.png"))!;
|
this.TroubleIcon = interfaceManager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "troubleIcon.png"))!;
|
||||||
this.UpdateIcon = interfaceManager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "updateIcon.png"))!;
|
this.UpdateIcon = interfaceManager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "updateIcon.png"))!;
|
||||||
|
|
|
||||||
|
|
@ -1735,7 +1735,7 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller
|
||||||
if (!enableTask.Result)
|
if (!enableTask.Result)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var loadTask = Task.Run(() => plugin.Load(PluginLoadReason.Installer))
|
var loadTask = Task.Run(() => plugin.LoadAsync(PluginLoadReason.Installer))
|
||||||
.ContinueWith(this.DisplayErrorContinuation, Locs.ErrorModal_LoadFail(plugin.Name));
|
.ContinueWith(this.DisplayErrorContinuation, Locs.ErrorModal_LoadFail(plugin.Name));
|
||||||
|
|
||||||
loadTask.Wait();
|
loadTask.Wait();
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using Dalamud.Interface.Colors;
|
using Dalamud.Interface.Colors;
|
||||||
|
|
@ -13,25 +14,35 @@ public class ProfilerWindow : Window
|
||||||
{
|
{
|
||||||
private double min;
|
private double min;
|
||||||
private double max;
|
private double max;
|
||||||
|
private List<List<Tuple<double, double>>> occupied = new();
|
||||||
|
|
||||||
public ProfilerWindow() : base("Profiler", forceMainWindow: true) { }
|
public ProfilerWindow() : base("Profiler", forceMainWindow: true) { }
|
||||||
|
|
||||||
public override void OnOpen()
|
public override void OnOpen()
|
||||||
{
|
{
|
||||||
this.min = Timings.AllTimings.Min(x => x.StartTime);
|
this.min = Timings.AllTimings.Keys.Min(x => x.StartTime);
|
||||||
this.max = Timings.AllTimings.Max(x => x.EndTime);
|
this.max = Timings.AllTimings.Keys.Max(x => x.EndTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class RectInfo
|
||||||
|
{
|
||||||
|
internal TimingHandle Timing;
|
||||||
|
internal Vector2 MinPos;
|
||||||
|
internal Vector2 MaxPos;
|
||||||
|
internal Vector4 RectColor;
|
||||||
|
internal bool Hover;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override void Draw()
|
public override void Draw()
|
||||||
{
|
{
|
||||||
var width = ImGui.GetWindowWidth();
|
var width = ImGui.GetWindowWidth();
|
||||||
var actualMin = Timings.AllTimings.Min(x => x.StartTime);
|
var actualMin = Timings.AllTimings.Keys.Min(x => x.StartTime);
|
||||||
var actualMax = Timings.AllTimings.Max(x => x.EndTime);
|
var actualMax = Timings.AllTimings.Keys.Max(x => x.EndTime);
|
||||||
|
|
||||||
ImGui.Text("Timings");
|
ImGui.Text("Timings");
|
||||||
|
|
||||||
const int childHeight = 300;
|
var childHeight = Math.Max(300, 20 * (2 + this.occupied.Count));
|
||||||
|
|
||||||
if (ImGui.BeginChild("Timings", new Vector2(0, childHeight), true))
|
if (ImGui.BeginChild("Timings", new Vector2(0, childHeight), true))
|
||||||
{
|
{
|
||||||
|
|
@ -68,48 +79,113 @@ public class ProfilerWindow : Window
|
||||||
|
|
||||||
uint maxRectDept = 0;
|
uint maxRectDept = 0;
|
||||||
|
|
||||||
foreach (var timingHandle in Timings.AllTimings)
|
foreach (var l in this.occupied)
|
||||||
|
l.Clear();
|
||||||
|
|
||||||
|
var parentDepthDict = new Dictionary<long, int>();
|
||||||
|
var rects = new Dictionary<long, RectInfo>();
|
||||||
|
var mousePos = ImGui.GetMousePos();
|
||||||
|
foreach (var timingHandle in Timings.AllTimings.Keys)
|
||||||
{
|
{
|
||||||
var startX = (timingHandle.StartTime - this.min) / (this.max - this.min) * width;
|
var startX = (timingHandle.StartTime - this.min) / (this.max - this.min) * width;
|
||||||
var endX = (timingHandle.EndTime - this.min) / (this.max - this.min) * width;
|
var endX = (timingHandle.EndTime - this.min) / (this.max - this.min) * width;
|
||||||
|
var depth = timingHandle.IdChain.Length < 2 ? 0 : parentDepthDict.GetValueOrDefault(timingHandle.IdChain[^2]);
|
||||||
|
for (; depth < this.occupied.Count; depth++)
|
||||||
|
{
|
||||||
|
var acceptable = true;
|
||||||
|
foreach (var (x1, x2) in this.occupied[depth])
|
||||||
|
{
|
||||||
|
if (x2 <= timingHandle.StartTime || x1 >= timingHandle.EndTime)
|
||||||
|
continue;
|
||||||
|
acceptable = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (acceptable)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (depth == this.occupied.Count)
|
||||||
|
this.occupied.Add(new());
|
||||||
|
this.occupied[depth].Add(Tuple.Create(timingHandle.StartTime, timingHandle.EndTime));
|
||||||
|
parentDepthDict[timingHandle.Id] = depth;
|
||||||
|
|
||||||
startX = Math.Max(startX, 0);
|
startX = Math.Max(startX, 0);
|
||||||
endX = Math.Max(endX, 0);
|
endX = Math.Max(endX, 0);
|
||||||
|
|
||||||
var rectColor = timingHandle.IsMainThread ? ImGuiColors.ParsedBlue : ImGuiColors.ParsedPurple;
|
Vector4 rectColor;
|
||||||
rectColor.X -= timingHandle.Depth * 0.12f;
|
if (this.occupied[depth].Count % 2 == 0)
|
||||||
rectColor.Y -= timingHandle.Depth * 0.12f;
|
rectColor = timingHandle.IsMainThread ? ImGuiColors.ParsedBlue : ImGuiColors.ParsedOrange;
|
||||||
rectColor.Z -= timingHandle.Depth * 0.12f;
|
else
|
||||||
|
rectColor = timingHandle.IsMainThread ? ImGuiColors.ParsedPurple : ImGuiColors.ParsedGold;
|
||||||
|
rectColor.X -= timingHandle.IdChain.Length * 0.12f;
|
||||||
|
rectColor.Y -= timingHandle.IdChain.Length * 0.12f;
|
||||||
|
rectColor.Z -= timingHandle.IdChain.Length * 0.12f;
|
||||||
|
|
||||||
if (maxRectDept < timingHandle.Depth)
|
if (maxRectDept < depth)
|
||||||
maxRectDept = timingHandle.Depth;
|
maxRectDept = (uint)depth;
|
||||||
|
|
||||||
if (startX == endX)
|
if (startX == endX)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var minPos = pos + new Vector2((uint)startX, 20 * timingHandle.Depth);
|
var minPos = pos + new Vector2((uint)startX, 20 * depth);
|
||||||
var maxPos = pos + new Vector2((uint)endX, 20 * (timingHandle.Depth + 1));
|
var maxPos = pos + new Vector2((uint)endX, 20 * (depth + 1));
|
||||||
|
|
||||||
|
rects[timingHandle.Id] = new RectInfo
|
||||||
|
{
|
||||||
|
Hover = mousePos.X >= minPos.X && mousePos.X < maxPos.X &&
|
||||||
|
mousePos.Y >= minPos.Y && mousePos.Y < maxPos.Y,
|
||||||
|
Timing = timingHandle,
|
||||||
|
MinPos = minPos,
|
||||||
|
MaxPos = maxPos,
|
||||||
|
RectColor = rectColor,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var hoveredItem in rects.Values.Where(x => x.Hover))
|
||||||
|
{
|
||||||
|
foreach (var rectInfo in rects.Values)
|
||||||
|
{
|
||||||
|
if (rectInfo == hoveredItem)
|
||||||
|
rectInfo.RectColor = new Vector4(255, 255, 255, 255);
|
||||||
|
else if (rectInfo.Timing.IdChain.Contains(hoveredItem.Timing.Id))
|
||||||
|
rectInfo.RectColor = ImGuiColors.ParsedGreen;
|
||||||
|
else if (hoveredItem.Timing.IdChain.Contains(rectInfo.Timing.Id))
|
||||||
|
rectInfo.RectColor = ImGuiColors.ParsedPink;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var rectInfo in rects.Values)
|
||||||
|
{
|
||||||
ImGui.GetWindowDrawList().AddRectFilled(
|
ImGui.GetWindowDrawList().AddRectFilled(
|
||||||
minPos,
|
rectInfo.MinPos,
|
||||||
maxPos,
|
rectInfo.MaxPos,
|
||||||
ImGui.GetColorU32(rectColor));
|
ImGui.GetColorU32(rectInfo.RectColor));
|
||||||
|
|
||||||
ImGui.GetWindowDrawList().AddTextClippedEx(minPos, maxPos, timingHandle.Name, null, Vector2.Zero, null);
|
if (rectInfo.Hover)
|
||||||
|
ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(0, 0, 0, 255));
|
||||||
|
ImGui.GetWindowDrawList().AddTextClippedEx(
|
||||||
|
rectInfo.MinPos,
|
||||||
|
rectInfo.MaxPos,
|
||||||
|
rectInfo.Timing.Name,
|
||||||
|
null,
|
||||||
|
Vector2.Zero,
|
||||||
|
null);
|
||||||
|
if (rectInfo.Hover)
|
||||||
|
ImGui.PopStyleColor();
|
||||||
|
|
||||||
// Show tooltip when hovered
|
// Show tooltip when hovered
|
||||||
var mousePos = ImGui.GetMousePos();
|
if (rectInfo.Hover)
|
||||||
if (mousePos.X > pos.X + startX && mousePos.X < pos.X + endX &&
|
|
||||||
mousePos.Y > pos.Y + (20 * timingHandle.Depth) &&
|
|
||||||
mousePos.Y < pos.Y + (20 * (timingHandle.Depth + 1)))
|
|
||||||
{
|
{
|
||||||
ImGui.BeginTooltip();
|
ImGui.BeginTooltip();
|
||||||
ImGui.Text(timingHandle.Name);
|
ImGui.TextUnformatted(rectInfo.Timing.Name);
|
||||||
ImGui.Text(timingHandle.MemberName);
|
ImGui.TextUnformatted(rectInfo.Timing.MemberName);
|
||||||
ImGui.Text($"{timingHandle.FileName}:{timingHandle.LineNumber}");
|
ImGui.TextUnformatted($"{rectInfo.Timing.FileName}:{rectInfo.Timing.LineNumber}");
|
||||||
ImGui.Text($"Duration: {timingHandle.Duration}ms");
|
ImGui.TextUnformatted($"Duration: {rectInfo.Timing.Duration}ms");
|
||||||
|
if (rectInfo.Timing.Parent != null)
|
||||||
|
ImGui.TextUnformatted($"Parent: {rectInfo.Timing.Parent.Name}");
|
||||||
ImGui.EndTooltip();
|
ImGui.EndTooltip();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ using System.Reflection;
|
||||||
using System.Runtime.Serialization;
|
using System.Runtime.Serialization;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Dalamud.Logging.Internal;
|
using Dalamud.Logging.Internal;
|
||||||
|
using Dalamud.Utility.Timing;
|
||||||
|
|
||||||
namespace Dalamud.IoC.Internal
|
namespace Dalamud.IoC.Internal
|
||||||
{
|
{
|
||||||
|
|
@ -60,7 +61,7 @@ namespace Dalamud.IoC.Internal
|
||||||
var parameterType = p.ParameterType;
|
var parameterType = p.ParameterType;
|
||||||
var requiredVersion = p.GetCustomAttribute(typeof(RequiredVersionAttribute)) as RequiredVersionAttribute;
|
var requiredVersion = p.GetCustomAttribute(typeof(RequiredVersionAttribute)) as RequiredVersionAttribute;
|
||||||
return (parameterType, requiredVersion);
|
return (parameterType, requiredVersion);
|
||||||
});
|
}).ToList();
|
||||||
|
|
||||||
var versionCheck = parameters.All(p => CheckInterfaceVersion(p.requiredVersion, p.parameterType));
|
var versionCheck = parameters.All(p => CheckInterfaceVersion(p.requiredVersion, p.parameterType));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -314,13 +314,13 @@ internal partial class PluginManager : IDisposable
|
||||||
// Dev plugins should load first.
|
// Dev plugins should load first.
|
||||||
pluginDefs.InsertRange(0, devPluginDefs);
|
pluginDefs.InsertRange(0, devPluginDefs);
|
||||||
|
|
||||||
Task LoadPluginOnBoot(string logPrefix, PluginDef pluginDef)
|
async Task LoadPluginOnBoot(string logPrefix, PluginDef pluginDef)
|
||||||
{
|
{
|
||||||
using (Timings.Start($"{pluginDef.DllFile.Name}: {logPrefix}Boot"))
|
using (Timings.Start($"{pluginDef.DllFile.Name}: {logPrefix}Boot"))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return this.LoadPluginAsync(
|
await this.LoadPluginAsync(
|
||||||
pluginDef.DllFile,
|
pluginDef.DllFile,
|
||||||
pluginDef.Manifest,
|
pluginDef.Manifest,
|
||||||
PluginLoadReason.Boot,
|
PluginLoadReason.Boot,
|
||||||
|
|
@ -336,8 +336,6 @@ internal partial class PluginManager : IDisposable
|
||||||
Log.Error(ex, "{0}: During boot plugin load, an unexpected error occurred", logPrefix);
|
Log.Error(ex, "{0}: During boot plugin load, an unexpected error occurred", logPrefix);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void LoadPluginsSync(string logPrefix, IEnumerable<PluginDef> pluginDefsList)
|
void LoadPluginsSync(string logPrefix, IEnumerable<PluginDef> pluginDefsList)
|
||||||
|
|
@ -350,7 +348,8 @@ internal partial class PluginManager : IDisposable
|
||||||
{
|
{
|
||||||
return Task.WhenAll(
|
return Task.WhenAll(
|
||||||
pluginDefsList
|
pluginDefsList
|
||||||
.Select(pluginDef => LoadPluginOnBoot(logPrefix, pluginDef))
|
.Select(pluginDef => Task.Run(Timings.AttachTimingHandle(
|
||||||
|
() => LoadPluginOnBoot(logPrefix, pluginDef))))
|
||||||
.ToArray());
|
.ToArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -394,15 +393,15 @@ internal partial class PluginManager : IDisposable
|
||||||
TaskContinuationOptions.RunContinuationsAsynchronously)
|
TaskContinuationOptions.RunContinuationsAsynchronously)
|
||||||
.Unwrap()
|
.Unwrap()
|
||||||
.ContinueWith(
|
.ContinueWith(
|
||||||
_ => Service<Framework>.Get().RunOnTick(() =>
|
_ => Service<Framework>.Get().RunOnTick(
|
||||||
{
|
() => LoadPluginsSync(
|
||||||
LoadPluginsSync(
|
|
||||||
"DrawAvailableSync",
|
"DrawAvailableSync",
|
||||||
syncPlugins.Where(def => def.Manifest?.LoadRequiredState is 0 or null));
|
syncPlugins.Where(def => def.Manifest?.LoadRequiredState is 0 or null))))
|
||||||
return LoadPluginsAsync(
|
.Unwrap()
|
||||||
"DrawAvailableAsync",
|
.ContinueWith(
|
||||||
asyncPlugins.Where(def => def.Manifest?.LoadRequiredState is 0 or null));
|
_ => LoadPluginsAsync(
|
||||||
}))
|
"DrawAvailableAsync",
|
||||||
|
asyncPlugins.Where(def => def.Manifest?.LoadRequiredState is 0 or null)))
|
||||||
.Unwrap());
|
.Unwrap());
|
||||||
|
|
||||||
// Save signatures when all plugins are done loading, successful or not.
|
// Save signatures when all plugins are done loading, successful or not.
|
||||||
|
|
@ -427,33 +426,16 @@ internal partial class PluginManager : IDisposable
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reload all loaded plugins.
|
/// Reload all loaded plugins.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void ReloadAllPlugins()
|
/// <returns>A task.</returns>
|
||||||
|
public Task ReloadAllPluginsAsync()
|
||||||
{
|
{
|
||||||
var aggregate = new List<Exception>();
|
|
||||||
|
|
||||||
lock (this.pluginListLock)
|
lock (this.pluginListLock)
|
||||||
{
|
{
|
||||||
foreach (var plugin in this.InstalledPlugins)
|
return Task.WhenAll(this.InstalledPlugins
|
||||||
{
|
.Where(x => x.IsLoaded)
|
||||||
if (plugin.IsLoaded)
|
.ToList()
|
||||||
{
|
.Select(x => Task.Run(async () => await x.ReloadAsync()))
|
||||||
try
|
.ToList());
|
||||||
{
|
|
||||||
plugin.Reload();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Log.Error(ex, "Error during reload all");
|
|
||||||
|
|
||||||
aggregate.Add(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (aggregate.Any())
|
|
||||||
{
|
|
||||||
throw new AggregateException(aggregate);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -706,10 +688,7 @@ internal partial class PluginManager : IDisposable
|
||||||
if (plugin.IsDisabled)
|
if (plugin.IsDisabled)
|
||||||
plugin.Enable();
|
plugin.Enable();
|
||||||
|
|
||||||
// Await for things that plugin just require
|
await plugin.LoadAsync(reason);
|
||||||
_ = await Service<SigScanner>.GetAsync();
|
|
||||||
|
|
||||||
plugin.Load(reason);
|
|
||||||
}
|
}
|
||||||
catch (InvalidPluginException)
|
catch (InvalidPluginException)
|
||||||
{
|
{
|
||||||
|
|
@ -1004,7 +983,7 @@ internal partial class PluginManager : IDisposable
|
||||||
Thread.Sleep(500);
|
Thread.Sleep(500);
|
||||||
|
|
||||||
// Let's indicate "installer" here since this is supposed to be a fresh install
|
// Let's indicate "installer" here since this is supposed to be a fresh install
|
||||||
plugin.Load(PluginLoadReason.Installer);
|
plugin.LoadAsync(PluginLoadReason.Installer).Wait();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -124,7 +124,7 @@ internal class LocalDevPlugin : LocalPlugin, IDisposable
|
||||||
var current = Interlocked.Increment(ref this.reloadCounter);
|
var current = Interlocked.Increment(ref this.reloadCounter);
|
||||||
|
|
||||||
Task.Delay(500).ContinueWith(
|
Task.Delay(500).ContinueWith(
|
||||||
_ =>
|
async _ =>
|
||||||
{
|
{
|
||||||
if (this.fileWatcherTokenSource.IsCancellationRequested)
|
if (this.fileWatcherTokenSource.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
|
|
@ -148,7 +148,7 @@ internal class LocalDevPlugin : LocalPlugin, IDisposable
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
this.Reload();
|
await this.ReloadAsync();
|
||||||
notificationManager.AddNotification($"The DevPlugin '{this.Name} was reloaded successfully.", "Plugin reloaded!", NotificationType.Success);
|
notificationManager.AddNotification($"The DevPlugin '{this.Name} was reloaded successfully.", "Plugin reloaded!", NotificationType.Success);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Dalamud.Configuration.Internal;
|
using Dalamud.Configuration.Internal;
|
||||||
using Dalamud.Game;
|
using Dalamud.Game;
|
||||||
using Dalamud.Game.Gui.Dtr;
|
using Dalamud.Game.Gui.Dtr;
|
||||||
|
|
@ -160,7 +160,7 @@ internal class LocalPlugin : IDisposable
|
||||||
public PluginState State { get; protected set; }
|
public PluginState State { get; protected set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the AssemblyName plugin, populated during <see cref="Load(PluginLoadReason, bool)"/>.
|
/// Gets the AssemblyName plugin, populated during <see cref="LoadAsync"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>Plugin type.</returns>
|
/// <returns>Plugin type.</returns>
|
||||||
public AssemblyName? AssemblyName { get; private set; }
|
public AssemblyName? AssemblyName { get; private set; }
|
||||||
|
|
@ -225,7 +225,8 @@ internal class LocalPlugin : IDisposable
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="reason">The reason why this plugin is being loaded.</param>
|
/// <param name="reason">The reason why this plugin is being loaded.</param>
|
||||||
/// <param name="reloading">Load while reloading.</param>
|
/// <param name="reloading">Load while reloading.</param>
|
||||||
public void Load(PluginLoadReason reason, bool reloading = false)
|
/// <returns>A task.</returns>
|
||||||
|
public async Task LoadAsync(PluginLoadReason reason, bool reloading = false)
|
||||||
{
|
{
|
||||||
var startInfo = Service<DalamudStartInfo>.Get();
|
var startInfo = Service<DalamudStartInfo>.Get();
|
||||||
var configuration = Service<DalamudConfiguration>.Get();
|
var configuration = Service<DalamudConfiguration>.Get();
|
||||||
|
|
@ -334,7 +335,7 @@ internal class LocalPlugin : IDisposable
|
||||||
this.DalamudInterface = new DalamudPluginInterface(this.pluginAssembly.GetName().Name!, this.DllFile, reason, this.IsDev);
|
this.DalamudInterface = new DalamudPluginInterface(this.pluginAssembly.GetName().Name!, this.DllFile, reason, this.IsDev);
|
||||||
|
|
||||||
var ioc = Service<ServiceContainer>.Get();
|
var ioc = Service<ServiceContainer>.Get();
|
||||||
this.instance = ioc.CreateAsync(this.pluginType, this.DalamudInterface).GetAwaiter().GetResult() as IDalamudPlugin;
|
this.instance = await ioc.CreateAsync(this.pluginType, this.DalamudInterface) as IDalamudPlugin;
|
||||||
if (this.instance == null)
|
if (this.instance == null)
|
||||||
{
|
{
|
||||||
this.State = PluginState.LoadError;
|
this.State = PluginState.LoadError;
|
||||||
|
|
@ -423,7 +424,8 @@ internal class LocalPlugin : IDisposable
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reload this plugin.
|
/// Reload this plugin.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Reload()
|
/// <returns>A task.</returns>
|
||||||
|
public async Task ReloadAsync()
|
||||||
{
|
{
|
||||||
this.Unload(true);
|
this.Unload(true);
|
||||||
|
|
||||||
|
|
@ -431,7 +433,7 @@ internal class LocalPlugin : IDisposable
|
||||||
var dtr = Service<DtrBar>.Get();
|
var dtr = Service<DtrBar>.Get();
|
||||||
dtr.HandleRemovedNodes();
|
dtr.HandleRemovedNodes();
|
||||||
|
|
||||||
this.Load(PluginLoadReason.Reload, true);
|
await this.LoadAsync(PluginLoadReason.Reload, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,15 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Dalamud.Configuration.Internal;
|
||||||
|
using Dalamud.Game;
|
||||||
|
using Dalamud.Interface.Internal;
|
||||||
using Dalamud.IoC.Internal;
|
using Dalamud.IoC.Internal;
|
||||||
using Dalamud.Logging.Internal;
|
using Dalamud.Logging.Internal;
|
||||||
|
using Dalamud.Utility.Timing;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
|
|
||||||
namespace Dalamud
|
namespace Dalamud
|
||||||
|
|
@ -26,16 +31,43 @@ namespace Dalamud
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static Task BlockingResolved { get; } = BlockingServicesLoadedTaskCompletionSource.Task;
|
public static Task BlockingResolved { get; } = BlockingServicesLoadedTaskCompletionSource.Task;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes Provided Services and FFXIVClientStructs.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dalamud">Instance of <see cref="Dalamud"/>.</param>
|
||||||
|
/// <param name="startInfo">Instance of <see cref="DalamudStartInfo"/>.</param>
|
||||||
|
/// <param name="configuration">Instance of <see cref="DalamudConfiguration"/>.</param>
|
||||||
|
public static void InitializeProvidedServicesAndClientStructs(Dalamud dalamud, DalamudStartInfo startInfo, DalamudConfiguration configuration)
|
||||||
|
{
|
||||||
|
Service<Dalamud>.Provide(dalamud);
|
||||||
|
Service<DalamudStartInfo>.Provide(startInfo);
|
||||||
|
Service<DalamudConfiguration>.Provide(configuration);
|
||||||
|
Service<ServiceContainer>.Provide(new ServiceContainer());
|
||||||
|
|
||||||
|
// Initialize the process information.
|
||||||
|
var cacheDir = new DirectoryInfo(Path.Combine(startInfo.WorkingDirectory!, "cachedSigs"));
|
||||||
|
if (!cacheDir.Exists)
|
||||||
|
cacheDir.Create();
|
||||||
|
Service<SigScanner>.Provide(new SigScanner(true, new FileInfo(Path.Combine(cacheDir.FullName, $"{startInfo.GameVersion}.json"))));
|
||||||
|
|
||||||
|
using (Timings.Start("CS Resolver Init"))
|
||||||
|
{
|
||||||
|
FFXIVClientStructs.Resolver.InitializeParallel(new FileInfo(Path.Combine(cacheDir.FullName, $"{startInfo.GameVersion}_cs.json")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Kicks off construction of services that can handle early loading.
|
/// Kicks off construction of services that can handle early loading.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>Task for initializing all services.</returns>
|
/// <returns>Task for initializing all services.</returns>
|
||||||
public static async Task InitializeEarlyLoadableServices()
|
public static async Task InitializeEarlyLoadableServices()
|
||||||
{
|
{
|
||||||
Service<ServiceContainer>.Provide(new ServiceContainer());
|
using var serviceInitializeTimings = Timings.Start("Services Init");
|
||||||
|
|
||||||
var service = typeof(Service<>);
|
var service = typeof(Service<>);
|
||||||
var blockingEarlyLoadingServices = new List<Task>();
|
|
||||||
|
var earlyLoadingServices = new HashSet<Type>();
|
||||||
|
var blockingEarlyLoadingServices = new HashSet<Type>();
|
||||||
|
var afterDrawingEarlyLoadedServices = new HashSet<Type>();
|
||||||
|
|
||||||
var dependencyServicesMap = new Dictionary<Type, List<Type>>();
|
var dependencyServicesMap = new Dictionary<Type, List<Type>>();
|
||||||
var getAsyncTaskMap = new Dictionary<Type, Task>();
|
var getAsyncTaskMap = new Dictionary<Type, Task>();
|
||||||
|
|
@ -52,10 +84,19 @@ namespace Dalamud
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
null);
|
null);
|
||||||
|
|
||||||
if (attr.IsAssignableTo(typeof(BlockingEarlyLoadedService)))
|
if (attr.IsAssignableTo(typeof(BlockingEarlyLoadedService)))
|
||||||
{
|
{
|
||||||
getAsyncTaskMap[serviceType] = getTask;
|
getAsyncTaskMap[serviceType] = getTask;
|
||||||
blockingEarlyLoadingServices.Add(getTask);
|
blockingEarlyLoadingServices.Add(serviceType);
|
||||||
|
}
|
||||||
|
else if (attr.IsAssignableTo(typeof(AfterDrawingEarlyLoadedService)))
|
||||||
|
{
|
||||||
|
afterDrawingEarlyLoadedServices.Add(serviceType);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
earlyLoadingServices.Add(serviceType);
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencyServicesMap[serviceType] =
|
dependencyServicesMap[serviceType] =
|
||||||
|
|
@ -69,50 +110,70 @@ namespace Dalamud
|
||||||
null);
|
null);
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = Task.WhenAll(blockingEarlyLoadingServices).ContinueWith(x =>
|
_ = Task.Run(async () =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (x.IsFaulted)
|
using var blockingServiceInitializeTimings = Timings.Start("BlockingServices Init");
|
||||||
BlockingServicesLoadedTaskCompletionSource.SetException(x.Exception!);
|
await Task.WhenAll(blockingEarlyLoadingServices.Select(x => getAsyncTaskMap[x]));
|
||||||
else
|
BlockingServicesLoadedTaskCompletionSource.SetResult();
|
||||||
BlockingServicesLoadedTaskCompletionSource.SetResult();
|
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
// don't care, as this means task result/exception has already been set
|
BlockingServicesLoadedTaskCompletionSource.SetException(e);
|
||||||
}
|
}
|
||||||
}).ConfigureAwait(false);
|
}).ConfigureAwait(false);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var tasks = new List<Task>();
|
for (var i = 0; i < 2; i++)
|
||||||
while (dependencyServicesMap.Any())
|
|
||||||
{
|
{
|
||||||
tasks.Clear();
|
var tasks = new List<Task>();
|
||||||
foreach (var (serviceType, dependencies) in dependencyServicesMap.ToList())
|
var servicesToLoad = new HashSet<Type>();
|
||||||
|
if (i == 0)
|
||||||
{
|
{
|
||||||
if (!dependencies.All(
|
servicesToLoad.UnionWith(earlyLoadingServices);
|
||||||
x => !getAsyncTaskMap.ContainsKey(x) || getAsyncTaskMap[x].IsCompleted))
|
servicesToLoad.UnionWith(blockingEarlyLoadingServices);
|
||||||
continue;
|
}
|
||||||
|
else
|
||||||
tasks.Add((Task)service.MakeGenericType(serviceType).InvokeMember(
|
{
|
||||||
"StartLoader",
|
servicesToLoad.UnionWith(afterDrawingEarlyLoadedServices);
|
||||||
BindingFlags.InvokeMethod | BindingFlags.Static | BindingFlags.Public,
|
await (await Service<InterfaceManager>.GetAsync()).SceneInitializeTask;
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null));
|
|
||||||
dependencyServicesMap.Remove(serviceType);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!tasks.Any())
|
while (servicesToLoad.Any())
|
||||||
throw new InvalidOperationException("Unresolvable dependency cycle detected");
|
|
||||||
|
|
||||||
await Task.WhenAll(tasks);
|
|
||||||
foreach (var task in tasks)
|
|
||||||
{
|
{
|
||||||
if (task.IsFaulted)
|
foreach (var serviceType in servicesToLoad)
|
||||||
throw task.Exception!;
|
{
|
||||||
|
if (dependencyServicesMap[serviceType].Any(
|
||||||
|
x => getAsyncTaskMap.GetValueOrDefault(x)?.IsCompleted == false))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
tasks.Add((Task)service.MakeGenericType(serviceType).InvokeMember(
|
||||||
|
"StartLoader",
|
||||||
|
BindingFlags.InvokeMethod | BindingFlags.Static | BindingFlags.Public,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null));
|
||||||
|
servicesToLoad.Remove(serviceType);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tasks.Any())
|
||||||
|
throw new InvalidOperationException("Unresolvable dependency cycle detected");
|
||||||
|
|
||||||
|
if (servicesToLoad.Any())
|
||||||
|
{
|
||||||
|
await Task.WhenAny(tasks);
|
||||||
|
var faultedTasks = tasks.Where(x => x.IsFaulted).Select(x => (Exception)x.Exception!).ToArray();
|
||||||
|
if (faultedTasks.Any())
|
||||||
|
throw new AggregateException(faultedTasks);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await Task.WhenAll(tasks);
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.RemoveAll(x => x.IsCompleted);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -173,5 +234,14 @@ namespace Dalamud
|
||||||
public class BlockingEarlyLoadedService : EarlyLoadedService
|
public class BlockingEarlyLoadedService : EarlyLoadedService
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates that the class is a service, and will be instantiated automatically on startup,
|
||||||
|
/// when drawing becomes available.
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Class)]
|
||||||
|
public class AfterDrawingEarlyLoadedService : EarlyLoadedService
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,9 +23,6 @@ namespace Dalamud
|
||||||
// ReSharper disable once StaticMemberInGenericType
|
// ReSharper disable once StaticMemberInGenericType
|
||||||
private static readonly TaskCompletionSource<T> InstanceTcs = new();
|
private static readonly TaskCompletionSource<T> InstanceTcs = new();
|
||||||
|
|
||||||
// ReSharper disable once StaticMemberInGenericType
|
|
||||||
private static bool startLoaderInvoked = false;
|
|
||||||
|
|
||||||
static Service()
|
static Service()
|
||||||
{
|
{
|
||||||
var exposeToPlugins = typeof(T).GetCustomAttribute<PluginInterfaceAttribute>() != null;
|
var exposeToPlugins = typeof(T).GetCustomAttribute<PluginInterfaceAttribute>() != null;
|
||||||
|
|
@ -45,37 +42,29 @@ namespace Dalamud
|
||||||
[UsedImplicitly]
|
[UsedImplicitly]
|
||||||
public static Task<T> StartLoader()
|
public static Task<T> StartLoader()
|
||||||
{
|
{
|
||||||
if (startLoaderInvoked)
|
|
||||||
throw new InvalidOperationException("StartLoader has already been called.");
|
|
||||||
|
|
||||||
var attr = typeof(T).GetCustomAttribute<ServiceManager.Service>(true)?.GetType();
|
var attr = typeof(T).GetCustomAttribute<ServiceManager.Service>(true)?.GetType();
|
||||||
if (attr?.IsAssignableTo(typeof(ServiceManager.EarlyLoadedService)) != true)
|
if (attr?.IsAssignableTo(typeof(ServiceManager.EarlyLoadedService)) != true)
|
||||||
throw new InvalidOperationException($"{typeof(T).Name} is not an EarlyLoadedService");
|
throw new InvalidOperationException($"{typeof(T).Name} is not an EarlyLoadedService");
|
||||||
|
|
||||||
startLoaderInvoked = true;
|
return Task.Run(Timings.AttachTimingHandle(async () =>
|
||||||
return Task.Run(async () =>
|
|
||||||
{
|
{
|
||||||
using (Timings.Start($"{typeof(T).Namespace} Enable"))
|
ServiceManager.Log.Debug("Service<{0}>: Begin construction", typeof(T).Name);
|
||||||
|
try
|
||||||
{
|
{
|
||||||
|
var x = await ConstructObject();
|
||||||
if (attr?.IsAssignableTo(typeof(ServiceManager.BlockingEarlyLoadedService)) == true)
|
if (attr?.IsAssignableTo(typeof(ServiceManager.BlockingEarlyLoadedService)) == true)
|
||||||
ServiceManager.Log.Debug("Service<{0}>: Begin construction", typeof(T).Name);
|
ServiceManager.Log.Debug("Service<{0}>: Construction complete", typeof(T).Name);
|
||||||
try
|
InstanceTcs.SetResult(x);
|
||||||
{
|
return x;
|
||||||
var x = await ConstructObject();
|
|
||||||
if (attr?.IsAssignableTo(typeof(ServiceManager.BlockingEarlyLoadedService)) == true)
|
|
||||||
ServiceManager.Log.Debug("Service<{0}>: Construction complete", typeof(T).Name);
|
|
||||||
InstanceTcs.SetResult(x);
|
|
||||||
return x;
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
InstanceTcs.SetException(e);
|
|
||||||
if (attr?.IsAssignableTo(typeof(ServiceManager.BlockingEarlyLoadedService)) == true)
|
|
||||||
ServiceManager.Log.Error(e, "Service<{0}>: Construction failure", typeof(T).Name);
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
InstanceTcs.SetException(e);
|
||||||
|
if (attr?.IsAssignableTo(typeof(ServiceManager.BlockingEarlyLoadedService)) == true)
|
||||||
|
ServiceManager.Log.Error(e, "Service<{0}>: Construction failure", typeof(T).Name);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -165,7 +154,10 @@ namespace Dalamud
|
||||||
var ctor = GetServiceConstructor();
|
var ctor = GetServiceConstructor();
|
||||||
var args = await Task.WhenAll(
|
var args = await Task.WhenAll(
|
||||||
ctor.GetParameters().Select(x => GetServiceObjectConstructArgument(x.ParameterType)));
|
ctor.GetParameters().Select(x => GetServiceObjectConstructArgument(x.ParameterType)));
|
||||||
return (T)ctor.Invoke(args)!;
|
using (Timings.Start($"{typeof(T).Name} Construct"))
|
||||||
|
{
|
||||||
|
return (T)ctor.Invoke(args)!;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,27 @@
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
namespace Dalamud.Utility.Timing;
|
namespace Dalamud.Utility.Timing;
|
||||||
|
|
||||||
public class TimingEvent
|
public class TimingEvent
|
||||||
{
|
{
|
||||||
|
private static long IdCounter = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Id of this timing event.
|
||||||
|
/// </summary>
|
||||||
|
public readonly long Id = Interlocked.Increment(ref IdCounter);
|
||||||
|
|
||||||
internal TimingEvent(string name)
|
internal TimingEvent(string name)
|
||||||
{
|
{
|
||||||
this.Name = name;
|
this.Name = name;
|
||||||
this.StartTime = Timings.Stopwatch.Elapsed.TotalMilliseconds;
|
this.StartTime = Timings.Stopwatch.Elapsed.TotalMilliseconds;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the time this timing started.
|
/// Gets the time this timing started.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public double StartTime { get; private set; }
|
public double StartTime { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the name of the timing.
|
/// Gets the name of the timing.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
|
|
@ -8,7 +9,7 @@ namespace Dalamud.Utility.Timing;
|
||||||
/// Class used for tracking a time interval taken.
|
/// Class used for tracking a time interval taken.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DebuggerDisplay("{Name} - {Duration}")]
|
[DebuggerDisplay("{Name} - {Duration}")]
|
||||||
public sealed class TimingHandle : TimingEvent, IDisposable
|
public sealed class TimingHandle : TimingEvent, IDisposable, IComparable<TimingHandle>
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="TimingHandle"/> class.
|
/// Initializes a new instance of the <see cref="TimingHandle"/> class.
|
||||||
|
|
@ -16,28 +17,33 @@ public sealed class TimingHandle : TimingEvent, IDisposable
|
||||||
/// <param name="name">The name of this timing.</param>
|
/// <param name="name">The name of this timing.</param>
|
||||||
internal TimingHandle(string name) : base(name)
|
internal TimingHandle(string name) : base(name)
|
||||||
{
|
{
|
||||||
this.Parent = Timings.Current.Value;
|
this.Stack = Timings.TaskTimingHandles;
|
||||||
Timings.Current.Value = this;
|
|
||||||
|
|
||||||
lock (Timings.AllTimings)
|
this.Parent = this.Stack.LastOrDefault();
|
||||||
|
|
||||||
|
if (this.Parent != null)
|
||||||
{
|
{
|
||||||
if (this.Parent != null)
|
this.Parent.ChildCount++;
|
||||||
{
|
this.IdChain = new long[this.Parent.IdChain.Length + 1];
|
||||||
this.ChildCount++;
|
Array.Copy(this.Parent.IdChain, this.IdChain, this.Parent.IdChain.Length);
|
||||||
}
|
|
||||||
|
|
||||||
this.EndTime = this.StartTime;
|
|
||||||
this.IsMainThread = ThreadSafety.IsMainThread;
|
|
||||||
|
|
||||||
if (Timings.ActiveTimings.Count > 0)
|
|
||||||
{
|
|
||||||
this.Depth = Timings.ActiveTimings.Max(x => x.Depth) + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
Timings.ActiveTimings.Add(this);
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.IdChain = new long[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
this.IdChain[^1] = this.Id;
|
||||||
|
this.EndTime = this.StartTime;
|
||||||
|
this.IsMainThread = ThreadSafety.IsMainThread;
|
||||||
|
|
||||||
|
this.Stack.Add(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the id chain.
|
||||||
|
/// </summary>
|
||||||
|
public long[] IdChain { get; init; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the time this timing ended.
|
/// Gets the time this timing ended.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -48,16 +54,16 @@ public sealed class TimingHandle : TimingEvent, IDisposable
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public double Duration => Math.Floor(this.EndTime - this.StartTime);
|
public double Duration => Math.Floor(this.EndTime - this.StartTime);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the attached timing handle stack.
|
||||||
|
/// </summary>
|
||||||
|
public List<TimingHandle> Stack { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the parent timing.
|
/// Gets the parent timing.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public TimingHandle? Parent { get; private set; }
|
public TimingHandle? Parent { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether or not this timing has already returned to its parent.
|
|
||||||
/// </summary>
|
|
||||||
public bool Returned { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether or not this timing was started on the main thread.
|
/// Gets a value indicating whether or not this timing was started on the main thread.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -68,26 +74,39 @@ public sealed class TimingHandle : TimingEvent, IDisposable
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public uint ChildCount { get; private set; }
|
public uint ChildCount { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the depth of this timing.
|
|
||||||
/// </summary>
|
|
||||||
public uint Depth { get; private set; }
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
this.EndTime = Timings.Stopwatch.Elapsed.TotalMilliseconds;
|
this.EndTime = Timings.Stopwatch.Elapsed.TotalMilliseconds;
|
||||||
Timings.Current.Value = this.Parent;
|
this.Stack.Remove(this);
|
||||||
|
if (this.Duration > 1 || this.ChildCount > 0)
|
||||||
lock (Timings.AllTimings)
|
|
||||||
{
|
{
|
||||||
if (this.Duration > 1 || this.ChildCount > 0)
|
lock (Timings.AllTimings)
|
||||||
{
|
{
|
||||||
Timings.AllTimings.Add(this);
|
Timings.AllTimings.Add(this, this);
|
||||||
this.Returned = this.Parent != null && Timings.ActiveTimings.Contains(this.Parent);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Timings.ActiveTimings.Remove(this);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public int CompareTo(TimingHandle? other)
|
||||||
|
{
|
||||||
|
if (other == null)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
var i = 0;
|
||||||
|
for (; i < this.IdChain.Length && i < other.IdChain.Length; i++)
|
||||||
|
{
|
||||||
|
if (this.IdChain[i] < other.IdChain[i])
|
||||||
|
return -1;
|
||||||
|
if (this.IdChain[i] > other.IdChain[i])
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.IdChain.Length < other.IdChain.Length)
|
||||||
|
return -1;
|
||||||
|
if (this.IdChain.Length > other.IdChain.Length)
|
||||||
|
return 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Dalamud.Utility.Timing;
|
namespace Dalamud.Utility.Timing;
|
||||||
|
|
||||||
|
|
@ -19,19 +20,75 @@ public static class Timings
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// All concluded timings.
|
/// All concluded timings.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static readonly List<TimingHandle> AllTimings = new();
|
internal static readonly SortedList<TimingHandle, TimingHandle> AllTimings = new();
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// All active timings.
|
|
||||||
/// </summary>
|
|
||||||
internal static readonly List<TimingHandle> ActiveTimings = new();
|
|
||||||
|
|
||||||
internal static readonly List<TimingEvent> Events = new();
|
internal static readonly List<TimingEvent> Events = new();
|
||||||
|
|
||||||
|
private static readonly AsyncLocal<Tuple<int?, List<TimingHandle>>> taskTimingHandleStorage = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Current active timing entry.
|
/// Gets or sets all active timings of current thread.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static readonly AsyncLocal<TimingHandle> Current = new();
|
internal static List<TimingHandle> TaskTimingHandles
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (taskTimingHandleStorage.Value == null || taskTimingHandleStorage.Value.Item1 != Task.CurrentId)
|
||||||
|
taskTimingHandleStorage.Value = Tuple.Create<int?, List<TimingHandle>>(Task.CurrentId, new());
|
||||||
|
return taskTimingHandleStorage.Value!.Item2!;
|
||||||
|
}
|
||||||
|
set => taskTimingHandleStorage.Value = Tuple.Create(Task.CurrentId, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attaches timing handle to a Func{T}.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="task">Task to attach.</param>
|
||||||
|
/// <typeparam name="T">Return type.</typeparam>
|
||||||
|
/// <returns>Attached task.</returns>
|
||||||
|
public static Func<T> AttachTimingHandle<T>(Func<T> task)
|
||||||
|
{
|
||||||
|
var outerTimingHandle = TaskTimingHandles;
|
||||||
|
return () =>
|
||||||
|
{
|
||||||
|
T res = default(T);
|
||||||
|
var prev = TaskTimingHandles;
|
||||||
|
TaskTimingHandles = outerTimingHandle;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
res = task();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
TaskTimingHandles = prev;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attaches timing handle to an Action.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="task">Task to attach.</param>
|
||||||
|
/// <returns>Attached task.</returns>
|
||||||
|
public static Action AttachTimingHandle(Action task)
|
||||||
|
{
|
||||||
|
var outerTimingHandle = TaskTimingHandles;
|
||||||
|
return () =>
|
||||||
|
{
|
||||||
|
var prev = TaskTimingHandles;
|
||||||
|
TaskTimingHandles = outerTimingHandle;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
task();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
TaskTimingHandles = prev;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Start a new timing.
|
/// Start a new timing.
|
||||||
|
|
@ -50,7 +107,7 @@ public static class Timings
|
||||||
LineNumber = sourceLineNumber,
|
LineNumber = sourceLineNumber,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Record a one-time event.
|
/// Record a one-time event.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue