mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-12 18:27:23 +01:00
feat: attempt to prevent some plugin disposal crashes by stopping Framework::Update event dispatching before unloading
This commit is contained in:
parent
35d88ac554
commit
de98b75336
2 changed files with 42 additions and 31 deletions
|
|
@ -358,6 +358,10 @@ namespace Dalamud
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
// this must be done before unloading plugins, to prevent crashes due to errors
|
||||||
|
// in plugin cleanup
|
||||||
|
this.Framework.DispatchUpdateEvents = false;
|
||||||
|
|
||||||
// this must be done before unloading plugins, or it can cause a race condition
|
// this must be done before unloading plugins, or it can cause a race condition
|
||||||
// due to rendering happening on another thread, where a plugin might receive
|
// due to rendering happening on another thread, where a plugin might receive
|
||||||
// a render call after it has been disposed, which can crash if it attempts to
|
// a render call after it has been disposed, which can crash if it attempts to
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,8 @@ namespace Dalamud.Game.Internal {
|
||||||
public sealed class Framework : IDisposable {
|
public sealed class Framework : IDisposable {
|
||||||
private readonly Dalamud dalamud;
|
private readonly Dalamud dalamud;
|
||||||
|
|
||||||
|
internal bool DispatchUpdateEvents { get; set; } = true;
|
||||||
|
|
||||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||||
private delegate bool OnUpdateDetour(IntPtr framework);
|
private delegate bool OnUpdateDetour(IntPtr framework);
|
||||||
|
|
||||||
|
|
@ -39,8 +41,8 @@ namespace Dalamud.Game.Internal {
|
||||||
/// A raw pointer to the instance of Client::Framework
|
/// A raw pointer to the instance of Client::Framework
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public FrameworkAddressResolver Address { get; }
|
public FrameworkAddressResolver Address { get; }
|
||||||
|
|
||||||
#region Stats
|
#region Stats
|
||||||
public static bool StatsEnabled { get; set; }
|
public static bool StatsEnabled { get; set; }
|
||||||
public static Dictionary<string, List<double>> StatsHistory = new Dictionary<string, List<double>>();
|
public static Dictionary<string, List<double>> StatsHistory = new Dictionary<string, List<double>>();
|
||||||
private static Stopwatch statsStopwatch = new Stopwatch();
|
private static Stopwatch statsStopwatch = new Stopwatch();
|
||||||
|
|
@ -125,39 +127,42 @@ namespace Dalamud.Game.Internal {
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
Log.Error(ex, "Exception while handling Framework::Update hook.");
|
Log.Error(ex, "Exception while handling Framework::Update hook.");
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
|
||||||
if (StatsEnabled && OnUpdateEvent != null) {
|
|
||||||
// Stat Tracking for Framework Updates
|
|
||||||
var invokeList = OnUpdateEvent.GetInvocationList();
|
|
||||||
var notUpdated = StatsHistory.Keys.ToList();
|
|
||||||
// Individually invoke OnUpdate handlers and time them.
|
|
||||||
foreach (var d in invokeList) {
|
|
||||||
statsStopwatch.Restart();
|
|
||||||
d.Method.Invoke(d.Target, new object[]{ this });
|
|
||||||
statsStopwatch.Stop();
|
|
||||||
var key = $"{d.Target}::{d.Method.Name}";
|
|
||||||
if (notUpdated.Contains(key)) notUpdated.Remove(key);
|
|
||||||
if (!StatsHistory.ContainsKey(key)) StatsHistory.Add(key, new List<double>());
|
|
||||||
StatsHistory[key].Add(statsStopwatch.Elapsed.TotalMilliseconds);
|
|
||||||
if (StatsHistory[key].Count > 1000) {
|
|
||||||
StatsHistory[key].RemoveRange(0, StatsHistory[key].Count - 1000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cleanup handlers that are no longer being called
|
if (this.DispatchUpdateEvents)
|
||||||
foreach (var key in notUpdated) {
|
{
|
||||||
if (StatsHistory[key].Count > 0) {
|
try {
|
||||||
StatsHistory[key].RemoveAt(0);
|
if (StatsEnabled && OnUpdateEvent != null) {
|
||||||
} else {
|
// Stat Tracking for Framework Updates
|
||||||
StatsHistory.Remove(key);
|
var invokeList = OnUpdateEvent.GetInvocationList();
|
||||||
|
var notUpdated = StatsHistory.Keys.ToList();
|
||||||
|
// Individually invoke OnUpdate handlers and time them.
|
||||||
|
foreach (var d in invokeList) {
|
||||||
|
statsStopwatch.Restart();
|
||||||
|
d.Method.Invoke(d.Target, new object[]{ this });
|
||||||
|
statsStopwatch.Stop();
|
||||||
|
var key = $"{d.Target}::{d.Method.Name}";
|
||||||
|
if (notUpdated.Contains(key)) notUpdated.Remove(key);
|
||||||
|
if (!StatsHistory.ContainsKey(key)) StatsHistory.Add(key, new List<double>());
|
||||||
|
StatsHistory[key].Add(statsStopwatch.Elapsed.TotalMilliseconds);
|
||||||
|
if (StatsHistory[key].Count > 1000) {
|
||||||
|
StatsHistory[key].RemoveRange(0, StatsHistory[key].Count - 1000);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cleanup handlers that are no longer being called
|
||||||
|
foreach (var key in notUpdated) {
|
||||||
|
if (StatsHistory[key].Count > 0) {
|
||||||
|
StatsHistory[key].RemoveAt(0);
|
||||||
|
} else {
|
||||||
|
StatsHistory.Remove(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
OnUpdateEvent?.Invoke(this);
|
||||||
}
|
}
|
||||||
} else {
|
} catch (Exception ex) {
|
||||||
OnUpdateEvent?.Invoke(this);
|
Log.Error(ex, "Exception while dispatching Framework::Update event.");
|
||||||
}
|
}
|
||||||
} catch (Exception ex) {
|
|
||||||
Log.Error(ex, "Exception while dispatching Framework::Update event.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.updateHook.Original(framework);
|
return this.updateHook.Original(framework);
|
||||||
|
|
@ -166,6 +171,8 @@ namespace Dalamud.Game.Internal {
|
||||||
private IntPtr HandleFrameworkDestroy() {
|
private IntPtr HandleFrameworkDestroy() {
|
||||||
Log.Information("Framework::OnDestroy!");
|
Log.Information("Framework::OnDestroy!");
|
||||||
|
|
||||||
|
this.DispatchUpdateEvents = false;
|
||||||
|
|
||||||
// Store the pointer to the original trampoline location
|
// Store the pointer to the original trampoline location
|
||||||
var originalPtr = Marshal.GetFunctionPointerForDelegate(this.destroyHook.Original);
|
var originalPtr = Marshal.GetFunctionPointerForDelegate(this.destroyHook.Original);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue