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