using AcDream.App; using AcDream.App.Plugins; using AcDream.App.Platform; using AcDream.App.Rendering; using AcDream.Core.Plugins; using AcDream.Runtime.Platform; using Serilog; GraphicalHostPlatformServices graphicalPlatform = GraphicalHostPlatformServices.Resolve(); graphicalPlatform.ConfigureWindowBackend(); ApplicationPathSet applicationPaths = graphicalPlatform.Paths; IReadOnlyList migratedConfigurationFiles = GraphicalLegacyConfigurationMigrator.Migrate(applicationPaths); if (args.Length >= 1 && args[0] == "ui-studio") { var so = AcDream.App.Studio.StudioOptions.Parse(args[1..]); using var sw = new AcDream.App.Studio.StudioWindow( so, applicationPaths); try { sw.Run(); return 0; } catch (NotSupportedException error) { Console.Error.WriteLine(error.Message); return 4; } } Log.Logger = new LoggerConfiguration() .MinimumLevel.Debug() .WriteTo.Console() .CreateLogger(); foreach (string migratedConfigurationFile in migratedConfigurationFiles) { Log.Information( "migrated legacy graphical configuration to {Path}", migratedConfigurationFile); } Log.Information( "graphical platform {RuntimeIdentifier}; native closure: {NativeDependencies}", graphicalPlatform.RuntimeIdentifier, string.Join( ", ", graphicalPlatform.NativeDependencies.Select( dependency => $"{dependency.Feature}={dependency.PublishedFileName}"))); var datDir = args.FirstOrDefault() ?? Environment.GetEnvironmentVariable("ACDREAM_DAT_DIR"); if (string.IsNullOrWhiteSpace(datDir)) { Log.Error("usage: AcDream.App (or set ACDREAM_DAT_DIR)"); return 2; } // Single read of the startup-time process environment. Every downstream // consumer (GameWindow + collaborators) reads the typed bundle, not the // raw env vars. See docs/architecture/code-structure.md ยง2 Rule 4. var runtimeOptions = RuntimeOptions.FromEnvironment(datDir); var worldGameState = new AcDream.Core.Plugins.WorldGameState(); var worldEvents = new AcDream.Core.Plugins.WorldEvents(); var uiRegistry = new AcDream.App.Plugins.BufferedUiRegistry(); using var window = new GameWindow( runtimeOptions, worldGameState, worldEvents, uiRegistry, graphicalPlatform); var host = new AppPluginHost( new SerilogAdapter(Log.Logger), worldGameState, worldEvents, window.Selection, uiRegistry); var loaded = new List(); var loadedPluginIds = new HashSet(StringComparer.OrdinalIgnoreCase); StringComparer pathComparer = graphicalPlatform.OperatingSystem == GraphicalHostOperatingSystem.Windows ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal; string[] pluginRoots = [ .. new[] { Path.Combine(AppContext.BaseDirectory, "plugins"), applicationPaths.PluginsDirectory, }.Distinct(pathComparer), ]; foreach (string pluginsDir in pluginRoots) { Log.Information("scanning plugins in {PluginsDir}", pluginsDir); foreach (var result in PluginDiscovery.Scan(pluginsDir)) { if (!result.Success) { Log.Warning( "plugin discovery failed for {Dir}: {Error}", result.PluginDirectory, result.Error); continue; } if (loadedPluginIds.Contains(result.Manifest!.Id)) { Log.Warning( "skipping duplicate plugin id {Id} from {Dir}", result.Manifest.Id, result.PluginDirectory); continue; } var loadResult = PluginLoader.Load( result.PluginDirectory, result.Manifest, host); if (!loadResult.Success) { Log.Warning( "plugin load failed for {Id}: {Error}", result.Manifest.Id, loadResult.Error); continue; } loadedPluginIds.Add(result.Manifest.Id); loaded.Add(loadResult); Log.Information( "loaded plugin {Id} ({DisplayName})", result.Manifest.Id, result.Manifest.DisplayName); } } try { foreach (var plugin in loaded) { try { plugin.Plugin!.Enable(); } catch (Exception ex) { Log.Error(ex, "plugin enable failed: {Id}", plugin.Manifest.Id); } } try { window.Run(); } catch (NotSupportedException error) { Log.Error("{GraphicalStartupFailure}", error.Message); return 4; } } finally { foreach (var plugin in loaded) { try { plugin.Plugin!.Disable(); } catch (Exception ex) { Log.Error(ex, "plugin disable failed: {Id}", plugin.Manifest.Id); } } Log.CloseAndFlush(); } return 0;