Improvements (#903)

This commit is contained in:
kizer 2022-06-29 18:51:40 +09:00 committed by GitHub
parent e9cd7e0273
commit 716736f022
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
55 changed files with 1809 additions and 872 deletions

View file

@ -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