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
171
src/AcDream.App/Plugins/BufferedRenderPackRegistry.cs
Normal file
171
src/AcDream.App/Plugins/BufferedRenderPackRegistry.cs
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
using AcDream.Plugin.Abstractions.Rendering;
|
||||
|
||||
namespace AcDream.App.Plugins;
|
||||
|
||||
/// <summary>
|
||||
/// Pre-device render-pack discovery buffer. Registration only retains the
|
||||
/// immutable declaration and lazy asset source; it deliberately never calls
|
||||
/// <see cref="IRenderPackAssets.OpenRead"/> or touches the renderer. This lets
|
||||
/// plugins register before the window/GPU exists without weakening the retail
|
||||
/// no-op contract.
|
||||
/// </summary>
|
||||
internal sealed class BufferedRenderPackRegistry : IRenderPackRegistry, IDisposable
|
||||
{
|
||||
private readonly object _sync = new();
|
||||
private readonly Dictionary<string, Registration> _registrations =
|
||||
new(StringComparer.OrdinalIgnoreCase);
|
||||
private long _revision;
|
||||
private long _nextRegistrationId;
|
||||
private bool _disposed;
|
||||
|
||||
/// <summary>
|
||||
/// Monotonic catalog generation. Consumers use <see cref="Changed"/> to
|
||||
/// invalidate their cached view and consume the new snapshot at a safe
|
||||
/// frame/UI boundary; no renderer path polls the registry per frame.
|
||||
/// </summary>
|
||||
internal long Revision
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_sync)
|
||||
return _revision;
|
||||
}
|
||||
}
|
||||
|
||||
internal event Action<long>? Changed;
|
||||
|
||||
internal IReadOnlyList<BufferedRenderPackRegistration> Snapshot()
|
||||
{
|
||||
lock (_sync)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
return _registrations.Values
|
||||
.OrderBy(static value => value.Descriptor.Id, StringComparer.OrdinalIgnoreCase)
|
||||
.Select(static value => new BufferedRenderPackRegistration(
|
||||
value.Descriptor,
|
||||
value.Assets,
|
||||
value.RegistrationId))
|
||||
.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
public IDisposable Register(
|
||||
RenderPackDescriptor descriptor,
|
||||
IRenderPackAssets assets)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(descriptor);
|
||||
ArgumentNullException.ThrowIfNull(assets);
|
||||
|
||||
Registration registration;
|
||||
long revision;
|
||||
lock (_sync)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
if (_registrations.ContainsKey(descriptor.Id))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"A render pack with id '{descriptor.Id}' is already registered.");
|
||||
}
|
||||
|
||||
registration = new Registration(
|
||||
this,
|
||||
descriptor,
|
||||
assets,
|
||||
checked(++_nextRegistrationId));
|
||||
_registrations.Add(descriptor.Id, registration);
|
||||
revision = checked(++_revision);
|
||||
}
|
||||
|
||||
PublishChanged(revision);
|
||||
return registration;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Registration[] registrations;
|
||||
long? revision = null;
|
||||
lock (_sync)
|
||||
{
|
||||
if (_disposed)
|
||||
return;
|
||||
_disposed = true;
|
||||
registrations = _registrations.Values.ToArray();
|
||||
_registrations.Clear();
|
||||
if (registrations.Length != 0)
|
||||
revision = checked(++_revision);
|
||||
}
|
||||
|
||||
foreach (Registration registration in registrations)
|
||||
registration.WithdrawFromOwner();
|
||||
if (revision is { } changedRevision)
|
||||
PublishChanged(changedRevision);
|
||||
}
|
||||
|
||||
private void Withdraw(Registration registration)
|
||||
{
|
||||
long? revision = null;
|
||||
lock (_sync)
|
||||
{
|
||||
if (_registrations.TryGetValue(
|
||||
registration.Descriptor.Id,
|
||||
out Registration? active)
|
||||
&& ReferenceEquals(active, registration))
|
||||
{
|
||||
_registrations.Remove(registration.Descriptor.Id);
|
||||
revision = checked(++_revision);
|
||||
}
|
||||
}
|
||||
|
||||
if (revision is { } changedRevision)
|
||||
PublishChanged(changedRevision);
|
||||
}
|
||||
|
||||
private void PublishChanged(long revision)
|
||||
{
|
||||
Delegate[] subscribers = Changed?.GetInvocationList() ?? [];
|
||||
foreach (Delegate subscriber in subscribers)
|
||||
{
|
||||
try { ((Action<long>)subscriber)(revision); }
|
||||
catch
|
||||
{
|
||||
// Registration ownership must not be corrupted by a UI or
|
||||
// controller observer. The next explicit snapshot still sees
|
||||
// the authoritative revision and contents.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class Registration : IDisposable
|
||||
{
|
||||
private BufferedRenderPackRegistry? _owner;
|
||||
|
||||
internal Registration(
|
||||
BufferedRenderPackRegistry owner,
|
||||
RenderPackDescriptor descriptor,
|
||||
IRenderPackAssets assets,
|
||||
long registrationId)
|
||||
{
|
||||
_owner = owner;
|
||||
Descriptor = descriptor;
|
||||
Assets = assets;
|
||||
RegistrationId = registrationId;
|
||||
}
|
||||
|
||||
internal RenderPackDescriptor Descriptor { get; }
|
||||
|
||||
internal IRenderPackAssets Assets { get; }
|
||||
|
||||
internal long RegistrationId { get; }
|
||||
|
||||
public void Dispose() =>
|
||||
Interlocked.Exchange(ref _owner, null)?.Withdraw(this);
|
||||
|
||||
internal void WithdrawFromOwner() =>
|
||||
Interlocked.Exchange(ref _owner, null);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed record BufferedRenderPackRegistration(
|
||||
RenderPackDescriptor Descriptor,
|
||||
IRenderPackAssets Assets,
|
||||
long RegistrationId);
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
using AcDream.Core.Plugins;
|
||||
using AcDream.Platform;
|
||||
using AcDream.Plugin.Abstractions;
|
||||
using AcDream.Plugin.Abstractions.Rendering;
|
||||
using AcDream.Runtime.Session;
|
||||
|
||||
namespace AcDream.App.Plugins;
|
||||
|
|
@ -44,7 +45,8 @@ internal sealed class GraphicalPluginSession : IDisposable
|
|||
IReadOnlyList<string>? allowList,
|
||||
string sessionId,
|
||||
IPluginHost host,
|
||||
SessionStatusWriter statusWriter)
|
||||
SessionStatusWriter statusWriter,
|
||||
IRenderPackRegistry? renderPacks = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(paths);
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(sessionId);
|
||||
|
|
@ -53,7 +55,11 @@ internal sealed class GraphicalPluginSession : IDisposable
|
|||
|
||||
var plugins = new PluginSession(
|
||||
host,
|
||||
status => Report(statusWriter, sessionId, status));
|
||||
status => Report(statusWriter, sessionId, status),
|
||||
renderPacks,
|
||||
renderPacks is null
|
||||
? [PluginKind.Gameplay]
|
||||
: [PluginKind.Gameplay, PluginKind.RenderPack]);
|
||||
return new GraphicalPluginSession(
|
||||
plugins,
|
||||
[
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue