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
|
|
@ -15,6 +15,7 @@ using Dalamud.Game.Text.Sanitizer;
|
|||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.GameFonts;
|
||||
using Dalamud.Interface.Internal;
|
||||
using Dalamud.Plugin.Internal;
|
||||
using Dalamud.Plugin.Ipc;
|
||||
|
|
|
|||
|
|
@ -38,6 +38,11 @@ internal partial class PluginManager : IDisposable, IServiceType
|
|||
/// </summary>
|
||||
public const int DalamudApiLevel = 6;
|
||||
|
||||
/// <summary>
|
||||
/// Default time to wait between plugin unload and plugin assembly unload.
|
||||
/// </summary>
|
||||
public const int PluginWaitBeforeFreeDefault = 500;
|
||||
|
||||
private static readonly ModuleLog Log = new("PLUGINM");
|
||||
|
||||
private readonly object pluginListLock = new();
|
||||
|
|
@ -207,16 +212,43 @@ internal partial class PluginManager : IDisposable, IServiceType
|
|||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var plugin in this.InstalledPlugins)
|
||||
if (this.InstalledPlugins.Any())
|
||||
{
|
||||
try
|
||||
// Unload them first, just in case some of plugin codes are still running via callbacks initiated externally.
|
||||
foreach (var plugin in this.InstalledPlugins.Where(plugin => !plugin.Manifest.CanUnloadAsync))
|
||||
{
|
||||
plugin.Dispose();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Error disposing {plugin.Name}");
|
||||
try
|
||||
{
|
||||
plugin.UnloadAsync(true, false).Wait();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Error unloading {plugin.Name}");
|
||||
}
|
||||
}
|
||||
|
||||
Task.WaitAll(this.InstalledPlugins
|
||||
.Where(plugin => plugin.Manifest.CanUnloadAsync)
|
||||
.Select(plugin => Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await plugin.UnloadAsync(true, false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Error unloading {plugin.Name}");
|
||||
}
|
||||
})).ToArray());
|
||||
|
||||
// Just in case plugins still have tasks running that they didn't cancel when they should have,
|
||||
// give them some time to complete it.
|
||||
Thread.Sleep(this.configuration.PluginWaitBeforeFree ?? PluginWaitBeforeFreeDefault);
|
||||
|
||||
// Now that we've waited enough, dispose the whole plugin.
|
||||
// Since plugins should have been unloaded above, this should be done quickly.
|
||||
foreach (var plugin in this.InstalledPlugins)
|
||||
plugin.ExplicitDisposeIgnoreExceptions($"Error disposing {plugin.Name}", Log);
|
||||
}
|
||||
|
||||
this.assemblyLocationMonoHook?.Dispose();
|
||||
|
|
@ -891,7 +923,7 @@ internal partial class PluginManager : IDisposable, IServiceType
|
|||
{
|
||||
try
|
||||
{
|
||||
plugin.Unload();
|
||||
await plugin.UnloadAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
@ -963,23 +995,30 @@ internal partial class PluginManager : IDisposable, IServiceType
|
|||
/// </summary>
|
||||
/// <param name="plugin">The plugin.</param>
|
||||
/// <exception cref="Exception">Throws if the plugin is still loading/unloading.</exception>
|
||||
public void DeleteConfiguration(LocalPlugin plugin)
|
||||
/// <returns>The task.</returns>
|
||||
public async Task DeleteConfigurationAsync(LocalPlugin plugin)
|
||||
{
|
||||
if (plugin.State == PluginState.InProgress)
|
||||
if (plugin.State == PluginState.Loading || plugin.State == PluginState.Unloaded)
|
||||
throw new Exception("Cannot delete configuration for a loading/unloading plugin");
|
||||
|
||||
if (plugin.IsLoaded)
|
||||
plugin.Unload();
|
||||
await plugin.UnloadAsync();
|
||||
|
||||
// Let's wait so any handles on files in plugin configurations can be closed
|
||||
Thread.Sleep(500);
|
||||
|
||||
this.PluginConfigs.Delete(plugin.Name);
|
||||
|
||||
Thread.Sleep(500);
|
||||
for (var waitUntil = Environment.TickCount64 + 1000; Environment.TickCount64 < waitUntil;)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.PluginConfigs.Delete(plugin.Name);
|
||||
break;
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
await Task.Delay(100);
|
||||
}
|
||||
}
|
||||
|
||||
// Let's indicate "installer" here since this is supposed to be a fresh install
|
||||
plugin.LoadAsync(PluginLoadReason.Installer).Wait();
|
||||
await plugin.LoadAsync(PluginLoadReason.Installer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -2,10 +2,13 @@ using System;
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Dalamud.Configuration.Internal;
|
||||
using Dalamud.Game;
|
||||
using Dalamud.Game.Gui.Dtr;
|
||||
using Dalamud.Interface.GameFonts;
|
||||
using Dalamud.Interface.Internal;
|
||||
using Dalamud.IoC.Internal;
|
||||
using Dalamud.Logging.Internal;
|
||||
using Dalamud.Plugin.Internal.Exceptions;
|
||||
|
|
@ -27,6 +30,8 @@ internal class LocalPlugin : IDisposable
|
|||
private readonly FileInfo disabledFile;
|
||||
private readonly FileInfo testingFile;
|
||||
|
||||
private readonly SemaphoreSlim pluginLoadStateLock = new(1);
|
||||
|
||||
private PluginLoader? loader;
|
||||
private Assembly? pluginAssembly;
|
||||
private Type? pluginType;
|
||||
|
|
@ -208,8 +213,20 @@ internal class LocalPlugin : IDisposable
|
|||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
this.instance?.Dispose();
|
||||
this.instance = null;
|
||||
var framework = Service<Framework>.GetNullable();
|
||||
var configuration = Service<DalamudConfiguration>.Get();
|
||||
|
||||
var didPluginDispose = false;
|
||||
if (this.instance != null)
|
||||
{
|
||||
didPluginDispose = true;
|
||||
if (this.Manifest.CanUnloadAsync || framework == null)
|
||||
this.instance.Dispose();
|
||||
else
|
||||
framework.RunOnFrameworkThread(() => this.instance.Dispose()).Wait();
|
||||
|
||||
this.instance = null;
|
||||
}
|
||||
|
||||
this.DalamudInterface?.ExplicitDispose();
|
||||
this.DalamudInterface = null;
|
||||
|
|
@ -217,6 +234,8 @@ internal class LocalPlugin : IDisposable
|
|||
this.pluginType = null;
|
||||
this.pluginAssembly = null;
|
||||
|
||||
if (this.loader != null && didPluginDispose)
|
||||
Thread.Sleep(configuration.PluginWaitBeforeFree ?? PluginManager.PluginWaitBeforeFreeDefault);
|
||||
this.loader?.Dispose();
|
||||
}
|
||||
|
||||
|
|
@ -228,54 +247,73 @@ internal class LocalPlugin : IDisposable
|
|||
/// <returns>A task.</returns>
|
||||
public async Task LoadAsync(PluginLoadReason reason, bool reloading = false)
|
||||
{
|
||||
var startInfo = Service<DalamudStartInfo>.Get();
|
||||
var configuration = Service<DalamudConfiguration>.Get();
|
||||
var pluginManager = Service<PluginManager>.Get();
|
||||
var configuration = await Service<DalamudConfiguration>.GetAsync();
|
||||
var framework = await Service<Framework>.GetAsync();
|
||||
var ioc = await Service<ServiceContainer>.GetAsync();
|
||||
var pluginManager = await Service<PluginManager>.GetAsync();
|
||||
var startInfo = await Service<DalamudStartInfo>.GetAsync();
|
||||
|
||||
// Allowed: Unloaded
|
||||
switch (this.State)
|
||||
{
|
||||
case PluginState.InProgress:
|
||||
throw new InvalidPluginOperationException($"Unable to load {this.Name}, already working");
|
||||
case PluginState.Loaded:
|
||||
throw new InvalidPluginOperationException($"Unable to load {this.Name}, already loaded");
|
||||
case PluginState.LoadError:
|
||||
throw new InvalidPluginOperationException($"Unable to load {this.Name}, load previously faulted, unload first");
|
||||
case PluginState.UnloadError:
|
||||
throw new InvalidPluginOperationException($"Unable to load {this.Name}, unload previously faulted, restart Dalamud");
|
||||
case PluginState.Unloaded:
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(this.State.ToString());
|
||||
}
|
||||
// UiBuilder constructor requires the following two.
|
||||
await Service<InterfaceManager>.GetAsync();
|
||||
await Service<GameFontManager>.GetAsync();
|
||||
|
||||
if (pluginManager.IsManifestBanned(this.Manifest))
|
||||
throw new BannedPluginException($"Unable to load {this.Name}, banned");
|
||||
|
||||
if (this.Manifest.ApplicableVersion < startInfo.GameVersion)
|
||||
throw new InvalidPluginOperationException($"Unable to load {this.Name}, no applicable version");
|
||||
|
||||
if (this.Manifest.DalamudApiLevel < PluginManager.DalamudApiLevel && !configuration.LoadAllApiLevels)
|
||||
throw new InvalidPluginOperationException($"Unable to load {this.Name}, incompatible API level");
|
||||
|
||||
if (this.Manifest.Disabled)
|
||||
throw new InvalidPluginOperationException($"Unable to load {this.Name}, disabled");
|
||||
|
||||
this.State = PluginState.InProgress;
|
||||
Log.Information($"Loading {this.DllFile.Name}");
|
||||
|
||||
if (this.DllFile.DirectoryName != null && File.Exists(Path.Combine(this.DllFile.DirectoryName, "Dalamud.dll")))
|
||||
{
|
||||
Log.Error("==== IMPORTANT MESSAGE TO {0}, THE DEVELOPER OF {1} ====", this.Manifest.Author!, this.Manifest.InternalName);
|
||||
Log.Error("YOU ARE INCLUDING DALAMUD DEPENDENCIES IN YOUR BUILDS!!!");
|
||||
Log.Error("You may not be able to load your plugin. \"<Private>False</Private>\" needs to be set in your csproj.");
|
||||
Log.Error("If you are using ILMerge, do not merge anything other than your direct dependencies.");
|
||||
Log.Error("Do not merge FFXIVClientStructs.Generators.dll.");
|
||||
Log.Error("Please refer to https://github.com/goatcorp/Dalamud/discussions/603 for more information.");
|
||||
}
|
||||
if (this.Manifest.LoadRequiredState == 0)
|
||||
_ = await Service<InterfaceManager.InterfaceManagerWithScene>.GetAsync();
|
||||
|
||||
await this.pluginLoadStateLock.WaitAsync();
|
||||
try
|
||||
{
|
||||
switch (this.State)
|
||||
{
|
||||
case PluginState.Loaded:
|
||||
throw new InvalidPluginOperationException($"Unable to load {this.Name}, already loaded");
|
||||
case PluginState.LoadError:
|
||||
throw new InvalidPluginOperationException(
|
||||
$"Unable to load {this.Name}, load previously faulted, unload first");
|
||||
case PluginState.UnloadError:
|
||||
throw new InvalidPluginOperationException(
|
||||
$"Unable to load {this.Name}, unload previously faulted, restart Dalamud");
|
||||
case PluginState.Unloaded:
|
||||
break;
|
||||
case PluginState.Loading:
|
||||
case PluginState.Unloading:
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(this.State.ToString());
|
||||
}
|
||||
|
||||
if (pluginManager.IsManifestBanned(this.Manifest))
|
||||
throw new BannedPluginException($"Unable to load {this.Name}, banned");
|
||||
|
||||
if (this.Manifest.ApplicableVersion < startInfo.GameVersion)
|
||||
throw new InvalidPluginOperationException($"Unable to load {this.Name}, no applicable version");
|
||||
|
||||
if (this.Manifest.DalamudApiLevel < PluginManager.DalamudApiLevel && !configuration.LoadAllApiLevels)
|
||||
throw new InvalidPluginOperationException($"Unable to load {this.Name}, incompatible API level");
|
||||
|
||||
if (this.Manifest.Disabled)
|
||||
throw new InvalidPluginOperationException($"Unable to load {this.Name}, disabled");
|
||||
|
||||
this.State = PluginState.Loading;
|
||||
Log.Information($"Loading {this.DllFile.Name}");
|
||||
|
||||
if (this.DllFile.DirectoryName != null &&
|
||||
File.Exists(Path.Combine(this.DllFile.DirectoryName, "Dalamud.dll")))
|
||||
{
|
||||
Log.Error(
|
||||
"==== IMPORTANT MESSAGE TO {0}, THE DEVELOPER OF {1} ====",
|
||||
this.Manifest.Author!,
|
||||
this.Manifest.InternalName);
|
||||
Log.Error(
|
||||
"YOU ARE INCLUDING DALAMUD DEPENDENCIES IN YOUR BUILDS!!!");
|
||||
Log.Error(
|
||||
"You may not be able to load your plugin. \"<Private>False</Private>\" needs to be set in your csproj.");
|
||||
Log.Error(
|
||||
"If you are using ILMerge, do not merge anything other than your direct dependencies.");
|
||||
Log.Error("Do not merge FFXIVClientStructs.Generators.dll.");
|
||||
Log.Error(
|
||||
"Please refer to https://github.com/goatcorp/Dalamud/discussions/603 for more information.");
|
||||
}
|
||||
|
||||
this.loader ??= PluginLoader.CreateFromAssemblyFile(this.DllFile.FullName, SetupLoaderConfig);
|
||||
|
||||
if (reloading || this.IsDev)
|
||||
|
|
@ -309,7 +347,8 @@ internal class LocalPlugin : IDisposable
|
|||
this.AssemblyName = this.pluginAssembly.GetName();
|
||||
|
||||
// Find the plugin interface implementation. It is guaranteed to exist after checking in the ctor.
|
||||
this.pluginType ??= this.pluginAssembly.GetTypes().First(type => type.IsAssignableTo(typeof(IDalamudPlugin)));
|
||||
this.pluginType ??= this.pluginAssembly.GetTypes()
|
||||
.First(type => type.IsAssignableTo(typeof(IDalamudPlugin)));
|
||||
|
||||
// Check for any loaded plugins with the same assembly name
|
||||
var assemblyName = this.pluginAssembly.GetName().Name;
|
||||
|
|
@ -319,7 +358,8 @@ internal class LocalPlugin : IDisposable
|
|||
if (otherPlugin == this || otherPlugin.instance == null)
|
||||
continue;
|
||||
|
||||
var otherPluginAssemblyName = otherPlugin.instance.GetType().Assembly.GetName().Name;
|
||||
var otherPluginAssemblyName =
|
||||
otherPlugin.instance.GetType().Assembly.GetName().Name;
|
||||
if (otherPluginAssemblyName == assemblyName && otherPluginAssemblyName != null)
|
||||
{
|
||||
this.State = PluginState.Unloaded;
|
||||
|
|
@ -330,17 +370,29 @@ internal class LocalPlugin : IDisposable
|
|||
}
|
||||
|
||||
// Update the location for the Location and CodeBase patches
|
||||
PluginManager.PluginLocations[this.pluginType.Assembly.FullName] = new PluginPatchData(this.DllFile);
|
||||
PluginManager.PluginLocations[this.pluginType.Assembly.FullName] =
|
||||
new PluginPatchData(this.DllFile);
|
||||
|
||||
this.DalamudInterface = new DalamudPluginInterface(this.pluginAssembly.GetName().Name!, this.DllFile, reason, this.IsDev);
|
||||
this.DalamudInterface =
|
||||
new DalamudPluginInterface(this.pluginAssembly.GetName().Name!, this.DllFile, reason, this.IsDev);
|
||||
|
||||
if (this.Manifest.LoadSync && this.Manifest.LoadRequiredState is 0 or 1)
|
||||
{
|
||||
this.instance = await framework.RunOnFrameworkThread(
|
||||
() => ioc.CreateAsync(this.pluginType!, this.DalamudInterface!)) as IDalamudPlugin;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.instance =
|
||||
await ioc.CreateAsync(this.pluginType!, this.DalamudInterface!) as IDalamudPlugin;
|
||||
}
|
||||
|
||||
var ioc = Service<ServiceContainer>.Get();
|
||||
this.instance = await ioc.CreateAsync(this.pluginType, this.DalamudInterface) as IDalamudPlugin;
|
||||
if (this.instance == null)
|
||||
{
|
||||
this.State = PluginState.LoadError;
|
||||
this.DalamudInterface.ExplicitDispose();
|
||||
Log.Error($"Error while loading {this.Name}, failed to bind and call the plugin constructor");
|
||||
Log.Error(
|
||||
$"Error while loading {this.Name}, failed to bind and call the plugin constructor");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -363,6 +415,10 @@ internal class LocalPlugin : IDisposable
|
|||
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
this.pluginLoadStateLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -370,31 +426,40 @@ internal class LocalPlugin : IDisposable
|
|||
/// in the plugin list until it has been actually disposed.
|
||||
/// </summary>
|
||||
/// <param name="reloading">Unload while reloading.</param>
|
||||
public void Unload(bool reloading = false)
|
||||
/// <param name="waitBeforeLoaderDispose">Wait before disposing loader.</param>
|
||||
/// <returns>The task.</returns>
|
||||
public async Task UnloadAsync(bool reloading = false, bool waitBeforeLoaderDispose = true)
|
||||
{
|
||||
// Allowed: Loaded, LoadError(we are cleaning this up while we're at it)
|
||||
switch (this.State)
|
||||
{
|
||||
case PluginState.InProgress:
|
||||
throw new InvalidPluginOperationException($"Unable to unload {this.Name}, already working");
|
||||
case PluginState.Unloaded:
|
||||
throw new InvalidPluginOperationException($"Unable to unload {this.Name}, already unloaded");
|
||||
case PluginState.UnloadError:
|
||||
throw new InvalidPluginOperationException($"Unable to unload {this.Name}, unload previously faulted, restart Dalamud");
|
||||
case PluginState.Loaded:
|
||||
break;
|
||||
case PluginState.LoadError:
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(this.State.ToString());
|
||||
}
|
||||
var configuration = Service<DalamudConfiguration>.Get();
|
||||
var framework = Service<Framework>.GetNullable();
|
||||
|
||||
await this.pluginLoadStateLock.WaitAsync();
|
||||
try
|
||||
{
|
||||
this.State = PluginState.InProgress;
|
||||
switch (this.State)
|
||||
{
|
||||
case PluginState.Unloaded:
|
||||
throw new InvalidPluginOperationException($"Unable to unload {this.Name}, already unloaded");
|
||||
case PluginState.UnloadError:
|
||||
throw new InvalidPluginOperationException(
|
||||
$"Unable to unload {this.Name}, unload previously faulted, restart Dalamud");
|
||||
case PluginState.Loaded:
|
||||
case PluginState.LoadError:
|
||||
break;
|
||||
case PluginState.Loading:
|
||||
case PluginState.Unloading:
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(this.State.ToString());
|
||||
}
|
||||
|
||||
this.State = PluginState.Unloading;
|
||||
Log.Information($"Unloading {this.DllFile.Name}");
|
||||
|
||||
this.instance?.Dispose();
|
||||
if (this.Manifest.CanUnloadAsync || framework == null)
|
||||
this.instance?.Dispose();
|
||||
else
|
||||
await framework.RunOnFrameworkThread(() => this.instance?.Dispose());
|
||||
|
||||
this.instance = null;
|
||||
|
||||
this.DalamudInterface?.ExplicitDispose();
|
||||
|
|
@ -405,6 +470,8 @@ internal class LocalPlugin : IDisposable
|
|||
|
||||
if (!reloading)
|
||||
{
|
||||
if (waitBeforeLoaderDispose && this.loader != null)
|
||||
await Task.Delay(configuration.PluginWaitBeforeFree ?? PluginManager.PluginWaitBeforeFreeDefault);
|
||||
this.loader?.Dispose();
|
||||
this.loader = null;
|
||||
}
|
||||
|
|
@ -419,6 +486,13 @@ internal class LocalPlugin : IDisposable
|
|||
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
// We need to handle removed DTR nodes here, as otherwise, plugins will not be able to re-add their bar entries after updates.
|
||||
Service<DtrBar>.GetNullable()?.HandleRemovedNodes();
|
||||
|
||||
this.pluginLoadStateLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -427,12 +501,7 @@ internal class LocalPlugin : IDisposable
|
|||
/// <returns>A task.</returns>
|
||||
public async Task ReloadAsync()
|
||||
{
|
||||
this.Unload(true);
|
||||
|
||||
// We need to handle removed DTR nodes here, as otherwise, plugins will not be able to re-add their bar entries after updates.
|
||||
var dtr = Service<DtrBar>.Get();
|
||||
dtr.HandleRemovedNodes();
|
||||
|
||||
await this.UnloadAsync(true);
|
||||
await this.LoadAsync(PluginLoadReason.Reload, true);
|
||||
}
|
||||
|
||||
|
|
@ -444,7 +513,8 @@ internal class LocalPlugin : IDisposable
|
|||
// Allowed: Unloaded, UnloadError
|
||||
switch (this.State)
|
||||
{
|
||||
case PluginState.InProgress:
|
||||
case PluginState.Loading:
|
||||
case PluginState.Unloading:
|
||||
case PluginState.Loaded:
|
||||
case PluginState.LoadError:
|
||||
throw new InvalidPluginOperationException($"Unable to enable {this.Name}, still loaded");
|
||||
|
|
@ -471,7 +541,8 @@ internal class LocalPlugin : IDisposable
|
|||
// Allowed: Unloaded, UnloadError
|
||||
switch (this.State)
|
||||
{
|
||||
case PluginState.InProgress:
|
||||
case PluginState.Loading:
|
||||
case PluginState.Unloading:
|
||||
case PluginState.Loaded:
|
||||
case PluginState.LoadError:
|
||||
throw new InvalidPluginOperationException($"Unable to disable {this.Name}, still loaded");
|
||||
|
|
|
|||
|
|
@ -156,6 +156,12 @@ internal record PluginManifest
|
|||
[JsonProperty]
|
||||
public int LoadPriority { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the plugin can be unloaded asynchronously.
|
||||
/// </summary>
|
||||
[JsonProperty]
|
||||
public bool CanUnloadAsync { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of screenshot image URLs to show in the plugin installer.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -16,9 +16,9 @@ internal enum PluginState
|
|||
UnloadError,
|
||||
|
||||
/// <summary>
|
||||
/// Currently loading.
|
||||
/// Currently unloading.
|
||||
/// </summary>
|
||||
InProgress,
|
||||
Unloading,
|
||||
|
||||
/// <summary>
|
||||
/// Load is successful.
|
||||
|
|
@ -29,4 +29,9 @@ internal enum PluginState
|
|||
/// Plugin has thrown an error during loading.
|
||||
/// </summary>
|
||||
LoadError,
|
||||
|
||||
/// <summary>
|
||||
/// Currently loading.
|
||||
/// </summary>
|
||||
Loading,
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue