DI: Load entire DI container in the background

This commit is contained in:
Liza 2023-02-22 22:20:50 +01:00
parent dbe6abd1db
commit 8d17c02186
5 changed files with 258 additions and 225 deletions

View File

@ -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,30 +24,22 @@ 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;
try
{
_logger.LogInformation("Starting async init"); _logger.LogInformation("Starting async init");
chat = _serviceProvider.GetService<Chat>();
await RemoveOldBackups(); await RemoveOldBackups();
await CreateBackups(); await CreateBackups();
@ -57,7 +48,7 @@ namespace Pal.Client
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
@ -82,24 +73,8 @@ namespace Pal.Client
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
LoadState = ELoadState.Loaded;
InitCompleted?.Invoke(null);
_logger.LogInformation("Async init complete"); _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
}
} }
} }

View File

@ -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();
// ensure we're not calling dispose recursively on ourselves
if (_serviceProvider != null)
{ {
_logger.LogInformation("Disposing DI Context"); _logger.LogInformation("Disposing DI Context");
_serviceProvider?.Dispose();
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 // ensure we're not keeping the file open longer than the plugin is loaded
using (SqliteConnection sqliteConnection = new(_sqliteConnectionString)) using (SqliteConnection sqliteConnection = new(_sqliteConnectionString))
SqliteConnection.ClearPool(sqliteConnection); SqliteConnection.ClearPool(sqliteConnection);
} }
else
{
_logger.LogDebug("DI context is already disposed");
}
}
} }
} }

View File

@ -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();
} }
} }

View File

@ -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,17 +139,31 @@ namespace Pal.Client
{ {
arguments = arguments.Trim(); arguments = arguments.Trim();
Task.Run(async () =>
{
IServiceScope rootScope;
try
{
rootScope = await _rootScopeCompletionSource.Task;
}
catch (Exception e)
{
_logger.LogError(e, "Could not wait for command root scope");
return;
}
IPalacePalConfiguration configuration = IPalacePalConfiguration configuration =
_rootScope.ServiceProvider.GetRequiredService<IPalacePalConfiguration>(); rootScope.ServiceProvider.GetRequiredService<IPalacePalConfiguration>();
Chat chat = rootScope.ServiceProvider.GetRequiredService<Chat>();
if (configuration.FirstUse && arguments != "" && arguments != "config") if (configuration.FirstUse && arguments != "" && arguments != "config")
{ {
_chat.Error(Localization.Error_FirstTimeSetupRequired); chat.Error(Localization.Error_FirstTimeSetupRequired);
return; return;
} }
try try
{ {
var sp = _rootScope.ServiceProvider; var sp = rootScope.ServiceProvider;
switch (arguments) switch (arguments)
{ {
@ -124,49 +188,57 @@ namespace Pal.Client
break; break;
default: default:
_chat.Error(string.Format(Localization.Command_pal_UnknownSubcommand, arguments, chat.Error(string.Format(Localization.Command_pal_UnknownSubcommand, arguments,
command)); command));
break; break;
} }
} }
catch (Exception e) catch (Exception e)
{ {
_chat.Error(e.ToString()); 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");
if (_loadState == ELoadState.Loaded)
{
_pluginInterface.UiBuilder.Draw -= Draw; _pluginInterface.UiBuilder.Draw -= Draw;
_pluginInterface.UiBuilder.OpenConfigUi -= OpenConfigUi; _pluginInterface.UiBuilder.OpenConfigUi -= OpenConfigUi;
_pluginInterface.LanguageChanged -= LanguageChanged; _pluginInterface.LanguageChanged -= LanguageChanged;
_clientState.Login -= Login; _clientState.Login -= Login;
}
_loader.InitCompleted -= InitCompleted; _initCts.Cancel();
_rootScope.Dispose(); _rootScope?.Dispose();
}
private enum ELoadState
{
Initializing,
Loaded,
Error
} }
} }
} }

View File

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