diff --git a/Dalamud/Game/Framework.cs b/Dalamud/Game/Framework.cs
index a13f0e209..6db9f7312 100644
--- a/Dalamud/Game/Framework.cs
+++ b/Dalamud/Game/Framework.cs
@@ -98,6 +98,11 @@ internal sealed class Framework : IDisposable, IServiceType, IFramework
///
public bool IsFrameworkUnloading { get; internal set; }
+ ///
+ /// Gets the list of update sub-delegates that didn't get updated this frame.
+ ///
+ internal List NonUpdatedSubDelegates { get; private set; } = new();
+
///
/// Gets or sets a value indicating whether to dispatch update events.
///
@@ -272,6 +277,58 @@ internal sealed class Framework : IDisposable, IServiceType, IFramework
this.updateStopwatch.Reset();
StatsStopwatch.Reset();
}
+
+ ///
+ /// Adds a update time to the stats history.
+ ///
+ /// Delegate Name.
+ /// Runtime.
+ internal static void AddToStats(string key, double ms)
+ {
+ if (!StatsHistory.ContainsKey(key))
+ StatsHistory.Add(key, new List());
+
+ StatsHistory[key].Add(ms);
+
+ if (StatsHistory[key].Count > 1000)
+ {
+ StatsHistory[key].RemoveRange(0, StatsHistory[key].Count - 1000);
+ }
+ }
+
+ ///
+ /// Profiles each sub-delegate in the eventDelegate and logs to StatsHistory.
+ ///
+ /// The Delegate to Profile.
+ /// The Framework Instance to pass to delegate.
+ internal void ProfileAndInvoke(IFramework.OnUpdateDelegate? eventDelegate, IFramework frameworkInstance)
+ {
+ if (eventDelegate is null) return;
+
+ var invokeList = eventDelegate.GetInvocationList();
+
+ // Individually invoke OnUpdate handlers and time them.
+ foreach (var d in invokeList)
+ {
+ var stopwatch = Stopwatch.StartNew();
+ try
+ {
+ d.Method.Invoke(d.Target, new object[] { frameworkInstance });
+ }
+ catch (Exception ex)
+ {
+ Log.Error(ex, "Exception while dispatching Framework::Update event.");
+ }
+
+ stopwatch.Stop();
+
+ var key = $"{d.Target}::{d.Method.Name}";
+ if (this.NonUpdatedSubDelegates.Contains(key))
+ this.NonUpdatedSubDelegates.Remove(key);
+
+ AddToStats(key, stopwatch.Elapsed.TotalMilliseconds);
+ }
+ }
[ServiceManager.CallWhenServicesReady]
private void ContinueConstruction()
@@ -329,19 +386,6 @@ internal sealed class Framework : IDisposable, IServiceType, IFramework
this.LastUpdate = DateTime.Now;
this.LastUpdateUTC = DateTime.UtcNow;
- void AddToStats(string key, double ms)
- {
- if (!StatsHistory.ContainsKey(key))
- StatsHistory.Add(key, new List());
-
- StatsHistory[key].Add(ms);
-
- if (StatsHistory[key].Count > 1000)
- {
- StatsHistory[key].RemoveRange(0, StatsHistory[key].Count - 1000);
- }
- }
-
if (StatsEnabled)
{
StatsStopwatch.Restart();
@@ -358,33 +402,11 @@ internal sealed class Framework : IDisposable, IServiceType, IFramework
if (StatsEnabled && this.Update != null)
{
// Stat Tracking for Framework Updates
- var invokeList = this.Update.GetInvocationList();
- var notUpdated = StatsHistory.Keys.ToList();
-
- // Individually invoke OnUpdate handlers and time them.
- foreach (var d in invokeList)
- {
- StatsStopwatch.Restart();
- try
- {
- d.Method.Invoke(d.Target, new object[] { this });
- }
- catch (Exception ex)
- {
- Log.Error(ex, "Exception while dispatching Framework::Update event.");
- }
-
- StatsStopwatch.Stop();
-
- var key = $"{d.Target}::{d.Method.Name}";
- if (notUpdated.Contains(key))
- notUpdated.Remove(key);
-
- AddToStats(key, StatsStopwatch.Elapsed.TotalMilliseconds);
- }
+ this.NonUpdatedSubDelegates = StatsHistory.Keys.ToList();
+ this.ProfileAndInvoke(this.Update, this);
// Cleanup handlers that are no longer being called
- foreach (var key in notUpdated)
+ foreach (var key in this.NonUpdatedSubDelegates)
{
if (key == nameof(this.RunPendingTickTasks))
continue;
@@ -593,5 +615,15 @@ internal class FrameworkPluginScoped : IDisposable, IServiceType, IFramework
public Task RunOnTick(Func func, TimeSpan delay = default, int delayTicks = default, CancellationToken cancellationToken = default)
=> this.frameworkService.RunOnTick(func, delay, delayTicks, cancellationToken);
- private void OnUpdateForward(IFramework framework) => this.Update?.Invoke(framework);
+ private void OnUpdateForward(IFramework framework)
+ {
+ if (Framework.StatsEnabled && this.Update != null)
+ {
+ this.frameworkService.ProfileAndInvoke(this.Update, framework);
+ }
+ else
+ {
+ this.Update?.Invoke(framework);
+ }
+ }
}