mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-30 12:23:39 +01:00
Improvements (#903)
This commit is contained in:
parent
e9cd7e0273
commit
716736f022
55 changed files with 1809 additions and 872 deletions
|
|
@ -8,10 +8,8 @@ using System.Threading.Tasks;
|
|||
|
||||
using Dalamud.Game.Gui;
|
||||
using Dalamud.Game.Gui.Toast;
|
||||
using Dalamud.Game.Libc;
|
||||
using Dalamud.Game.Network;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Interface.Internal;
|
||||
using Dalamud.IoC;
|
||||
using Dalamud.IoC.Internal;
|
||||
using Dalamud.Utility;
|
||||
|
|
@ -32,21 +30,19 @@ namespace Dalamud.Game
|
|||
private readonly List<RunOnNextTickTaskBase> runOnNextTickTaskList = new();
|
||||
private readonly Stopwatch updateStopwatch = new();
|
||||
|
||||
private Hook<OnUpdateDetour> updateHook;
|
||||
private Hook<OnDestroyDetour> freeHook;
|
||||
private Hook<OnRealDestroyDelegate> destroyHook;
|
||||
private readonly Hook<OnUpdateDetour> updateHook;
|
||||
private readonly Hook<OnRealDestroyDelegate> destroyHook;
|
||||
|
||||
private Thread? frameworkUpdateThread;
|
||||
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private Framework(GameGui gameGui, GameNetwork gameNetwork, SigScanner sigScanner)
|
||||
private Framework(SigScanner sigScanner)
|
||||
{
|
||||
this.Address = new FrameworkAddressResolver();
|
||||
this.Address.Setup(sigScanner);
|
||||
|
||||
this.updateHook = new Hook<OnUpdateDetour>(this.Address.TickAddress, this.HandleFrameworkUpdate);
|
||||
this.freeHook = new Hook<OnDestroyDetour>(this.Address.FreeAddress, this.HandleFrameworkFree);
|
||||
this.destroyHook = new Hook<OnRealDestroyDelegate>(this.Address.DestroyAddress, this.HandleFrameworkDestroy);
|
||||
this.updateHook = Hook<OnUpdateDetour>.FromAddress(this.Address.TickAddress, this.HandleFrameworkUpdate);
|
||||
this.destroyHook = Hook<OnRealDestroyDelegate>.FromAddress(this.Address.DestroyAddress, this.HandleFrameworkDestroy);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -113,6 +109,11 @@ namespace Dalamud.Game
|
|||
/// </summary>
|
||||
public bool IsInFrameworkUpdateThread => Thread.CurrentThread == this.frameworkUpdateThread;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether game Framework is unloading.
|
||||
/// </summary>
|
||||
public bool IsFrameworkUnloading { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to dispatch update events.
|
||||
/// </summary>
|
||||
|
|
@ -124,7 +125,8 @@ namespace Dalamud.Game
|
|||
/// <typeparam name="T">Return type.</typeparam>
|
||||
/// <param name="func">Function to call.</param>
|
||||
/// <returns>Task representing the pending or already completed function.</returns>
|
||||
public Task<T> RunOnFrameworkThread<T>(Func<T> func) => this.IsInFrameworkUpdateThread ? Task.FromResult(func()) : this.RunOnTick(func);
|
||||
public Task<T> RunOnFrameworkThread<T>(Func<T> func) =>
|
||||
this.IsInFrameworkUpdateThread || this.IsFrameworkUnloading ? Task.FromResult(func()) : this.RunOnTick(func);
|
||||
|
||||
/// <summary>
|
||||
/// Run given function right away if this function has been called from game's Framework.Update thread, or otherwise run on next Framework.Update call.
|
||||
|
|
@ -133,7 +135,7 @@ namespace Dalamud.Game
|
|||
/// <returns>Task representing the pending or already completed function.</returns>
|
||||
public Task RunOnFrameworkThread(Action action)
|
||||
{
|
||||
if (this.IsInFrameworkUpdateThread)
|
||||
if (this.IsInFrameworkUpdateThread || this.IsFrameworkUnloading)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
|
@ -151,6 +153,24 @@ namespace Dalamud.Game
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Run given function right away if this function has been called from game's Framework.Update thread, or otherwise run on next Framework.Update call.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Return type.</typeparam>
|
||||
/// <param name="func">Function to call.</param>
|
||||
/// <returns>Task representing the pending or already completed function.</returns>
|
||||
public Task<T> RunOnFrameworkThread<T>(Func<Task<T>> func) =>
|
||||
this.IsInFrameworkUpdateThread || this.IsFrameworkUnloading ? func() : this.RunOnTick(func);
|
||||
|
||||
/// <summary>
|
||||
/// Run given function right away if this function has been called from game's Framework.Update thread, or otherwise run on next Framework.Update call.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Return type.</typeparam>
|
||||
/// <param name="func">Function to call.</param>
|
||||
/// <returns>Task representing the pending or already completed function.</returns>
|
||||
public Task RunOnFrameworkThread(Func<Task> func) =>
|
||||
this.IsInFrameworkUpdateThread || this.IsFrameworkUnloading ? func() : this.RunOnTick(func);
|
||||
|
||||
/// <summary>
|
||||
/// Run given function in upcoming Framework.Tick call.
|
||||
/// </summary>
|
||||
|
|
@ -162,6 +182,16 @@ namespace Dalamud.Game
|
|||
/// <returns>Task representing the pending function.</returns>
|
||||
public Task<T> RunOnTick<T>(Func<T> func, TimeSpan delay = default, int delayTicks = default, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (this.IsFrameworkUnloading)
|
||||
{
|
||||
if (delay == default && delayTicks == default)
|
||||
return this.RunOnFrameworkThread(func);
|
||||
|
||||
var cts = new CancellationTokenSource();
|
||||
cts.Cancel();
|
||||
return Task.FromCanceled<T>(cts.Token);
|
||||
}
|
||||
|
||||
var tcs = new TaskCompletionSource<T>();
|
||||
this.runOnNextTickTaskList.Add(new RunOnNextTickTaskFunc<T>()
|
||||
{
|
||||
|
|
@ -184,6 +214,16 @@ namespace Dalamud.Game
|
|||
/// <returns>Task representing the pending function.</returns>
|
||||
public Task RunOnTick(Action action, TimeSpan delay = default, int delayTicks = default, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (this.IsFrameworkUnloading)
|
||||
{
|
||||
if (delay == default && delayTicks == default)
|
||||
return this.RunOnFrameworkThread(action);
|
||||
|
||||
var cts = new CancellationTokenSource();
|
||||
cts.Cancel();
|
||||
return Task.FromCanceled(cts.Token);
|
||||
}
|
||||
|
||||
var tcs = new TaskCompletionSource();
|
||||
this.runOnNextTickTaskList.Add(new RunOnNextTickTaskAction()
|
||||
{
|
||||
|
|
@ -196,22 +236,88 @@ namespace Dalamud.Game
|
|||
return tcs.Task;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Run given function in upcoming Framework.Tick call.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Return type.</typeparam>
|
||||
/// <param name="func">Function to call.</param>
|
||||
/// <param name="delay">Wait for given timespan before calling this function.</param>
|
||||
/// <param name="delayTicks">Count given number of Framework.Tick calls before calling this function. This takes precedence over delay parameter.</param>
|
||||
/// <param name="cancellationToken">Cancellation token which will prevent the execution of this function if wait conditions are not met.</param>
|
||||
/// <returns>Task representing the pending function.</returns>
|
||||
public Task<T> RunOnTick<T>(Func<Task<T>> func, TimeSpan delay = default, int delayTicks = default, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (this.IsFrameworkUnloading)
|
||||
{
|
||||
if (delay == default && delayTicks == default)
|
||||
return this.RunOnFrameworkThread(func);
|
||||
|
||||
var cts = new CancellationTokenSource();
|
||||
cts.Cancel();
|
||||
return Task.FromCanceled<T>(cts.Token);
|
||||
}
|
||||
|
||||
var tcs = new TaskCompletionSource<Task<T>>();
|
||||
this.runOnNextTickTaskList.Add(new RunOnNextTickTaskFunc<Task<T>>()
|
||||
{
|
||||
RemainingTicks = delayTicks,
|
||||
RunAfterTickCount = Environment.TickCount64 + (long)Math.Ceiling(delay.TotalMilliseconds),
|
||||
CancellationToken = cancellationToken,
|
||||
TaskCompletionSource = tcs,
|
||||
Func = func,
|
||||
});
|
||||
return tcs.Task.ContinueWith(x => x.Result, cancellationToken).Unwrap();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Run given function in upcoming Framework.Tick call.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Return type.</typeparam>
|
||||
/// <param name="func">Function to call.</param>
|
||||
/// <param name="delay">Wait for given timespan before calling this function.</param>
|
||||
/// <param name="delayTicks">Count given number of Framework.Tick calls before calling this function. This takes precedence over delay parameter.</param>
|
||||
/// <param name="cancellationToken">Cancellation token which will prevent the execution of this function if wait conditions are not met.</param>
|
||||
/// <returns>Task representing the pending function.</returns>
|
||||
public Task RunOnTick(Func<Task> func, TimeSpan delay = default, int delayTicks = default, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (this.IsFrameworkUnloading)
|
||||
{
|
||||
if (delay == default && delayTicks == default)
|
||||
return this.RunOnFrameworkThread(func);
|
||||
|
||||
var cts = new CancellationTokenSource();
|
||||
cts.Cancel();
|
||||
return Task.FromCanceled(cts.Token);
|
||||
}
|
||||
|
||||
var tcs = new TaskCompletionSource<Task>();
|
||||
this.runOnNextTickTaskList.Add(new RunOnNextTickTaskFunc<Task>()
|
||||
{
|
||||
RemainingTicks = delayTicks,
|
||||
RunAfterTickCount = Environment.TickCount64 + (long)Math.Ceiling(delay.TotalMilliseconds),
|
||||
CancellationToken = cancellationToken,
|
||||
TaskCompletionSource = tcs,
|
||||
Func = func,
|
||||
});
|
||||
return tcs.Task.ContinueWith(x => x.Result, cancellationToken).Unwrap();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dispose of managed and unmanaged resources.
|
||||
/// </summary>
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
Service<GameGui>.GetNullable()?.ExplicitDispose();
|
||||
Service<GameNetwork>.GetNullable()?.ExplicitDispose();
|
||||
this.RunOnFrameworkThread(() =>
|
||||
{
|
||||
// ReSharper disable once AccessToDisposedClosure
|
||||
this.updateHook.Disable();
|
||||
|
||||
this.updateHook?.Disable();
|
||||
this.freeHook?.Disable();
|
||||
this.destroyHook?.Disable();
|
||||
Thread.Sleep(500);
|
||||
// ReSharper disable once AccessToDisposedClosure
|
||||
this.destroyHook.Disable();
|
||||
}).Wait();
|
||||
|
||||
this.updateHook?.Dispose();
|
||||
this.freeHook?.Dispose();
|
||||
this.destroyHook?.Dispose();
|
||||
this.updateHook.Dispose();
|
||||
this.destroyHook.Dispose();
|
||||
|
||||
this.updateStopwatch.Reset();
|
||||
statsStopwatch.Reset();
|
||||
|
|
@ -221,7 +327,6 @@ namespace Dalamud.Game
|
|||
private void ContinueConstruction()
|
||||
{
|
||||
this.updateHook.Enable();
|
||||
this.freeHook.Enable();
|
||||
this.destroyHook.Enable();
|
||||
}
|
||||
|
||||
|
|
@ -314,41 +419,21 @@ namespace Dalamud.Game
|
|||
}
|
||||
|
||||
original:
|
||||
return this.updateHook.Original(framework);
|
||||
return this.updateHook.OriginalDisposeSafe(framework);
|
||||
}
|
||||
|
||||
private bool HandleFrameworkDestroy(IntPtr framework)
|
||||
{
|
||||
if (this.DispatchUpdateEvents)
|
||||
{
|
||||
Log.Information("Framework::Destroy!");
|
||||
|
||||
var dalamud = Service<Dalamud>.Get();
|
||||
dalamud.DisposePlugins();
|
||||
|
||||
Log.Information("Framework::Destroy OK!");
|
||||
}
|
||||
|
||||
this.IsFrameworkUnloading = true;
|
||||
this.DispatchUpdateEvents = false;
|
||||
|
||||
return this.destroyHook.Original(framework);
|
||||
}
|
||||
Log.Information("Framework::Destroy!");
|
||||
Service<Dalamud>.Get().Unload();
|
||||
this.runOnNextTickTaskList.RemoveAll(x => x.Run());
|
||||
ServiceManager.UnloadAllServices();
|
||||
Log.Information("Framework::Destroy OK!");
|
||||
|
||||
private IntPtr HandleFrameworkFree()
|
||||
{
|
||||
Log.Information("Framework::Free!");
|
||||
|
||||
// Store the pointer to the original trampoline location
|
||||
var originalPtr = Marshal.GetFunctionPointerForDelegate(this.freeHook.Original);
|
||||
|
||||
var dalamud = Service<Dalamud>.Get();
|
||||
dalamud.Unload();
|
||||
dalamud.WaitForUnloadFinish();
|
||||
|
||||
Log.Information("Framework::Free OK!");
|
||||
|
||||
// Return the original trampoline location to cleanly exit
|
||||
return originalPtr;
|
||||
return this.destroyHook.OriginalDisposeSafe(framework);
|
||||
}
|
||||
|
||||
private abstract class RunOnNextTickTaskBase
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue