feat(render): implement Campaign AR and terrain fidelity
This commit is contained in:
parent
99cf26e00c
commit
7a5f96ede5
368 changed files with 50611 additions and 950 deletions
|
|
@ -1,4 +1,5 @@
|
|||
using AcDream.Plugin.Abstractions;
|
||||
using AcDream.Plugin.Abstractions.Rendering;
|
||||
|
||||
namespace AcDream.Core.Plugins;
|
||||
|
||||
|
|
@ -17,16 +18,25 @@ public readonly record struct PluginSessionStatus(
|
|||
PluginSessionStatusKind Kind,
|
||||
string? Error = null);
|
||||
|
||||
/// <summary>A configured plugin declares no entry point usable by this host.</summary>
|
||||
public sealed class PluginHostKindException : Exception
|
||||
{
|
||||
public PluginHostKindException(string message) : base(message) { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// One host/session-scoped plugin lifetime. Discovery, allow-listing,
|
||||
/// initialize/enable, failure isolation, reverse-order disable, and collectible
|
||||
/// load-context release are shared by graphical and no-window hosts so their
|
||||
/// configured plugin-set semantics cannot drift.
|
||||
/// initialize/enable, declarative render-pack registration, failure isolation,
|
||||
/// reverse-order disable, and collectible load-context release are shared by
|
||||
/// graphical and no-window hosts so their configured plugin-set semantics
|
||||
/// cannot drift. Unsupported kinds are filtered before assembly loading.
|
||||
/// </summary>
|
||||
public sealed class PluginSession : IDisposable
|
||||
{
|
||||
private readonly IPluginHost _host;
|
||||
private readonly Action<PluginSessionStatus>? _report;
|
||||
private readonly IRenderPackRegistry? _renderPacks;
|
||||
private readonly HashSet<PluginKind> _supportedKinds;
|
||||
private readonly List<ActivePlugin> _loaded = [];
|
||||
private readonly List<WeakReference> _releasedContexts = [];
|
||||
private bool _started;
|
||||
|
|
@ -34,10 +44,28 @@ public sealed class PluginSession : IDisposable
|
|||
|
||||
public PluginSession(
|
||||
IPluginHost host,
|
||||
Action<PluginSessionStatus>? report = null)
|
||||
Action<PluginSessionStatus>? report = null,
|
||||
IRenderPackRegistry? renderPacks = null,
|
||||
IEnumerable<PluginKind>? supportedKinds = null)
|
||||
{
|
||||
_host = host ?? throw new ArgumentNullException(nameof(host));
|
||||
_report = report;
|
||||
_renderPacks = renderPacks;
|
||||
_supportedKinds = new HashSet<PluginKind>(
|
||||
supportedKinds
|
||||
?? (renderPacks is null
|
||||
? [PluginKind.Gameplay]
|
||||
: [PluginKind.Gameplay, PluginKind.RenderPack]));
|
||||
if (_supportedKinds.Count == 0)
|
||||
throw new ArgumentException(
|
||||
"At least one supported plugin kind is required.",
|
||||
nameof(supportedKinds));
|
||||
if (_supportedKinds.Contains(PluginKind.RenderPack) && renderPacks is null)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"A host that supports render-pack plugins must supply a render-pack registry.",
|
||||
nameof(renderPacks));
|
||||
}
|
||||
}
|
||||
|
||||
public int LoadedCount => _loaded.Count;
|
||||
|
|
@ -122,6 +150,25 @@ public sealed class PluginSession : IDisposable
|
|||
string id = result.Manifest!.Id;
|
||||
if (requestedSet is not null && !requestedSet.Contains(id))
|
||||
continue;
|
||||
if (!result.Manifest.Kinds.Any(_supportedKinds.Contains))
|
||||
{
|
||||
// A shared plugin root may contain graphical-only packs.
|
||||
// An unfiltered/headless scan omits those silently. An
|
||||
// explicitly requested id gets one precise host-kind
|
||||
// failure, still without loading its assembly.
|
||||
if (requestedSet is not null)
|
||||
{
|
||||
AddOrdered(discoveredOrder, id);
|
||||
AddError(
|
||||
errors,
|
||||
id,
|
||||
new PluginHostKindException(
|
||||
$"plugin '{id}' declares only "
|
||||
+ $"{string.Join(", ", result.Manifest.Kinds)} entry points, "
|
||||
+ "which this host does not support."));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
AddOrdered(discoveredOrder, id);
|
||||
if (!candidates.TryGetValue(id, out List<PluginDiscoveryResult>? list))
|
||||
{
|
||||
|
|
@ -160,23 +207,27 @@ public sealed class PluginSession : IDisposable
|
|||
{
|
||||
ActivePlugin active = _loaded[index];
|
||||
LoadedPlugin loaded = active.Loaded;
|
||||
try
|
||||
if (loaded.Plugin is not null)
|
||||
{
|
||||
loaded.Plugin!.Disable();
|
||||
}
|
||||
catch (Exception error)
|
||||
{
|
||||
SafeLog(
|
||||
static (log, message, exception) =>
|
||||
log.Error(message, exception),
|
||||
$"plugin disable failed: {loaded.Manifest.Id}",
|
||||
error);
|
||||
try
|
||||
{
|
||||
loaded.Plugin.Disable();
|
||||
}
|
||||
catch (Exception error)
|
||||
{
|
||||
SafeLog(
|
||||
static (log, message, exception) =>
|
||||
log.Error(message, exception),
|
||||
$"plugin disable failed: {loaded.Manifest.Id}",
|
||||
error);
|
||||
}
|
||||
}
|
||||
|
||||
// Host-owned registrations are released even when Disable throws.
|
||||
// This must precede ALC unload so no UI binding or event delegate
|
||||
// can keep the plugin assembly reachable.
|
||||
active.Scope.Dispose();
|
||||
ReleaseRenderScope(active.RenderPackScope, loaded.Manifest.Id);
|
||||
|
||||
try
|
||||
{
|
||||
|
|
@ -208,16 +259,23 @@ public sealed class PluginSession : IDisposable
|
|||
foreach (PluginDiscoveryResult candidate in available)
|
||||
{
|
||||
var scope = new ScopedPluginHost(_host);
|
||||
ScopedRenderPackRegistry? renderPackScope =
|
||||
candidate.Manifest!.Declares(PluginKind.RenderPack)
|
||||
&& _renderPacks is not null
|
||||
? new ScopedRenderPackRegistry(_renderPacks)
|
||||
: null;
|
||||
LoadedPlugin loaded = PluginLoader.Load(
|
||||
candidate.PluginDirectory,
|
||||
candidate.Manifest!,
|
||||
scope);
|
||||
candidate.Manifest,
|
||||
scope,
|
||||
renderPackScope);
|
||||
if (!loaded.Success)
|
||||
{
|
||||
// Initialize can register callbacks before it fails. The
|
||||
// registration transaction closes before plugin cleanup
|
||||
// and, critically, before any ALC Unloading notification.
|
||||
scope.Dispose();
|
||||
ReleaseRenderScope(renderPackScope, candidate.Manifest.Id);
|
||||
ReleaseFailedLoad(loaded);
|
||||
AddError(
|
||||
errors,
|
||||
|
|
@ -229,8 +287,8 @@ public sealed class PluginSession : IDisposable
|
|||
|
||||
try
|
||||
{
|
||||
loaded.Plugin!.Enable();
|
||||
_loaded.Add(new ActivePlugin(loaded, scope));
|
||||
loaded.Plugin?.Enable();
|
||||
_loaded.Add(new ActivePlugin(loaded, scope, renderPackScope));
|
||||
SafeLog(
|
||||
static (log, message, _) => log.Info(message),
|
||||
$"plugin loaded: {loaded.Manifest.Id} "
|
||||
|
|
@ -244,7 +302,7 @@ public sealed class PluginSession : IDisposable
|
|||
catch (Exception error)
|
||||
{
|
||||
AddError(errors, id, error);
|
||||
ReleaseFailedEnable(loaded, scope);
|
||||
ReleaseFailedEnable(loaded, scope, renderPackScope);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -274,22 +332,27 @@ public sealed class PluginSession : IDisposable
|
|||
|
||||
private void ReleaseFailedEnable(
|
||||
LoadedPlugin loaded,
|
||||
ScopedPluginHost scope)
|
||||
ScopedPluginHost scope,
|
||||
ScopedRenderPackRegistry? renderPackScope)
|
||||
{
|
||||
try
|
||||
if (loaded.Plugin is not null)
|
||||
{
|
||||
loaded.Plugin!.Disable();
|
||||
}
|
||||
catch (Exception error)
|
||||
{
|
||||
SafeLog(
|
||||
static (log, message, exception) =>
|
||||
log.Error(message, exception),
|
||||
$"plugin cleanup after enable failure failed: {loaded.Manifest.Id}",
|
||||
error);
|
||||
try
|
||||
{
|
||||
loaded.Plugin.Disable();
|
||||
}
|
||||
catch (Exception error)
|
||||
{
|
||||
SafeLog(
|
||||
static (log, message, exception) =>
|
||||
log.Error(message, exception),
|
||||
$"plugin cleanup after enable failure failed: {loaded.Manifest.Id}",
|
||||
error);
|
||||
}
|
||||
}
|
||||
|
||||
scope.Dispose();
|
||||
ReleaseRenderScope(renderPackScope, loaded.Manifest.Id);
|
||||
|
||||
_releasedContexts.Add(new WeakReference(loaded.LoadContext!));
|
||||
try
|
||||
|
|
@ -342,6 +405,26 @@ public sealed class PluginSession : IDisposable
|
|||
}
|
||||
}
|
||||
|
||||
private void ReleaseRenderScope(
|
||||
ScopedRenderPackRegistry? scope,
|
||||
string pluginId)
|
||||
{
|
||||
if (scope is null)
|
||||
return;
|
||||
try
|
||||
{
|
||||
scope.Dispose();
|
||||
}
|
||||
catch (Exception error)
|
||||
{
|
||||
SafeLog(
|
||||
static (log, message, exception) =>
|
||||
log.Error(message, exception),
|
||||
$"render-pack registration cleanup failed: {pluginId}",
|
||||
error);
|
||||
}
|
||||
}
|
||||
|
||||
private void Report(PluginSessionStatus status)
|
||||
{
|
||||
if (_report is null)
|
||||
|
|
@ -417,5 +500,6 @@ public sealed class PluginSession : IDisposable
|
|||
|
||||
private sealed record ActivePlugin(
|
||||
LoadedPlugin Loaded,
|
||||
ScopedPluginHost Scope);
|
||||
ScopedPluginHost Scope,
|
||||
ScopedRenderPackRegistry? RenderPackScope);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue