mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-14 20:54:16 +01:00
fix: correctly use dependency order to unload, declare all plugin services as deps to PM
This commit is contained in:
parent
af670d6b11
commit
f032adb155
4 changed files with 222 additions and 26 deletions
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
|
@ -19,6 +20,13 @@ namespace Dalamud;
|
|||
/// </summary>
|
||||
internal static class ServiceManager
|
||||
{
|
||||
/**
|
||||
* TODO:
|
||||
* - Unify dependency walking code(load/unload
|
||||
* - Visualize/output .dot or imgui thing
|
||||
*/
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Static log facility for Service{T}, to avoid duplicate instances for different types.
|
||||
/// </summary>
|
||||
|
|
@ -27,6 +35,38 @@ internal static class ServiceManager
|
|||
private static readonly TaskCompletionSource BlockingServicesLoadedTaskCompletionSource = new();
|
||||
|
||||
private static readonly List<Type> LoadedServices = new();
|
||||
|
||||
/// <summary>
|
||||
/// Kinds of services.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum ServiceKind
|
||||
{
|
||||
/// <summary>
|
||||
/// Not a service.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Regular service.
|
||||
/// </summary>
|
||||
ManualService = 1 << 0,
|
||||
|
||||
/// <summary>
|
||||
/// Service that is loaded asynchronously while the game starts.
|
||||
/// </summary>
|
||||
EarlyLoadedService = 1 << 1,
|
||||
|
||||
/// <summary>
|
||||
/// Service that is loaded before the game starts.
|
||||
/// </summary>
|
||||
BlockingEarlyLoadedService = 1 << 2,
|
||||
|
||||
/// <summary>
|
||||
/// Service that is loaded automatically when the game starts, synchronously or asynchronously.
|
||||
/// </summary>
|
||||
AutoLoadService = EarlyLoadedService | BlockingEarlyLoadedService,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets task that gets completed when all blocking early loading services are done loading.
|
||||
|
|
@ -86,12 +126,34 @@ internal static class ServiceManager
|
|||
|
||||
var dependencyServicesMap = new Dictionary<Type, List<Type>>();
|
||||
var getAsyncTaskMap = new Dictionary<Type, Task>();
|
||||
|
||||
// fill getAsyncTaskMap with services that were provided
|
||||
lock (LoadedServices)
|
||||
{
|
||||
foreach (var loadedService in LoadedServices)
|
||||
{
|
||||
var getTask = (Task)typeof(Service<>)
|
||||
.MakeGenericType(loadedService)
|
||||
.InvokeMember(
|
||||
"GetAsync",
|
||||
BindingFlags.InvokeMethod | BindingFlags.Static | BindingFlags.Public,
|
||||
null,
|
||||
null,
|
||||
null);
|
||||
|
||||
Debug.Assert(getTask != null, "Provided service getTask was null");
|
||||
|
||||
getAsyncTaskMap[typeof(Service<>).MakeGenericType(loadedService)] = getTask;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var serviceType in Assembly.GetExecutingAssembly().GetTypes())
|
||||
{
|
||||
var attr = serviceType.GetCustomAttribute<Service>(true)?.GetType();
|
||||
if (attr?.IsAssignableTo(typeof(EarlyLoadedService)) != true)
|
||||
var serviceKind = serviceType.GetServiceKind();
|
||||
if (serviceKind == ServiceKind.None)
|
||||
continue;
|
||||
|
||||
Debug.Assert(!serviceKind.HasFlag(ServiceKind.ManualService), "Regular services should never end up here");
|
||||
|
||||
var getTask = (Task)typeof(Service<>)
|
||||
.MakeGenericType(serviceType)
|
||||
|
|
@ -102,9 +164,9 @@ internal static class ServiceManager
|
|||
null,
|
||||
null);
|
||||
|
||||
if (attr.IsAssignableTo(typeof(BlockingEarlyLoadedService)))
|
||||
if (serviceKind.HasFlag(ServiceKind.BlockingEarlyLoadedService))
|
||||
{
|
||||
getAsyncTaskMap[serviceType] = getTask;
|
||||
getAsyncTaskMap[typeof(Service<>).MakeGenericType(serviceType)] = getTask;
|
||||
blockingEarlyLoadingServices.Add(serviceType);
|
||||
}
|
||||
else
|
||||
|
|
@ -148,8 +210,29 @@ internal static class ServiceManager
|
|||
{
|
||||
foreach (var serviceType in servicesToLoad)
|
||||
{
|
||||
if (dependencyServicesMap[serviceType].Any(
|
||||
x => getAsyncTaskMap.GetValueOrDefault(x)?.IsCompleted == false))
|
||||
var hasDeps = true;
|
||||
foreach (var dependency in dependencyServicesMap[serviceType])
|
||||
{
|
||||
var depServiceKind = dependency.GetServiceKind();
|
||||
var depResolveTask = getAsyncTaskMap.GetValueOrDefault(dependency);
|
||||
|
||||
if (depResolveTask == null && (depServiceKind.HasFlag(ServiceKind.EarlyLoadedService) || depServiceKind.HasFlag(ServiceKind.BlockingEarlyLoadedService)))
|
||||
{
|
||||
Log.Information("{Type}: {Dependency} has no resolver task, is it early loaded or blocking early loaded?", serviceType.FullName!, dependency.FullName!);
|
||||
Debug.Assert(false, $"No resolver for dependent service {dependency.FullName}");
|
||||
}
|
||||
else if (depResolveTask is { Status: TaskStatus.Created })
|
||||
{
|
||||
depResolveTask.Start();
|
||||
}
|
||||
else if (depResolveTask is { IsCompleted: false })
|
||||
{
|
||||
Log.Verbose("{Type} waiting for {Dependency}", serviceType.FullName!, dependency.FullName!);
|
||||
hasDeps = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasDeps)
|
||||
continue;
|
||||
|
||||
tasks.Add((Task)typeof(Service<>)
|
||||
|
|
@ -227,23 +310,104 @@ internal static class ServiceManager
|
|||
return;
|
||||
}
|
||||
|
||||
lock (LoadedServices)
|
||||
var dependencyServicesMap = new Dictionary<Type, List<Type>>();
|
||||
var allToUnload = new HashSet<Type>();
|
||||
var unloadOrder = new List<Type>();
|
||||
|
||||
Log.Information("==== COLLECTING SERVICES TO UNLOAD ====");
|
||||
|
||||
foreach (var serviceType in Assembly.GetExecutingAssembly().GetTypes())
|
||||
{
|
||||
while (LoadedServices.Any())
|
||||
if (!serviceType.IsAssignableTo(typeof(IServiceType)))
|
||||
continue;
|
||||
|
||||
dependencyServicesMap[serviceType] =
|
||||
((List<Type>)typeof(Service<>)
|
||||
.MakeGenericType(serviceType)
|
||||
.InvokeMember(
|
||||
"GetDependencyServices",
|
||||
BindingFlags.InvokeMethod | BindingFlags.Static | BindingFlags.Public,
|
||||
null,
|
||||
null,
|
||||
null))!
|
||||
.Select(x => x.GetGenericArguments()[0]).ToList();
|
||||
|
||||
/*
|
||||
Log.Verbose("=> Deps for {Type}", serviceType.FullName!);
|
||||
foreach (var dependencyService in dependencyServicesMap[serviceType])
|
||||
{
|
||||
var serviceType = LoadedServices.Last();
|
||||
LoadedServices.RemoveAt(LoadedServices.Count - 1);
|
||||
Log.Verbose("\t\t=> {Type}", dependencyService.FullName!);
|
||||
}
|
||||
*/
|
||||
|
||||
typeof(Service<>)
|
||||
.MakeGenericType(serviceType)
|
||||
allToUnload.Add(serviceType);
|
||||
}
|
||||
|
||||
void UnloadService(Type serviceType)
|
||||
{
|
||||
if (unloadOrder.Contains(serviceType))
|
||||
return;
|
||||
|
||||
var deps = dependencyServicesMap[serviceType];
|
||||
foreach (var dep in deps)
|
||||
{
|
||||
UnloadService(dep);
|
||||
}
|
||||
|
||||
unloadOrder.Add(serviceType);
|
||||
Log.Information("Queue for unload {Type}", serviceType.FullName!);
|
||||
}
|
||||
|
||||
foreach (var serviceType in allToUnload)
|
||||
{
|
||||
UnloadService(serviceType);
|
||||
}
|
||||
|
||||
Log.Information("==== UNLOADING ALL SERVICES ====");
|
||||
|
||||
unloadOrder.Reverse();
|
||||
foreach (var type in unloadOrder)
|
||||
{
|
||||
Log.Verbose("Unload {Type}", type.FullName!);
|
||||
|
||||
typeof(Service<>)
|
||||
.MakeGenericType(type)
|
||||
.InvokeMember(
|
||||
"Unset",
|
||||
BindingFlags.InvokeMethod | BindingFlags.Static | BindingFlags.NonPublic,
|
||||
null,
|
||||
null,
|
||||
null);
|
||||
}
|
||||
}
|
||||
|
||||
lock (LoadedServices)
|
||||
{
|
||||
LoadedServices.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the service type of this type.
|
||||
/// </summary>
|
||||
/// <param name="type">The type to check.</param>
|
||||
/// <returns>The type of service this type is.</returns>
|
||||
public static ServiceKind GetServiceKind(this Type type)
|
||||
{
|
||||
var attr = type.GetCustomAttribute<Service>(true)?.GetType();
|
||||
if (attr == null)
|
||||
return ServiceKind.None;
|
||||
|
||||
Debug.Assert(
|
||||
type.IsAssignableTo(typeof(IServiceType)),
|
||||
"Service did not inherit from IServiceType");
|
||||
|
||||
if (attr.IsAssignableTo(typeof(BlockingEarlyLoadedService)))
|
||||
return ServiceKind.BlockingEarlyLoadedService;
|
||||
|
||||
if (attr.IsAssignableTo(typeof(EarlyLoadedService)))
|
||||
return ServiceKind.EarlyLoadedService;
|
||||
|
||||
return ServiceKind.ManualService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue