diff --git a/AcDream.slnx b/AcDream.slnx
index 16bf362..fa37fc2 100644
--- a/AcDream.slnx
+++ b/AcDream.slnx
@@ -4,6 +4,7 @@
+
diff --git a/src/AcDream.App/AcDream.App.csproj b/src/AcDream.App/AcDream.App.csproj
index b1e7d62..e4b69ca 100644
--- a/src/AcDream.App/AcDream.App.csproj
+++ b/src/AcDream.App/AcDream.App.csproj
@@ -24,4 +24,26 @@
PreserveNewest
+
+
+
+ false
+ true
+
+
+
+
+ <_SmokePluginSourceDir>..\AcDream.Plugins.Smoke\bin\$(Configuration)\net10.0
+ <_SmokePluginDestDir>$(OutputPath)plugins\AcDream.Plugins.Smoke
+
+
+
+
+
diff --git a/src/AcDream.App/Plugins/AppPluginHost.cs b/src/AcDream.App/Plugins/AppPluginHost.cs
new file mode 100644
index 0000000..dbe918f
--- /dev/null
+++ b/src/AcDream.App/Plugins/AppPluginHost.cs
@@ -0,0 +1,9 @@
+using AcDream.Plugin.Abstractions;
+
+namespace AcDream.App.Plugins;
+
+public sealed class AppPluginHost : IPluginHost
+{
+ public AppPluginHost(IPluginLogger log) => Log = log;
+ public IPluginLogger Log { get; }
+}
diff --git a/src/AcDream.App/Plugins/SerilogAdapter.cs b/src/AcDream.App/Plugins/SerilogAdapter.cs
new file mode 100644
index 0000000..9f0b4f0
--- /dev/null
+++ b/src/AcDream.App/Plugins/SerilogAdapter.cs
@@ -0,0 +1,12 @@
+using AcDream.Plugin.Abstractions;
+
+namespace AcDream.App.Plugins;
+
+public sealed class SerilogAdapter : IPluginLogger
+{
+ private readonly Serilog.ILogger _log;
+ public SerilogAdapter(Serilog.ILogger log) => _log = log;
+ public void Info(string message) => _log.Information("{Message}", message);
+ public void Warn(string message) => _log.Warning("{Message}", message);
+ public void Error(string message, Exception? exception = null) => _log.Error(exception, "{Message}", message);
+}
diff --git a/src/AcDream.App/Program.cs b/src/AcDream.App/Program.cs
index 6412b46..c876cf1 100644
--- a/src/AcDream.App/Program.cs
+++ b/src/AcDream.App/Program.cs
@@ -1,15 +1,61 @@
+using AcDream.App.Plugins;
using AcDream.App.Rendering;
+using AcDream.Core.Plugins;
+using Serilog;
-var datDir = args.FirstOrDefault()
- ?? Environment.GetEnvironmentVariable("ACDREAM_DAT_DIR");
+Log.Logger = new LoggerConfiguration()
+ .MinimumLevel.Debug()
+ .WriteTo.Console()
+ .CreateLogger();
+var datDir = args.FirstOrDefault() ?? Environment.GetEnvironmentVariable("ACDREAM_DAT_DIR");
if (string.IsNullOrWhiteSpace(datDir))
{
- Console.Error.WriteLine("usage: AcDream.App ");
- Console.Error.WriteLine(" or: set ACDREAM_DAT_DIR and run with no args");
+ Log.Error("usage: AcDream.App (or set ACDREAM_DAT_DIR)");
return 2;
}
-using var window = new GameWindow(datDir);
-window.Run();
+var host = new AppPluginHost(new SerilogAdapter(Log.Logger));
+
+var pluginsDir = Path.Combine(AppContext.BaseDirectory, "plugins");
+Log.Information("scanning plugins in {PluginsDir}", pluginsDir);
+
+var loaded = new List();
+foreach (var result in PluginDiscovery.Scan(pluginsDir))
+{
+ if (!result.Success)
+ {
+ Log.Warning("plugin discovery failed for {Dir}: {Error}", result.PluginDirectory, result.Error);
+ 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;
+ }
+
+ loaded.Add(loadResult);
+ Log.Information("loaded plugin {Id} ({DisplayName})", result.Manifest!.Id, result.Manifest.DisplayName);
+}
+
+foreach (var plugin in loaded)
+ plugin.Plugin!.Enable();
+
+try
+{
+ using var window = new GameWindow(datDir);
+ window.Run();
+}
+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;
diff --git a/src/AcDream.Plugins.Smoke/AcDream.Plugins.Smoke.csproj b/src/AcDream.Plugins.Smoke/AcDream.Plugins.Smoke.csproj
new file mode 100644
index 0000000..0f5b0c9
--- /dev/null
+++ b/src/AcDream.Plugins.Smoke/AcDream.Plugins.Smoke.csproj
@@ -0,0 +1,17 @@
+
+
+ net10.0
+ enable
+ enable
+ latest
+
+
+
+
+ false
+ runtime
+
+
+
diff --git a/src/AcDream.Plugins.Smoke/SmokePlugin.cs b/src/AcDream.Plugins.Smoke/SmokePlugin.cs
new file mode 100644
index 0000000..310b1a3
--- /dev/null
+++ b/src/AcDream.Plugins.Smoke/SmokePlugin.cs
@@ -0,0 +1,17 @@
+using AcDream.Plugin.Abstractions;
+
+namespace AcDream.Plugins.Smoke;
+
+public sealed class SmokePlugin : IAcDreamPlugin
+{
+ private IPluginHost? _host;
+
+ public void Initialize(IPluginHost host)
+ {
+ _host = host;
+ _host.Log.Info("smoke plugin initialized");
+ }
+
+ public void Enable() => _host?.Log.Info("smoke plugin enabled");
+ public void Disable() => _host?.Log.Info("smoke plugin disabled");
+}