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,5 +1,6 @@
|
|||
using System.Reflection;
|
||||
using AcDream.Plugin.Abstractions;
|
||||
using AcDream.Plugin.Abstractions.Rendering;
|
||||
|
||||
namespace AcDream.Core.Plugins;
|
||||
|
||||
|
|
@ -7,14 +8,19 @@ public static class PluginLoader
|
|||
{
|
||||
/// <summary>
|
||||
/// Load a plugin DLL from <paramref name="pluginDirectory"/> into a collectible
|
||||
/// <see cref="System.Runtime.Loader.AssemblyLoadContext"/>, find the first type
|
||||
/// implementing <see cref="IAcDreamPlugin"/>, instantiate it, and call its
|
||||
/// <see cref="IAcDreamPlugin.Initialize"/> with the supplied host. Any failure
|
||||
/// is returned as a failed <see cref="LoadedPlugin"/> rather than thrown.
|
||||
/// <see cref="System.Runtime.Loader.AssemblyLoadContext"/>, resolve the gameplay
|
||||
/// and/or render-pack entry points declared by its manifest, and invoke only
|
||||
/// the facilities this host supplied. Any failure is returned as a failed
|
||||
/// <see cref="LoadedPlugin"/> rather than thrown.
|
||||
/// A returned partial plugin/context remains caller-owned; this method never
|
||||
/// requests unload because the caller must close host registrations first.
|
||||
/// requests unload because the caller must close host and render-pack
|
||||
/// registrations first.
|
||||
/// </summary>
|
||||
public static LoadedPlugin Load(string pluginDirectory, PluginManifest manifest, IPluginHost host)
|
||||
public static LoadedPlugin Load(
|
||||
string pluginDirectory,
|
||||
PluginManifest manifest,
|
||||
IPluginHost host,
|
||||
IRenderPackRegistry? renderPacks = null)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(pluginDirectory);
|
||||
ArgumentNullException.ThrowIfNull(manifest);
|
||||
|
|
@ -45,6 +51,7 @@ public static class PluginLoader
|
|||
|
||||
PluginAssemblyLoadContext? alc = null;
|
||||
IAcDreamPlugin? instance = null;
|
||||
IRenderPackPlugin? renderPackInstance = null;
|
||||
try
|
||||
{
|
||||
alc = new PluginAssemblyLoadContext(pluginDirectory, dllPath);
|
||||
|
|
@ -60,10 +67,51 @@ public static class PluginLoader
|
|||
types = rtle.Types.OfType<Type>();
|
||||
}
|
||||
|
||||
var pluginType = types
|
||||
.FirstOrDefault(t => !t.IsAbstract && typeof(IAcDreamPlugin).IsAssignableFrom(t));
|
||||
Type[] concreteTypes = types
|
||||
.Where(static type => !type.IsAbstract && !type.IsInterface)
|
||||
.ToArray();
|
||||
Type? pluginType = manifest.Declares(PluginKind.Gameplay)
|
||||
? concreteTypes.FirstOrDefault(
|
||||
static type => typeof(IAcDreamPlugin).IsAssignableFrom(type))
|
||||
: null;
|
||||
bool registerRenderPack =
|
||||
manifest.Declares(PluginKind.RenderPack) && renderPacks is not null;
|
||||
CountingRenderPackRegistry? countedRenderPacks = registerRenderPack
|
||||
? new CountingRenderPackRegistry(renderPacks!)
|
||||
: null;
|
||||
Type? renderPackType = null;
|
||||
if (registerRenderPack)
|
||||
{
|
||||
Type[] renderPackTypes = concreteTypes
|
||||
.Where(static type => typeof(IRenderPackPlugin).IsAssignableFrom(type))
|
||||
.ToArray();
|
||||
if (renderPackTypes.Length != 1)
|
||||
{
|
||||
return new LoadedPlugin(
|
||||
manifest,
|
||||
Plugin: null,
|
||||
LoadContext: alc,
|
||||
Error: new InvalidOperationException(
|
||||
$"render-pack entry DLL '{manifest.EntryDll}' must contain exactly "
|
||||
+ "one IRenderPackPlugin implementation; found "
|
||||
+ renderPackTypes.Length));
|
||||
}
|
||||
|
||||
if (pluginType is null)
|
||||
renderPackType = renderPackTypes[0];
|
||||
if (!renderPackType.IsVisible
|
||||
|| renderPackType.GetConstructor(Type.EmptyTypes) is null)
|
||||
{
|
||||
return new LoadedPlugin(
|
||||
manifest,
|
||||
Plugin: null,
|
||||
LoadContext: alc,
|
||||
Error: new InvalidOperationException(
|
||||
$"render-pack entry type '{renderPackType.FullName}' must be public, "
|
||||
+ "non-abstract, and expose a public parameterless constructor"));
|
||||
}
|
||||
}
|
||||
|
||||
if (manifest.Declares(PluginKind.Gameplay) && pluginType is null)
|
||||
{
|
||||
return new LoadedPlugin(
|
||||
manifest,
|
||||
|
|
@ -73,9 +121,58 @@ public static class PluginLoader
|
|||
$"no IAcDreamPlugin implementation found in {manifest.EntryDll}"));
|
||||
}
|
||||
|
||||
instance = (IAcDreamPlugin)Activator.CreateInstance(pluginType)!;
|
||||
instance.Initialize(host);
|
||||
return new LoadedPlugin(manifest, instance, alc, Error: null);
|
||||
if (registerRenderPack && renderPackType is null)
|
||||
{
|
||||
return new LoadedPlugin(
|
||||
manifest,
|
||||
Plugin: null,
|
||||
LoadContext: alc,
|
||||
Error: new InvalidOperationException(
|
||||
$"no IRenderPackPlugin implementation found in {manifest.EntryDll}"));
|
||||
}
|
||||
|
||||
if (pluginType is null && renderPackType is null)
|
||||
{
|
||||
return new LoadedPlugin(
|
||||
manifest,
|
||||
Plugin: null,
|
||||
LoadContext: alc,
|
||||
Error: new InvalidOperationException(
|
||||
"the host did not supply any facility declared by this plugin"));
|
||||
}
|
||||
|
||||
object? sharedInstance = null;
|
||||
if (pluginType is not null)
|
||||
{
|
||||
sharedInstance = Activator.CreateInstance(pluginType);
|
||||
instance = (IAcDreamPlugin?)sharedInstance
|
||||
?? throw new InvalidOperationException(
|
||||
$"could not construct IAcDreamPlugin {pluginType.FullName}");
|
||||
instance.Initialize(host);
|
||||
}
|
||||
|
||||
if (renderPackType is not null)
|
||||
{
|
||||
object renderObject = ReferenceEquals(renderPackType, pluginType)
|
||||
? sharedInstance!
|
||||
: Activator.CreateInstance(renderPackType)
|
||||
?? throw new InvalidOperationException(
|
||||
$"could not construct IRenderPackPlugin {renderPackType.FullName}");
|
||||
renderPackInstance = (IRenderPackPlugin)renderObject;
|
||||
renderPackInstance.Register(countedRenderPacks!);
|
||||
if (countedRenderPacks!.RegistrationCount == 0)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"render-pack entry point '{renderPackType.FullName}' registered no packs");
|
||||
}
|
||||
}
|
||||
|
||||
return new LoadedPlugin(
|
||||
manifest,
|
||||
instance,
|
||||
alc,
|
||||
Error: null,
|
||||
renderPackInstance);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
@ -87,7 +184,45 @@ public static class PluginLoader
|
|||
manifest,
|
||||
Plugin: instance,
|
||||
LoadContext: alc,
|
||||
Error: ex);
|
||||
Error: ex,
|
||||
RenderPackPlugin: renderPackInstance);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class CountingRenderPackRegistry(IRenderPackRegistry inner) :
|
||||
IRenderPackRegistry
|
||||
{
|
||||
private int _registrationCount;
|
||||
|
||||
internal int RegistrationCount => Volatile.Read(ref _registrationCount);
|
||||
|
||||
public IDisposable Register(
|
||||
RenderPackDescriptor descriptor,
|
||||
IRenderPackAssets assets)
|
||||
{
|
||||
IDisposable registration = inner.Register(descriptor, assets)
|
||||
?? throw new InvalidOperationException(
|
||||
"The render-pack registry returned a null registration handle.");
|
||||
Interlocked.Increment(ref _registrationCount);
|
||||
return new CountedRegistration(this, registration);
|
||||
}
|
||||
|
||||
private sealed class CountedRegistration(
|
||||
CountingRenderPackRegistry owner,
|
||||
IDisposable inner) : IDisposable
|
||||
{
|
||||
private CountingRenderPackRegistry? _owner = owner;
|
||||
private IDisposable? _inner = inner;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
CountingRenderPackRegistry? activeOwner =
|
||||
Interlocked.Exchange(ref _owner, null);
|
||||
if (activeOwner is null)
|
||||
return;
|
||||
Interlocked.Decrement(ref activeOwner._registrationCount);
|
||||
Interlocked.Exchange(ref _inner, null)?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue