using System; using System.IO; using Dalamud.Data; using Dalamud.Extensions.MicrosoftLogging; using Dalamud.Game; using Dalamud.Game.ClientState; using Dalamud.Game.ClientState.Conditions; using Dalamud.Game.ClientState.Objects; using Dalamud.Game.Command; using Dalamud.Game.Gui; using Dalamud.Interface.Windowing; using Dalamud.Plugin; using Dalamud.Plugin.Services; using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Pal.Client.Commands; using Pal.Client.Configuration; using Pal.Client.Configuration.Legacy; using Pal.Client.Database; using Pal.Client.DependencyInjection; using Pal.Client.Floors; using Pal.Client.Net; using Pal.Client.Rendering; using Pal.Client.Scheduled; using Pal.Client.Windows; namespace Pal.Client; /// /// DI-aware Plugin. /// internal sealed class DependencyInjectionContext : IDisposable { public const string DatabaseFileName = "palace-pal.data.sqlite3"; /// /// Initialized as temporary logger, will be overriden once context is ready with a logger that supports scopes. /// private ILogger _logger; private readonly string _sqliteConnectionString; private readonly ServiceCollection _serviceCollection = new(); private ServiceProvider? _serviceProvider; public DependencyInjectionContext( DalamudPluginInterface pluginInterface, IClientState clientState, IGameGui gameGui, IChatGui chatGui, IObjectTable objectTable, IFramework framework, ICondition condition, ICommandManager commandManager, IDataManager dataManager, IGameInteropProvider gameInteropProvider, IPluginLog pluginLog, Plugin plugin) { var loggerProvider = new DalamudLoggerProvider(pluginLog); _logger = loggerProvider.CreateLogger(); _logger.LogInformation("Building dalamud service container for {Assembly}", typeof(DependencyInjectionContext).Assembly.FullName); // set up legacy services #pragma warning disable CS0612 JsonFloorState.SetContextProperties(pluginInterface.GetPluginConfigDirectory()); #pragma warning restore CS0612 // set up logging _serviceCollection.AddLogging(builder => builder.AddFilter("Pal", LogLevel.Trace) .AddFilter("Microsoft.EntityFrameworkCore.Database", LogLevel.Warning) .AddFilter("Grpc", LogLevel.Debug) .ClearProviders() .AddDalamudLogger(pluginLog)); // dalamud _serviceCollection.AddSingleton(plugin); _serviceCollection.AddSingleton(pluginInterface); _serviceCollection.AddSingleton(clientState); _serviceCollection.AddSingleton(gameGui); _serviceCollection.AddSingleton(chatGui); _serviceCollection.AddSingleton(); _serviceCollection.AddSingleton(objectTable); _serviceCollection.AddSingleton(framework); _serviceCollection.AddSingleton(condition); _serviceCollection.AddSingleton(commandManager); _serviceCollection.AddSingleton(dataManager); _serviceCollection.AddSingleton(gameInteropProvider); _serviceCollection.AddSingleton(new WindowSystem(typeof(DependencyInjectionContext).AssemblyQualifiedName)); _sqliteConnectionString = $"Data Source={Path.Join(pluginInterface.GetPluginConfigDirectory(), DatabaseFileName)}"; } public IServiceProvider BuildServiceContainer() { _logger.LogInformation("Building async service container for {Assembly}", typeof(DependencyInjectionContext).Assembly.FullName); // EF core _serviceCollection.AddDbContext(o => o .UseSqlite(_sqliteConnectionString) .UseModel(Database.Compiled.PalClientContextModel.Instance)); _serviceCollection.AddTransient(); _serviceCollection.AddScoped(); // plugin-specific _serviceCollection.AddScoped(); _serviceCollection.AddScoped(); _serviceCollection.AddScoped(); _serviceCollection.AddScoped(); _serviceCollection.AddScoped(); _serviceCollection.AddScoped(sp => sp.GetRequiredService().Load()); _serviceCollection.AddTransient(); // commands _serviceCollection.AddScoped(); _serviceCollection.AddScoped(); _serviceCollection.AddScoped(); _serviceCollection.AddScoped(); _serviceCollection.AddScoped(); // territory & marker related services _serviceCollection.AddScoped(); _serviceCollection.AddScoped(); _serviceCollection.AddScoped(); _serviceCollection.AddScoped(); _serviceCollection.AddScoped(); _serviceCollection.AddScoped(); // windows & related services _serviceCollection.AddScoped(); _serviceCollection.AddScoped(); _serviceCollection.AddScoped(); _serviceCollection.AddScoped(); // rendering _serviceCollection.AddScoped(); _serviceCollection.AddScoped(); _serviceCollection.AddScoped(); // queue handling _serviceCollection.AddTransient, QueuedImport.Handler>(); _serviceCollection .AddTransient, QueuedUndoImport.Handler>(); _serviceCollection .AddTransient, QueuedConfigUpdate.Handler>(); _serviceCollection .AddTransient, QueuedSyncResponse.Handler>(); // build _serviceProvider = _serviceCollection.BuildServiceProvider(new ServiceProviderOptions { ValidateOnBuild = true, ValidateScopes = true, }); #if RELEASE // You're welcome to remove this code in your fork, but please make sure that: // - none of the links accessible within FFXIV open the original repo (e.g. in the plugin installer), and // - you host your own server instance // // This is mainly to avoid this plugin being included in 'mega-repos' that, for whatever reason, decide // that collecting all plugins is a good idea (and break half in the process). _serviceProvider.GetService(); #endif // This is not ideal as far as loading the plugin goes, because there's no way to check for errors and // tell Dalamud that no, the plugin isn't ready -- so the plugin will count as properly initialized, // even if it's not. // // There's 2-3 seconds of slowdown primarily caused by the sqlite init, but that needs to happen for // config stuff. _logger = _serviceProvider.GetRequiredService>(); _logger.LogInformation("Service container built"); return _serviceProvider; } public void Dispose() { _logger.LogInformation("Disposing DI Context"); _serviceProvider?.Dispose(); // ensure we're not keeping the file open longer than the plugin is loaded using (SqliteConnection sqliteConnection = new(_sqliteConnectionString)) SqliteConnection.ClearPool(sqliteConnection); } }