DI: Load entire DI container in the background
This commit is contained in:
parent
dbe6abd1db
commit
8d17c02186
@ -16,7 +16,6 @@ using Pal.Client.Configuration;
|
|||||||
using Pal.Client.Configuration.Legacy;
|
using Pal.Client.Configuration.Legacy;
|
||||||
using Pal.Client.Database;
|
using Pal.Client.Database;
|
||||||
using Pal.Client.DependencyInjection;
|
using Pal.Client.DependencyInjection;
|
||||||
using Pal.Client.Properties;
|
|
||||||
using Pal.Client.Windows;
|
using Pal.Client.Windows;
|
||||||
|
|
||||||
namespace Pal.Client
|
namespace Pal.Client
|
||||||
@ -25,80 +24,56 @@ namespace Pal.Client
|
|||||||
/// Takes care of async plugin init - this is mostly everything that requires either the config or the database to
|
/// Takes care of async plugin init - this is mostly everything that requires either the config or the database to
|
||||||
/// be available.
|
/// be available.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal sealed class DependencyInjectionLoader
|
internal sealed class DependencyContextInitializer
|
||||||
{
|
{
|
||||||
private readonly ILogger<DependencyInjectionLoader> _logger;
|
private readonly ILogger<DependencyContextInitializer> _logger;
|
||||||
private readonly IServiceProvider _serviceProvider;
|
private readonly IServiceProvider _serviceProvider;
|
||||||
|
|
||||||
public DependencyInjectionLoader(ILogger<DependencyInjectionLoader> logger, IServiceProvider serviceProvider)
|
public DependencyContextInitializer(ILogger<DependencyContextInitializer> logger, IServiceProvider serviceProvider)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_serviceProvider = serviceProvider;
|
_serviceProvider = serviceProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ELoadState LoadState { get; private set; } = ELoadState.Initializing;
|
|
||||||
|
|
||||||
public event Action<Action?>? InitCompleted;
|
|
||||||
|
|
||||||
public async Task InitializeAsync(CancellationToken cancellationToken)
|
public async Task InitializeAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
using IDisposable? logScope = _logger.BeginScope("AsyncInit");
|
using IDisposable? logScope = _logger.BeginScope("AsyncInit");
|
||||||
|
|
||||||
Chat? chat = null;
|
_logger.LogInformation("Starting async init");
|
||||||
try
|
|
||||||
{
|
|
||||||
_logger.LogInformation("Starting async init");
|
|
||||||
chat = _serviceProvider.GetService<Chat>();
|
|
||||||
|
|
||||||
await RemoveOldBackups();
|
await RemoveOldBackups();
|
||||||
await CreateBackups();
|
await CreateBackups();
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
await RunMigrations(cancellationToken);
|
await RunMigrations(cancellationToken);
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
await RunCleanup(_logger);
|
await RunCleanup();
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
// v1 migration: config migration for import history, json migration for markers
|
// v1 migration: config migration for import history, json migration for markers
|
||||||
_serviceProvider.GetRequiredService<ConfigurationManager>().Migrate();
|
_serviceProvider.GetRequiredService<ConfigurationManager>().Migrate();
|
||||||
await _serviceProvider.GetRequiredService<JsonMigration>().MigrateAsync(cancellationToken);
|
await _serviceProvider.GetRequiredService<JsonMigration>().MigrateAsync(cancellationToken);
|
||||||
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
// windows that have logic to open on startup
|
// windows that have logic to open on startup
|
||||||
_serviceProvider.GetRequiredService<AgreementWindow>();
|
_serviceProvider.GetRequiredService<AgreementWindow>();
|
||||||
|
|
||||||
// initialize components that are mostly self-contained/self-registered
|
// initialize components that are mostly self-contained/self-registered
|
||||||
_serviceProvider.GetRequiredService<Hooks>();
|
_serviceProvider.GetRequiredService<Hooks>();
|
||||||
_serviceProvider.GetRequiredService<FrameworkService>();
|
_serviceProvider.GetRequiredService<FrameworkService>();
|
||||||
_serviceProvider.GetRequiredService<ChatService>();
|
_serviceProvider.GetRequiredService<ChatService>();
|
||||||
|
|
||||||
// eager load any commands to find errors now, not when running them
|
// eager load any commands to find errors now, not when running them
|
||||||
_serviceProvider.GetRequiredService<PalConfigCommand>();
|
_serviceProvider.GetRequiredService<PalConfigCommand>();
|
||||||
_serviceProvider.GetRequiredService<PalNearCommand>();
|
_serviceProvider.GetRequiredService<PalNearCommand>();
|
||||||
_serviceProvider.GetRequiredService<PalStatsCommand>();
|
_serviceProvider.GetRequiredService<PalStatsCommand>();
|
||||||
_serviceProvider.GetRequiredService<PalTestConnectionCommand>();
|
_serviceProvider.GetRequiredService<PalTestConnectionCommand>();
|
||||||
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
LoadState = ELoadState.Loaded;
|
_logger.LogInformation("Async init complete");
|
||||||
InitCompleted?.Invoke(null);
|
|
||||||
_logger.LogInformation("Async init complete");
|
|
||||||
}
|
|
||||||
catch (ObjectDisposedException)
|
|
||||||
{
|
|
||||||
InitCompleted?.Invoke(null);
|
|
||||||
LoadState = ELoadState.Error;
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
_logger.LogError(e, "Async load failed");
|
|
||||||
InitCompleted?.Invoke(() =>
|
|
||||||
chat?.Error(string.Format(Localization.Error_LoadFailed, $"{e.GetType()} - {e.Message}")));
|
|
||||||
|
|
||||||
LoadState = ELoadState.Error;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task RemoveOldBackups()
|
private async Task RemoveOldBackups()
|
||||||
@ -186,7 +161,7 @@ namespace Pal.Client
|
|||||||
_logger.LogInformation("Completed database migrations");
|
_logger.LogInformation("Completed database migrations");
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task RunCleanup(ILogger<DependencyInjectionLoader> logger)
|
private async Task RunCleanup()
|
||||||
{
|
{
|
||||||
await using var scope = _serviceProvider.CreateAsyncScope();
|
await using var scope = _serviceProvider.CreateAsyncScope();
|
||||||
await using var dbContext = scope.ServiceProvider.GetRequiredService<PalClientContext>();
|
await using var dbContext = scope.ServiceProvider.GetRequiredService<PalClientContext>();
|
||||||
@ -196,13 +171,5 @@ namespace Pal.Client
|
|||||||
|
|
||||||
await dbContext.SaveChangesAsync();
|
await dbContext.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public enum ELoadState
|
|
||||||
{
|
|
||||||
Initializing,
|
|
||||||
Loaded,
|
|
||||||
Error
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,6 +1,5 @@
|
|||||||
using System.Globalization;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading;
|
|
||||||
using Dalamud.Data;
|
using Dalamud.Data;
|
||||||
using Dalamud.Game;
|
using Dalamud.Game;
|
||||||
using Dalamud.Game.ClientState;
|
using Dalamud.Game.ClientState;
|
||||||
@ -32,20 +31,18 @@ namespace Pal.Client
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// DI-aware Plugin.
|
/// DI-aware Plugin.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
// ReSharper disable once UnusedType.Global
|
internal sealed class DependencyInjectionContext : IDisposable
|
||||||
internal sealed class DependencyInjectionContext : IDalamudPlugin
|
|
||||||
{
|
{
|
||||||
public static DalamudLoggerProvider LoggerProvider { get; } = new();
|
public static DalamudLoggerProvider LoggerProvider { get; } = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initialized as temporary logger, will be overriden once context is ready with a logger that supports scopes.
|
/// Initialized as temporary logger, will be overriden once context is ready with a logger that supports scopes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly ILogger _logger = LoggerProvider.CreateLogger<DependencyInjectionContext>();
|
private ILogger _logger = LoggerProvider.CreateLogger<DependencyInjectionContext>();
|
||||||
|
|
||||||
private readonly string _sqliteConnectionString;
|
private readonly string _sqliteConnectionString;
|
||||||
private readonly CancellationTokenSource _initCts = new();
|
private readonly ServiceCollection _serviceCollection = new();
|
||||||
private ServiceProvider? _serviceProvider;
|
private ServiceProvider? _serviceProvider;
|
||||||
private Plugin? _plugin;
|
|
||||||
|
|
||||||
public string Name => Localization.Palace_Pal;
|
public string Name => Localization.Palace_Pal;
|
||||||
|
|
||||||
@ -58,9 +55,10 @@ namespace Pal.Client
|
|||||||
Framework framework,
|
Framework framework,
|
||||||
Condition condition,
|
Condition condition,
|
||||||
CommandManager commandManager,
|
CommandManager commandManager,
|
||||||
DataManager dataManager)
|
DataManager dataManager,
|
||||||
|
Plugin plugin)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Building service container for {Assembly}",
|
_logger.LogInformation("Building dalamud service container for {Assembly}",
|
||||||
typeof(DependencyInjectionContext).Assembly.FullName);
|
typeof(DependencyInjectionContext).Assembly.FullName);
|
||||||
|
|
||||||
// set up legacy services
|
// set up legacy services
|
||||||
@ -69,8 +67,7 @@ namespace Pal.Client
|
|||||||
#pragma warning restore CS0612
|
#pragma warning restore CS0612
|
||||||
|
|
||||||
// set up logging
|
// set up logging
|
||||||
IServiceCollection services = new ServiceCollection();
|
_serviceCollection.AddLogging(builder =>
|
||||||
services.AddLogging(builder =>
|
|
||||||
builder.AddFilter("Pal", LogLevel.Trace)
|
builder.AddFilter("Pal", LogLevel.Trace)
|
||||||
.AddFilter("Microsoft.EntityFrameworkCore.Database", LogLevel.Warning)
|
.AddFilter("Microsoft.EntityFrameworkCore.Database", LogLevel.Warning)
|
||||||
.AddFilter("Grpc", LogLevel.Debug)
|
.AddFilter("Grpc", LogLevel.Debug)
|
||||||
@ -78,70 +75,78 @@ namespace Pal.Client
|
|||||||
.AddProvider(LoggerProvider));
|
.AddProvider(LoggerProvider));
|
||||||
|
|
||||||
// dalamud
|
// dalamud
|
||||||
services.AddSingleton<IDalamudPlugin>(this);
|
_serviceCollection.AddSingleton<IDalamudPlugin>(plugin);
|
||||||
services.AddSingleton(pluginInterface);
|
_serviceCollection.AddSingleton(pluginInterface);
|
||||||
services.AddSingleton(clientState);
|
_serviceCollection.AddSingleton(clientState);
|
||||||
services.AddSingleton(gameGui);
|
_serviceCollection.AddSingleton(gameGui);
|
||||||
services.AddSingleton(chatGui);
|
_serviceCollection.AddSingleton(chatGui);
|
||||||
services.AddSingleton<Chat>();
|
_serviceCollection.AddSingleton<Chat>();
|
||||||
services.AddSingleton(objectTable);
|
_serviceCollection.AddSingleton(objectTable);
|
||||||
services.AddSingleton(framework);
|
_serviceCollection.AddSingleton(framework);
|
||||||
services.AddSingleton(condition);
|
_serviceCollection.AddSingleton(condition);
|
||||||
services.AddSingleton(commandManager);
|
_serviceCollection.AddSingleton(commandManager);
|
||||||
services.AddSingleton(dataManager);
|
_serviceCollection.AddSingleton(dataManager);
|
||||||
services.AddSingleton(new WindowSystem(typeof(DependencyInjectionContext).AssemblyQualifiedName));
|
_serviceCollection.AddSingleton(new WindowSystem(typeof(DependencyInjectionContext).AssemblyQualifiedName));
|
||||||
|
|
||||||
// EF core
|
|
||||||
_sqliteConnectionString =
|
_sqliteConnectionString =
|
||||||
$"Data Source={Path.Join(pluginInterface.GetPluginConfigDirectory(), "palace-pal.data.sqlite3")}";
|
$"Data Source={Path.Join(pluginInterface.GetPluginConfigDirectory(), "palace-pal.data.sqlite3")}";
|
||||||
services.AddDbContext<PalClientContext>(o => o.UseSqlite(_sqliteConnectionString));
|
}
|
||||||
services.AddTransient<JsonMigration>();
|
|
||||||
services.AddScoped<Cleanup>();
|
public IServiceProvider BuildServiceContainer()
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Building async service container for {Assembly}",
|
||||||
|
typeof(DependencyInjectionContext).Assembly.FullName);
|
||||||
|
|
||||||
|
// EF core
|
||||||
|
_serviceCollection.AddDbContext<PalClientContext>(o => o.UseSqlite(_sqliteConnectionString));
|
||||||
|
_serviceCollection.AddTransient<JsonMigration>();
|
||||||
|
_serviceCollection.AddScoped<Cleanup>();
|
||||||
|
|
||||||
// plugin-specific
|
// plugin-specific
|
||||||
services.AddScoped<DependencyInjectionLoader>();
|
_serviceCollection.AddScoped<DependencyContextInitializer>();
|
||||||
services.AddScoped<DebugState>();
|
_serviceCollection.AddScoped<DebugState>();
|
||||||
services.AddScoped<Hooks>();
|
_serviceCollection.AddScoped<Hooks>();
|
||||||
services.AddScoped<RemoteApi>();
|
_serviceCollection.AddScoped<RemoteApi>();
|
||||||
services.AddScoped<ConfigurationManager>();
|
_serviceCollection.AddScoped<ConfigurationManager>();
|
||||||
services.AddScoped<IPalacePalConfiguration>(sp => sp.GetRequiredService<ConfigurationManager>().Load());
|
_serviceCollection.AddScoped<IPalacePalConfiguration>(sp =>
|
||||||
services.AddTransient<RepoVerification>();
|
sp.GetRequiredService<ConfigurationManager>().Load());
|
||||||
|
_serviceCollection.AddTransient<RepoVerification>();
|
||||||
|
|
||||||
// commands
|
// commands
|
||||||
services.AddScoped<PalConfigCommand>();
|
_serviceCollection.AddScoped<PalConfigCommand>();
|
||||||
services.AddScoped<PalNearCommand>();
|
_serviceCollection.AddScoped<PalNearCommand>();
|
||||||
services.AddScoped<PalStatsCommand>();
|
_serviceCollection.AddScoped<PalStatsCommand>();
|
||||||
services.AddScoped<PalTestConnectionCommand>();
|
_serviceCollection.AddScoped<PalTestConnectionCommand>();
|
||||||
|
|
||||||
// territory & marker related services
|
// territory & marker related services
|
||||||
services.AddScoped<TerritoryState>();
|
_serviceCollection.AddScoped<TerritoryState>();
|
||||||
services.AddScoped<FrameworkService>();
|
_serviceCollection.AddScoped<FrameworkService>();
|
||||||
services.AddScoped<ChatService>();
|
_serviceCollection.AddScoped<ChatService>();
|
||||||
services.AddScoped<FloorService>();
|
_serviceCollection.AddScoped<FloorService>();
|
||||||
services.AddScoped<ImportService>();
|
_serviceCollection.AddScoped<ImportService>();
|
||||||
|
|
||||||
// windows & related services
|
// windows & related services
|
||||||
services.AddScoped<AgreementWindow>();
|
_serviceCollection.AddScoped<AgreementWindow>();
|
||||||
services.AddScoped<ConfigWindow>();
|
_serviceCollection.AddScoped<ConfigWindow>();
|
||||||
services.AddScoped<StatisticsService>();
|
_serviceCollection.AddScoped<StatisticsService>();
|
||||||
services.AddScoped<StatisticsWindow>();
|
_serviceCollection.AddScoped<StatisticsWindow>();
|
||||||
|
|
||||||
// rendering
|
// rendering
|
||||||
services.AddScoped<SimpleRenderer>();
|
_serviceCollection.AddScoped<SimpleRenderer>();
|
||||||
services.AddScoped<SplatoonRenderer>();
|
_serviceCollection.AddScoped<SplatoonRenderer>();
|
||||||
services.AddScoped<RenderAdapter>();
|
_serviceCollection.AddScoped<RenderAdapter>();
|
||||||
|
|
||||||
// queue handling
|
// queue handling
|
||||||
services.AddTransient<IQueueOnFrameworkThread.Handler<QueuedImport>, QueuedImport.Handler>();
|
_serviceCollection.AddTransient<IQueueOnFrameworkThread.Handler<QueuedImport>, QueuedImport.Handler>();
|
||||||
services.AddTransient<IQueueOnFrameworkThread.Handler<QueuedUndoImport>, QueuedUndoImport.Handler>();
|
_serviceCollection
|
||||||
services.AddTransient<IQueueOnFrameworkThread.Handler<QueuedConfigUpdate>, QueuedConfigUpdate.Handler>();
|
.AddTransient<IQueueOnFrameworkThread.Handler<QueuedUndoImport>, QueuedUndoImport.Handler>();
|
||||||
services.AddTransient<IQueueOnFrameworkThread.Handler<QueuedSyncResponse>, QueuedSyncResponse.Handler>();
|
_serviceCollection
|
||||||
|
.AddTransient<IQueueOnFrameworkThread.Handler<QueuedConfigUpdate>, QueuedConfigUpdate.Handler>();
|
||||||
// set up the current UI language before creating anything
|
_serviceCollection
|
||||||
Localization.Culture = new CultureInfo(pluginInterface.UiLanguage);
|
.AddTransient<IQueueOnFrameworkThread.Handler<QueuedSyncResponse>, QueuedSyncResponse.Handler>();
|
||||||
|
|
||||||
// build
|
// build
|
||||||
_serviceProvider = services.BuildServiceProvider(new ServiceProviderOptions
|
_serviceProvider = _serviceCollection.BuildServiceProvider(new ServiceProviderOptions
|
||||||
{
|
{
|
||||||
ValidateOnBuild = true,
|
ValidateOnBuild = true,
|
||||||
ValidateScopes = true,
|
ValidateScopes = true,
|
||||||
@ -165,34 +170,19 @@ namespace Pal.Client
|
|||||||
// There's 2-3 seconds of slowdown primarily caused by the sqlite init, but that needs to happen for
|
// There's 2-3 seconds of slowdown primarily caused by the sqlite init, but that needs to happen for
|
||||||
// config stuff.
|
// config stuff.
|
||||||
_logger = _serviceProvider.GetRequiredService<ILogger<DependencyInjectionContext>>();
|
_logger = _serviceProvider.GetRequiredService<ILogger<DependencyInjectionContext>>();
|
||||||
_logger.LogInformation("Service container built, creating plugin");
|
_logger.LogInformation("Service container built");
|
||||||
_plugin = new Plugin(pluginInterface, _serviceProvider, _initCts.Token);
|
|
||||||
|
return _serviceProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_initCts.Cancel();
|
_logger.LogInformation("Disposing DI Context");
|
||||||
|
_serviceProvider?.Dispose();
|
||||||
|
|
||||||
// ensure we're not calling dispose recursively on ourselves
|
// ensure we're not keeping the file open longer than the plugin is loaded
|
||||||
if (_serviceProvider != null)
|
using (SqliteConnection sqliteConnection = new(_sqliteConnectionString))
|
||||||
{
|
SqliteConnection.ClearPool(sqliteConnection);
|
||||||
_logger.LogInformation("Disposing DI Context");
|
|
||||||
|
|
||||||
ServiceProvider serviceProvider = _serviceProvider;
|
|
||||||
_serviceProvider = null;
|
|
||||||
|
|
||||||
_plugin?.Dispose();
|
|
||||||
_plugin = null;
|
|
||||||
serviceProvider.Dispose();
|
|
||||||
|
|
||||||
// ensure we're not keeping the file open longer than the plugin is loaded
|
|
||||||
using (SqliteConnection sqliteConnection = new(_sqliteConnectionString))
|
|
||||||
SqliteConnection.ClearPool(sqliteConnection);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_logger.LogDebug("DI context is already disposed");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
using Dalamud.Game.ClientState.Objects.Types;
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
using Dalamud.Hooking;
|
using Dalamud.Hooking;
|
||||||
using Dalamud.Logging;
|
|
||||||
using Dalamud.Memory;
|
using Dalamud.Memory;
|
||||||
using Dalamud.Utility.Signatures;
|
using Dalamud.Utility.Signatures;
|
||||||
using System;
|
using System;
|
||||||
@ -32,8 +31,11 @@ namespace Pal.Client
|
|||||||
_territoryState = territoryState;
|
_territoryState = territoryState;
|
||||||
_frameworkService = frameworkService;
|
_frameworkService = frameworkService;
|
||||||
|
|
||||||
|
_logger.LogDebug("Initializing game hooks");
|
||||||
SignatureHelper.Initialise(this);
|
SignatureHelper.Initialise(this);
|
||||||
ActorVfxCreateHook.Enable();
|
ActorVfxCreateHook.Enable();
|
||||||
|
|
||||||
|
_logger.LogDebug("Game hooks initialized");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -82,6 +84,7 @@ namespace Pal.Client
|
|||||||
{
|
{
|
||||||
if (vfxPath == "vfx/common/eff/dk05th_stdn0t.avfx" || vfxPath == "vfx/common/eff/dk05ht_ipws0t.avfx")
|
if (vfxPath == "vfx/common/eff/dk05th_stdn0t.avfx" || vfxPath == "vfx/common/eff/dk05ht_ipws0t.avfx")
|
||||||
{
|
{
|
||||||
|
_logger.LogDebug("VFX '{Path}' playing at {Location}", vfxPath, obj.Position);
|
||||||
_frameworkService.NextUpdateObjects.Enqueue(obj.Address);
|
_frameworkService.NextUpdateObjects.Enqueue(obj.Address);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -96,6 +99,7 @@ namespace Pal.Client
|
|||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
|
_logger.LogDebug("Disposing game hooks");
|
||||||
ActorVfxCreateHook.Dispose();
|
ActorVfxCreateHook.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,15 @@
|
|||||||
using Dalamud.Interface.Windowing;
|
using Dalamud.Interface.Windowing;
|
||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
using Pal.Client.Rendering;
|
using Pal.Client.Rendering;
|
||||||
using Pal.Client.Windows;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Dalamud.Game;
|
||||||
using Dalamud.Game.ClientState;
|
using Dalamud.Game.ClientState;
|
||||||
using Dalamud.Game.Command;
|
using Dalamud.Game.Command;
|
||||||
|
using Dalamud.Game.Gui;
|
||||||
using Pal.Client.Properties;
|
using Pal.Client.Properties;
|
||||||
using ECommons;
|
using ECommons;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
@ -24,52 +25,101 @@ namespace Pal.Client
|
|||||||
/// need to be sent to different receivers depending on priority or configuration .
|
/// need to be sent to different receivers depending on priority or configuration .
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <see cref="DependencyInjectionContext"/>
|
/// <see cref="DependencyInjectionContext"/>
|
||||||
internal sealed class Plugin : IDisposable
|
internal sealed class Plugin : IDalamudPlugin
|
||||||
{
|
{
|
||||||
|
private readonly CancellationTokenSource _initCts = new();
|
||||||
|
|
||||||
private readonly DalamudPluginInterface _pluginInterface;
|
private readonly DalamudPluginInterface _pluginInterface;
|
||||||
private readonly ILogger<Plugin> _logger;
|
|
||||||
private readonly CommandManager _commandManager;
|
private readonly CommandManager _commandManager;
|
||||||
private readonly Chat _chat;
|
|
||||||
private readonly WindowSystem _windowSystem;
|
|
||||||
private readonly ClientState _clientState;
|
private readonly ClientState _clientState;
|
||||||
|
private readonly ChatGui _chatGui;
|
||||||
|
private readonly Framework _framework;
|
||||||
|
|
||||||
private readonly IServiceScope _rootScope;
|
private readonly TaskCompletionSource<IServiceScope> _rootScopeCompletionSource = new();
|
||||||
private readonly DependencyInjectionLoader _loader;
|
private ELoadState _loadState = ELoadState.Initializing;
|
||||||
|
|
||||||
|
private DependencyInjectionContext? _dependencyInjectionContext;
|
||||||
|
private ILogger _logger = DependencyInjectionContext.LoggerProvider.CreateLogger<Plugin>();
|
||||||
|
private WindowSystem? _windowSystem;
|
||||||
|
private IServiceScope? _rootScope;
|
||||||
private Action? _loginAction;
|
private Action? _loginAction;
|
||||||
|
|
||||||
public Plugin(
|
public Plugin(
|
||||||
DalamudPluginInterface pluginInterface,
|
DalamudPluginInterface pluginInterface,
|
||||||
IServiceProvider serviceProvider,
|
CommandManager commandManager,
|
||||||
CancellationToken cancellationToken)
|
ClientState clientState,
|
||||||
|
ChatGui chatGui,
|
||||||
|
Framework framework)
|
||||||
{
|
{
|
||||||
_pluginInterface = pluginInterface;
|
_pluginInterface = pluginInterface;
|
||||||
_logger = serviceProvider.GetRequiredService<ILogger<Plugin>>();
|
_commandManager = commandManager;
|
||||||
_commandManager = serviceProvider.GetRequiredService<CommandManager>();
|
_clientState = clientState;
|
||||||
_chat = serviceProvider.GetRequiredService<Chat>();
|
_chatGui = chatGui;
|
||||||
_windowSystem = serviceProvider.GetRequiredService<WindowSystem>();
|
_framework = framework;
|
||||||
_clientState = serviceProvider.GetRequiredService<ClientState>();
|
|
||||||
|
|
||||||
_rootScope = serviceProvider.CreateScope();
|
// set up the current UI language before creating anything
|
||||||
_loader = _rootScope.ServiceProvider.GetRequiredService<DependencyInjectionLoader>();
|
Localization.Culture = new CultureInfo(_pluginInterface.UiLanguage);
|
||||||
_loader.InitCompleted += InitCompleted;
|
|
||||||
var _ = Task.Run(async () => await _loader.InitializeAsync(cancellationToken));
|
|
||||||
|
|
||||||
pluginInterface.UiBuilder.Draw += Draw;
|
|
||||||
pluginInterface.UiBuilder.OpenConfigUi += OpenConfigUi;
|
|
||||||
pluginInterface.LanguageChanged += LanguageChanged;
|
|
||||||
_clientState.Login += Login;
|
|
||||||
|
|
||||||
_commandManager.AddHandler("/pal", new CommandInfo(OnCommand)
|
_commandManager.AddHandler("/pal", new CommandInfo(OnCommand)
|
||||||
{
|
{
|
||||||
HelpMessage = Localization.Command_pal_HelpText
|
HelpMessage = Localization.Command_pal_HelpText
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Task.Run(async () => await CreateDependencyContext());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InitCompleted(Action? loginAction)
|
public string Name => Localization.Palace_Pal;
|
||||||
{
|
|
||||||
LanguageChanged(_pluginInterface.UiLanguage);
|
|
||||||
|
|
||||||
|
private async Task CreateDependencyContext()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_dependencyInjectionContext = _pluginInterface.Create<DependencyInjectionContext>(this)
|
||||||
|
?? throw new Exception("Could not create DI root context class");
|
||||||
|
var serviceProvider = _dependencyInjectionContext.BuildServiceContainer();
|
||||||
|
_initCts.Token.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
_logger = serviceProvider.GetRequiredService<ILogger<Plugin>>();
|
||||||
|
_windowSystem = serviceProvider.GetRequiredService<WindowSystem>();
|
||||||
|
_rootScope = serviceProvider.CreateScope();
|
||||||
|
|
||||||
|
var loader = _rootScope.ServiceProvider.GetRequiredService<DependencyContextInitializer>();
|
||||||
|
await loader.InitializeAsync(_initCts.Token);
|
||||||
|
|
||||||
|
await _framework.RunOnFrameworkThread(() =>
|
||||||
|
{
|
||||||
|
_pluginInterface.UiBuilder.Draw += Draw;
|
||||||
|
_pluginInterface.UiBuilder.OpenConfigUi += OpenConfigUi;
|
||||||
|
_pluginInterface.LanguageChanged += LanguageChanged;
|
||||||
|
_clientState.Login += Login;
|
||||||
|
});
|
||||||
|
_rootScopeCompletionSource.SetResult(_rootScope);
|
||||||
|
_loadState = ELoadState.Loaded;
|
||||||
|
}
|
||||||
|
catch (ObjectDisposedException e)
|
||||||
|
{
|
||||||
|
_rootScopeCompletionSource.SetException(e);
|
||||||
|
_loadState = ELoadState.Error;
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException e)
|
||||||
|
{
|
||||||
|
_rootScopeCompletionSource.SetException(e);
|
||||||
|
_loadState = ELoadState.Error;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_rootScopeCompletionSource.SetException(e);
|
||||||
|
_logger.LogError(e, "Async load failed");
|
||||||
|
ShowErrorOnLogin(() =>
|
||||||
|
new Chat(_chatGui).Error(string.Format(Localization.Error_LoadFailed,
|
||||||
|
$"{e.GetType()} - {e.Message}")));
|
||||||
|
|
||||||
|
_loadState = ELoadState.Error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ShowErrorOnLogin(Action? loginAction)
|
||||||
|
{
|
||||||
if (_clientState.IsLoggedIn)
|
if (_clientState.IsLoggedIn)
|
||||||
{
|
{
|
||||||
loginAction?.Invoke();
|
loginAction?.Invoke();
|
||||||
@ -89,84 +139,106 @@ namespace Pal.Client
|
|||||||
{
|
{
|
||||||
arguments = arguments.Trim();
|
arguments = arguments.Trim();
|
||||||
|
|
||||||
IPalacePalConfiguration configuration =
|
Task.Run(async () =>
|
||||||
_rootScope.ServiceProvider.GetRequiredService<IPalacePalConfiguration>();
|
|
||||||
if (configuration.FirstUse && arguments != "" && arguments != "config")
|
|
||||||
{
|
{
|
||||||
_chat.Error(Localization.Error_FirstTimeSetupRequired);
|
IServiceScope rootScope;
|
||||||
return;
|
try
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var sp = _rootScope.ServiceProvider;
|
|
||||||
|
|
||||||
switch (arguments)
|
|
||||||
{
|
{
|
||||||
case "":
|
rootScope = await _rootScopeCompletionSource.Task;
|
||||||
case "config":
|
|
||||||
sp.GetRequiredService<PalConfigCommand>().Execute();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "stats":
|
|
||||||
sp.GetRequiredService<PalStatsCommand>().Execute();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "tc":
|
|
||||||
case "test-connection":
|
|
||||||
sp.GetRequiredService<PalTestConnectionCommand>().Execute();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "near":
|
|
||||||
case "tnear":
|
|
||||||
case "hnear":
|
|
||||||
sp.GetRequiredService<PalNearCommand>().Execute(arguments);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
_chat.Error(string.Format(Localization.Command_pal_UnknownSubcommand, arguments,
|
|
||||||
command));
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
catch (Exception e)
|
||||||
catch (Exception e)
|
{
|
||||||
{
|
_logger.LogError(e, "Could not wait for command root scope");
|
||||||
_chat.Error(e.ToString());
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IPalacePalConfiguration configuration =
|
||||||
|
rootScope.ServiceProvider.GetRequiredService<IPalacePalConfiguration>();
|
||||||
|
Chat chat = rootScope.ServiceProvider.GetRequiredService<Chat>();
|
||||||
|
if (configuration.FirstUse && arguments != "" && arguments != "config")
|
||||||
|
{
|
||||||
|
chat.Error(Localization.Error_FirstTimeSetupRequired);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var sp = rootScope.ServiceProvider;
|
||||||
|
|
||||||
|
switch (arguments)
|
||||||
|
{
|
||||||
|
case "":
|
||||||
|
case "config":
|
||||||
|
sp.GetRequiredService<PalConfigCommand>().Execute();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "stats":
|
||||||
|
sp.GetRequiredService<PalStatsCommand>().Execute();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "tc":
|
||||||
|
case "test-connection":
|
||||||
|
sp.GetRequiredService<PalTestConnectionCommand>().Execute();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "near":
|
||||||
|
case "tnear":
|
||||||
|
case "hnear":
|
||||||
|
sp.GetRequiredService<PalNearCommand>().Execute(arguments);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
chat.Error(string.Format(Localization.Command_pal_UnknownSubcommand, arguments,
|
||||||
|
command));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
chat.Error(e.ToString());
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OpenConfigUi()
|
private void OpenConfigUi()
|
||||||
=> _rootScope.ServiceProvider.GetRequiredService<PalConfigCommand>().Execute();
|
=> _rootScope!.ServiceProvider.GetRequiredService<PalConfigCommand>().Execute();
|
||||||
|
|
||||||
private void LanguageChanged(string languageCode)
|
private void LanguageChanged(string languageCode)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Language set to '{Language}'", languageCode);
|
_logger.LogInformation("Language set to '{Language}'", languageCode);
|
||||||
|
|
||||||
Localization.Culture = new CultureInfo(languageCode);
|
Localization.Culture = new CultureInfo(languageCode);
|
||||||
_windowSystem.Windows.OfType<ILanguageChanged>()
|
_windowSystem!.Windows.OfType<ILanguageChanged>()
|
||||||
.Each(w => w.LanguageChanged());
|
.Each(w => w.LanguageChanged());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Draw()
|
private void Draw()
|
||||||
{
|
{
|
||||||
if (_loader.LoadState == DependencyInjectionLoader.ELoadState.Loaded)
|
_rootScope!.ServiceProvider.GetRequiredService<RenderAdapter>().DrawLayers();
|
||||||
{
|
_windowSystem!.Draw();
|
||||||
_rootScope.ServiceProvider.GetRequiredService<RenderAdapter>().DrawLayers();
|
|
||||||
_windowSystem.Draw();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_commandManager.RemoveHandler("/pal");
|
_commandManager.RemoveHandler("/pal");
|
||||||
|
|
||||||
_pluginInterface.UiBuilder.Draw -= Draw;
|
if (_loadState == ELoadState.Loaded)
|
||||||
_pluginInterface.UiBuilder.OpenConfigUi -= OpenConfigUi;
|
{
|
||||||
_pluginInterface.LanguageChanged -= LanguageChanged;
|
_pluginInterface.UiBuilder.Draw -= Draw;
|
||||||
_clientState.Login -= Login;
|
_pluginInterface.UiBuilder.OpenConfigUi -= OpenConfigUi;
|
||||||
|
_pluginInterface.LanguageChanged -= LanguageChanged;
|
||||||
|
_clientState.Login -= Login;
|
||||||
|
}
|
||||||
|
|
||||||
_loader.InitCompleted -= InitCompleted;
|
_initCts.Cancel();
|
||||||
_rootScope.Dispose();
|
_rootScope?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum ELoadState
|
||||||
|
{
|
||||||
|
Initializing,
|
||||||
|
Loaded,
|
||||||
|
Error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ namespace Pal.Client.Scheduled
|
|||||||
{
|
{
|
||||||
if (queued is T t)
|
if (queued is T t)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Handling {QueuedType}", queued.GetType());
|
_logger.LogDebug("Handling {QueuedType}", queued.GetType());
|
||||||
Run(t, ref recreateLayout);
|
Run(t, ref recreateLayout);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
Loading…
Reference in New Issue
Block a user