refactor(app): compose atomic frame roots
Move the complete update/render construction graph into a typed Phase-8 owner, explicitly carry the content dependencies it consumes, and publish both roots through one exact lease. Extract lifecycle resource sampling and frame-owned late bindings so partial startup and shutdown withdraw the same generation without window callbacks. Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
parent
826f9ea9b5
commit
3628aeb520
21 changed files with 1052 additions and 374 deletions
516
src/AcDream.App/Composition/FrameRootComposition.cs
Normal file
516
src/AcDream.App/Composition/FrameRootComposition.cs
Normal file
|
|
@ -0,0 +1,516 @@
|
|||
using AcDream.App.Diagnostics;
|
||||
using AcDream.App.Input;
|
||||
using AcDream.App.Rendering;
|
||||
using AcDream.App.Rendering.Vfx;
|
||||
using AcDream.App.Settings;
|
||||
using AcDream.App.Update;
|
||||
using AcDream.App.World;
|
||||
using AcDream.Core.Combat;
|
||||
using AcDream.Core.Lighting;
|
||||
using AcDream.Core.Physics;
|
||||
using AcDream.Core.Selection;
|
||||
using AcDream.Core.World;
|
||||
using Silk.NET.Input;
|
||||
using Silk.NET.OpenGL;
|
||||
using Silk.NET.Windowing;
|
||||
|
||||
namespace AcDream.App.Composition;
|
||||
|
||||
internal sealed record FrameRootDependencies(
|
||||
RuntimeOptions Options,
|
||||
GL Gl,
|
||||
IWindow Window,
|
||||
IInputContext Input,
|
||||
WorldTimeService WorldTime,
|
||||
WeatherSystem Weather,
|
||||
LightManager Lighting,
|
||||
WorldEnvironmentController WorldEnvironment,
|
||||
PhysicsEngine PhysicsEngine,
|
||||
CellVisibility CellVisibility,
|
||||
LocalPlayerModeState PlayerMode,
|
||||
LocalPlayerIdentityState PlayerIdentity,
|
||||
ChaseCameraInputState ChaseCameraInput,
|
||||
LocalPlayerControllerSlot PlayerController,
|
||||
LiveWorldOriginState WorldOrigin,
|
||||
ParticleVisibilityController ParticleVisibility,
|
||||
EntityEffectPoseRegistry EffectPoses,
|
||||
WorldRenderRangeState RenderRange,
|
||||
RuntimeSettingsController Settings,
|
||||
DisplayFramePacingController DisplayFramePacing,
|
||||
WorldSceneDebugState WorldSceneDebugState,
|
||||
RetailAlphaQueue RetailAlphaQueue,
|
||||
FrameProfiler FrameProfiler,
|
||||
bool FrameDiagnosticsEnabled,
|
||||
IRenderFrameDiagnosticLog RenderDiagnosticLog,
|
||||
DebugVmRenderFactsPublisher DebugVmRenderFacts,
|
||||
IInputCaptureSource InputCapture,
|
||||
DispatcherCameraInputSource CameraInput,
|
||||
SelectionState Selection,
|
||||
LiveEntityAnimationRuntimeView<LiveEntityAnimationState> Animations,
|
||||
UpdateFrameClock UpdateClock,
|
||||
CombatState Combat,
|
||||
GameFrameGraphSlot FrameGraphs,
|
||||
Action<string> Log);
|
||||
|
||||
internal sealed record FrameRootResult(
|
||||
UpdateFrameOrchestrator Update,
|
||||
RenderFrameOrchestrator Render,
|
||||
WorldLifecycleAutomationController? LifecycleAutomation,
|
||||
FrameRootRuntimeBindings RuntimeBindings,
|
||||
IDisposable FrameGraphPublication,
|
||||
AcDream.App.Net.LiveSessionHost SessionHost);
|
||||
|
||||
internal interface IGameWindowFrameRootPublication
|
||||
{
|
||||
void PublishFrameRoots(FrameRootResult result);
|
||||
}
|
||||
|
||||
internal enum FrameRootCompositionPoint
|
||||
{
|
||||
RenderResourcesCreated,
|
||||
WorldRendererCreated,
|
||||
LifecycleAutomationBound,
|
||||
RenderRootCreated,
|
||||
UpdateRootCreated,
|
||||
FrameGraphPublished,
|
||||
ResultPublished,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exact late edges created with the frame roots. Completed detach operations
|
||||
/// are removed immediately so shutdown retry never replays them.
|
||||
/// </summary>
|
||||
internal sealed class FrameRootRuntimeBindings : IDisposable
|
||||
{
|
||||
private readonly List<(string Name, IDisposable Binding)> _bindings = [];
|
||||
private bool _deactivationStarted;
|
||||
|
||||
public void Adopt(string name, IDisposable binding)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(name);
|
||||
ArgumentNullException.ThrowIfNull(binding);
|
||||
ObjectDisposedException.ThrowIf(_deactivationStarted, this);
|
||||
_bindings.Add((name, binding));
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_deactivationStarted && _bindings.Count == 0)
|
||||
return;
|
||||
_deactivationStarted = true;
|
||||
|
||||
List<Exception>? failures = null;
|
||||
for (int i = _bindings.Count - 1; i >= 0; i--)
|
||||
{
|
||||
(string name, IDisposable binding) = _bindings[i];
|
||||
try
|
||||
{
|
||||
binding.Dispose();
|
||||
_bindings.RemoveAt(i);
|
||||
}
|
||||
catch (Exception failure)
|
||||
{
|
||||
(failures ??= []).Add(new InvalidOperationException(
|
||||
$"Frame-root binding '{name}' did not detach.",
|
||||
failure));
|
||||
}
|
||||
}
|
||||
|
||||
if (failures is not null)
|
||||
{
|
||||
throw new AggregateException(
|
||||
"Frame-root binding cleanup remains incomplete.",
|
||||
failures);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class FrameRootCompositionPhase
|
||||
: IFrameRootCompositionPhase<
|
||||
HostInputCameraResult,
|
||||
ContentEffectsAudioResult,
|
||||
SettingsDevToolsResult,
|
||||
WorldRenderResult,
|
||||
InteractionRetainedUiResult,
|
||||
LivePresentationResult,
|
||||
SessionPlayerResult,
|
||||
FrameRootResult>
|
||||
{
|
||||
private readonly FrameRootDependencies _dependencies;
|
||||
private readonly IGameWindowFrameRootPublication _publication;
|
||||
private readonly Action<FrameRootCompositionPoint>? _faultInjection;
|
||||
|
||||
public FrameRootCompositionPhase(
|
||||
FrameRootDependencies dependencies,
|
||||
IGameWindowFrameRootPublication publication,
|
||||
Action<FrameRootCompositionPoint>? faultInjection = null)
|
||||
{
|
||||
_dependencies = dependencies
|
||||
?? throw new ArgumentNullException(nameof(dependencies));
|
||||
_publication = publication
|
||||
?? throw new ArgumentNullException(nameof(publication));
|
||||
_faultInjection = faultInjection;
|
||||
}
|
||||
|
||||
public FrameRootResult Compose(
|
||||
HostInputCameraResult host,
|
||||
ContentEffectsAudioResult content,
|
||||
SettingsDevToolsResult settings,
|
||||
WorldRenderResult world,
|
||||
InteractionRetainedUiResult interaction,
|
||||
LivePresentationResult live,
|
||||
SessionPlayerResult session)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(host);
|
||||
ArgumentNullException.ThrowIfNull(content);
|
||||
ArgumentNullException.ThrowIfNull(settings);
|
||||
ArgumentNullException.ThrowIfNull(world);
|
||||
ArgumentNullException.ThrowIfNull(interaction);
|
||||
ArgumentNullException.ThrowIfNull(live);
|
||||
ArgumentNullException.ThrowIfNull(session);
|
||||
|
||||
var scope = new CompositionAcquisitionScope();
|
||||
FrameRootRuntimeBindings? bindings = null;
|
||||
bool bindingsOwnedByScope = false;
|
||||
try
|
||||
{
|
||||
FrameRootResult result = ComposeCore(
|
||||
host,
|
||||
content,
|
||||
settings,
|
||||
world,
|
||||
interaction,
|
||||
live,
|
||||
session,
|
||||
scope,
|
||||
ref bindings,
|
||||
ref bindingsOwnedByScope);
|
||||
scope.Complete();
|
||||
return result;
|
||||
}
|
||||
catch (Exception failure)
|
||||
{
|
||||
if (bindings is not null && !bindingsOwnedByScope)
|
||||
{
|
||||
scope.Own(
|
||||
"frame-root runtime bindings",
|
||||
bindings,
|
||||
static value => value.Dispose());
|
||||
}
|
||||
|
||||
scope.RollbackAndThrow(failure);
|
||||
throw new System.Diagnostics.UnreachableException();
|
||||
}
|
||||
}
|
||||
|
||||
private FrameRootResult ComposeCore(
|
||||
HostInputCameraResult host,
|
||||
ContentEffectsAudioResult content,
|
||||
SettingsDevToolsResult settings,
|
||||
WorldRenderResult world,
|
||||
InteractionRetainedUiResult interaction,
|
||||
LivePresentationResult live,
|
||||
SessionPlayerResult session,
|
||||
CompositionAcquisitionScope scope,
|
||||
ref FrameRootRuntimeBindings? bindings,
|
||||
ref bool bindingsOwnedByScope)
|
||||
{
|
||||
FrameRootDependencies d = _dependencies;
|
||||
WorldRenderFoundation foundation = world.Foundation;
|
||||
var teleportRenderState =
|
||||
new LocalPlayerTeleportRenderStateSource(session.LocalTeleport);
|
||||
var renderLoginState = new RenderLoginStateSource(
|
||||
d.Options.LiveMode,
|
||||
d.PlayerMode);
|
||||
var renderFrameGlState = new RenderFrameGlStateController(
|
||||
new SilkRenderFrameGlStateApi(d.Gl));
|
||||
var renderFrameLivePreparation =
|
||||
new RuntimeRenderFrameLivePreparation(
|
||||
foundation.TextureCache,
|
||||
foundation.MeshAdapter,
|
||||
session.WorldReveal,
|
||||
teleportRenderState,
|
||||
renderLoginState,
|
||||
new LiveLoginRevealCellSource(
|
||||
live.LiveEntities,
|
||||
d.PlayerIdentity),
|
||||
live.ParticleRenderer,
|
||||
d.FrameProfiler,
|
||||
d.FrameDiagnosticsEnabled);
|
||||
var renderFrameResources = new RenderFrameResourceController(
|
||||
host.GpuFrameFlights,
|
||||
new RuntimeRenderFrameBeginResources(
|
||||
foundation.TextureCache,
|
||||
live.DrawDispatcher,
|
||||
live.EnvCellRenderer,
|
||||
live.PortalDepthMask,
|
||||
foundation.TextRenderer,
|
||||
interaction.RetainedUi?.Host.TextRenderer,
|
||||
live.ClipFrame,
|
||||
foundation.Terrain,
|
||||
foundation.SceneLighting,
|
||||
d.FrameProfiler,
|
||||
d.Gl),
|
||||
new RuntimeRenderFrameClearPhase(
|
||||
d.Gl,
|
||||
d.WorldTime,
|
||||
d.Weather,
|
||||
teleportRenderState,
|
||||
d.ParticleVisibility,
|
||||
host.WorldRenderDiagnostics,
|
||||
renderFrameGlState),
|
||||
renderFrameLivePreparation);
|
||||
Fault(FrameRootCompositionPoint.RenderResourcesCreated);
|
||||
|
||||
var renderWeatherFrame = new RenderWeatherFrameController(
|
||||
d.WorldTime,
|
||||
d.Weather);
|
||||
var skyPesFrame = new SkyPesFrameController(
|
||||
content.ScriptRunner,
|
||||
content.ParticleSink,
|
||||
d.EffectPoses,
|
||||
live.EntityEffects);
|
||||
var worldFrameEnvironment =
|
||||
new RuntimeWorldFrameEnvironmentPreparation(
|
||||
d.Options,
|
||||
d.WorldTime,
|
||||
d.Lighting,
|
||||
live.DrawDispatcher,
|
||||
live.EnvCellRenderer,
|
||||
foundation.SceneLighting,
|
||||
d.RenderRange,
|
||||
skyPesFrame);
|
||||
var worldRenderFrameBuilder = new WorldRenderFrameBuilder(
|
||||
new RuntimeWorldFrameCameraSource(
|
||||
host.CameraController,
|
||||
session.LocalTeleport),
|
||||
new RuntimeWorldFrameVisibilityPreparation(
|
||||
live.SelectionScene,
|
||||
d.ParticleVisibility,
|
||||
foundation.Terrain,
|
||||
session.WorldReveal,
|
||||
live.EnvCellFrustum),
|
||||
new RuntimeWorldFrameSettingsPreview(
|
||||
d.Settings,
|
||||
content.Audio?.Engine,
|
||||
host.CameraController,
|
||||
d.DisplayFramePacing),
|
||||
new RuntimeWorldFrameRootSource(
|
||||
d.PhysicsEngine,
|
||||
d.CellVisibility,
|
||||
d.PlayerMode,
|
||||
d.ChaseCameraInput,
|
||||
d.PlayerController,
|
||||
d.WorldOrigin),
|
||||
worldFrameEnvironment,
|
||||
new RuntimeWorldFrameAnimatedEntitySource(
|
||||
d.Animations,
|
||||
live.StaticAnimationScheduler,
|
||||
live.EquippedChildren),
|
||||
new RuntimeWorldFrameBuildingSource(
|
||||
live.LandblockPipeline,
|
||||
d.CellVisibility));
|
||||
var terrainDrawDiagnostics = new TerrainDrawDiagnosticsController(
|
||||
d.FrameDiagnosticsEnabled,
|
||||
host.WorldRenderDiagnostics,
|
||||
new RuntimeFramePipelineDiagnosticFactsSource(
|
||||
foundation.Terrain,
|
||||
live.LandblockPipeline,
|
||||
renderFrameLivePreparation,
|
||||
live.DrawDispatcher,
|
||||
session.Streaming,
|
||||
live.LiveEntities,
|
||||
live.WorldState),
|
||||
d.RenderDiagnosticLog);
|
||||
var retailPViewCells = new RetailPViewCellSource(d.CellVisibility);
|
||||
var retailPViewPassExecutor = new RetailPViewPassExecutor(
|
||||
d.Gl,
|
||||
renderFrameGlState,
|
||||
new SilkRetailPViewFramebufferSource(d.Window),
|
||||
live.ClipFrame,
|
||||
foundation.Terrain,
|
||||
live.EnvCellRenderer,
|
||||
live.DrawDispatcher,
|
||||
live.SkyRenderer,
|
||||
content.ParticleSystem,
|
||||
live.ParticleRenderer,
|
||||
live.PortalDepthMask,
|
||||
d.RetailAlphaQueue,
|
||||
host.WorldRenderDiagnostics,
|
||||
terrainDrawDiagnostics);
|
||||
var worldSceneDiagnostics = new WorldSceneDiagnosticsController(
|
||||
host.WorldRenderDiagnostics,
|
||||
new RuntimeWorldScenePViewDiagnosticSource(
|
||||
d.PlayerController,
|
||||
d.PhysicsEngine,
|
||||
d.CellVisibility),
|
||||
d.WorldSceneDebugState,
|
||||
foundation.DebugLines,
|
||||
d.PhysicsEngine,
|
||||
d.PlayerMode,
|
||||
d.PlayerController,
|
||||
d.DebugVmRenderFacts,
|
||||
settings.DevTools is not null);
|
||||
var worldScenePasses = new WorldScenePassExecutor(
|
||||
d.Gl,
|
||||
renderFrameGlState,
|
||||
live.ClipFrame,
|
||||
live.DrawDispatcher,
|
||||
live.EnvCellRenderer,
|
||||
foundation.Terrain,
|
||||
terrainDrawDiagnostics,
|
||||
live.SkyRenderer,
|
||||
content.ParticleSystem,
|
||||
live.ParticleRenderer);
|
||||
var worldSceneRenderer = new WorldSceneRenderer(
|
||||
renderFrameResources,
|
||||
renderLoginState,
|
||||
d.WorldEnvironment,
|
||||
worldRenderFrameBuilder,
|
||||
new RuntimeWorldSceneEntitySource(live.WorldState),
|
||||
live.SelectionScene,
|
||||
d.RetailAlphaQueue,
|
||||
d.ParticleVisibility,
|
||||
new WorldScenePViewRenderer(
|
||||
new RetailPViewRenderer(),
|
||||
retailPViewPassExecutor,
|
||||
retailPViewPassExecutor),
|
||||
retailPViewCells,
|
||||
worldScenePasses,
|
||||
d.RenderRange,
|
||||
worldSceneDiagnostics);
|
||||
Fault(FrameRootCompositionPoint.WorldRendererCreated);
|
||||
|
||||
bindings = new FrameRootRuntimeBindings();
|
||||
WorldLifecycleAutomationController? lifecycleAutomation = null;
|
||||
if (interaction.RetainedUi?.Screenshots is { } screenshots
|
||||
&& d.Options.AutomationArtifactDirectory is { } artifactDirectory)
|
||||
{
|
||||
var resourceSnapshots =
|
||||
new WorldLifecycleResourceSnapshotSource(
|
||||
live.WorldState,
|
||||
d.Animations,
|
||||
live.FrameDiagnostics,
|
||||
live.LiveEntities,
|
||||
session.Streaming,
|
||||
content.ParticleSystem,
|
||||
content.ParticleSink,
|
||||
live.EntityEffects,
|
||||
live.Lights,
|
||||
content.ScriptRunner,
|
||||
foundation.MeshAdapter,
|
||||
foundation.TextureCache,
|
||||
live.DrawDispatcher,
|
||||
d.FrameProfiler);
|
||||
lifecycleAutomation =
|
||||
new WorldLifecycleAutomationController(
|
||||
() => session.WorldReveal.Snapshot,
|
||||
() => session.WorldReveal.PortalMaterializationCount,
|
||||
resourceSnapshots.Capture,
|
||||
screenshots,
|
||||
artifactDirectory,
|
||||
message => d.Log("[UI-PROBE] " + message));
|
||||
bindings.Adopt(
|
||||
"world lifecycle automation",
|
||||
interaction.LateBindings.Automation.Bind(
|
||||
lifecycleAutomation));
|
||||
}
|
||||
Fault(FrameRootCompositionPoint.LifecycleAutomationBound);
|
||||
|
||||
IRetainedGameplayUiFrame? retainedGameplayUi =
|
||||
d.Options.RetailUi && interaction.RetainedUi is { } retained
|
||||
? new RetainedGameplayUiFrame(retained.Runtime, d.Input)
|
||||
: null;
|
||||
IPrivateFrameScreenshot? privateScreenshot =
|
||||
interaction.RetainedUi?.Screenshots is { } frameScreenshots
|
||||
? new PrivateFrameScreenshot(frameScreenshots)
|
||||
: null;
|
||||
var privatePresentation = new PrivatePresentationRenderer(
|
||||
new LocalPlayerPortalViewport(
|
||||
session.LocalTeleport,
|
||||
host.CameraController),
|
||||
renderFrameResources,
|
||||
live.PaperdollPresenter,
|
||||
retainedGameplayUi,
|
||||
settings.DevTools?.Presenter,
|
||||
privateScreenshot);
|
||||
var framePreparation = new RenderFramePreparationController(
|
||||
renderFrameResources,
|
||||
settings.DevTools?.Presenter,
|
||||
renderWeatherFrame);
|
||||
var renderFrame = new RenderFrameOrchestrator(
|
||||
host.GpuFrameFlights,
|
||||
framePreparation,
|
||||
worldSceneRenderer,
|
||||
privatePresentation,
|
||||
live.FrameDiagnostics,
|
||||
(IRenderFrameFailureRecovery?)settings.DevTools?.Presenter
|
||||
?? NullRenderFrameFailureRecovery.Instance);
|
||||
Fault(FrameRootCompositionPoint.RenderRootCreated);
|
||||
|
||||
var liveFrameCoordinator = new RetailLiveFrameCoordinator(
|
||||
session.LiveObjectFrame,
|
||||
live.WorldState,
|
||||
session.LiveSession,
|
||||
session.LocalPlayerFrame,
|
||||
session.LiveSpatialReconciler);
|
||||
var cameraFrame = new CameraFrameController(
|
||||
host.CameraController,
|
||||
d.InputCapture,
|
||||
d.CameraInput,
|
||||
session.LocalPlayerFrameRuntime,
|
||||
d.ChaseCameraInput,
|
||||
session.LocalPlayerFrame,
|
||||
session.LiveSpatialReconciler,
|
||||
new AcDream.App.Combat.CombatCameraTargetSource(
|
||||
d.Settings,
|
||||
d.Combat,
|
||||
d.Selection,
|
||||
live.SelectionQuery));
|
||||
var updateFrame = new UpdateFrameOrchestrator(
|
||||
new LiveEntityTeardownFramePhase(live.LiveEntities),
|
||||
new ConsoleUpdateFrameFailureSink(),
|
||||
d.UpdateClock,
|
||||
new PhysicsScriptClockPublisher(content.ScriptRunner),
|
||||
session.StreamingFrame,
|
||||
session.GameplayInput,
|
||||
liveFrameCoordinator,
|
||||
new LiveEntityLivenessFramePhase(
|
||||
session.Liveness,
|
||||
new StopwatchClientMonotonicTimeSource()),
|
||||
session.LocalTeleport,
|
||||
new PlayerModeAutoEntryFramePhase(session.PlayerModeAutoEntry),
|
||||
cameraFrame);
|
||||
Fault(FrameRootCompositionPoint.UpdateRootCreated);
|
||||
|
||||
var bindingsLease = scope.Own(
|
||||
"frame-root runtime bindings",
|
||||
bindings,
|
||||
static value => value.Dispose());
|
||||
bindingsOwnedByScope = true;
|
||||
IDisposable frameGraphPublication = d.FrameGraphs.PublishOwned(
|
||||
updateFrame,
|
||||
renderFrame);
|
||||
var graphLease = scope.Own(
|
||||
"game frame graph publication",
|
||||
frameGraphPublication,
|
||||
static value => value.Dispose());
|
||||
Fault(FrameRootCompositionPoint.FrameGraphPublished);
|
||||
|
||||
var result = new FrameRootResult(
|
||||
updateFrame,
|
||||
renderFrame,
|
||||
lifecycleAutomation,
|
||||
bindings,
|
||||
frameGraphPublication,
|
||||
session.SessionHost);
|
||||
_publication.PublishFrameRoots(result);
|
||||
graphLease.Transfer();
|
||||
bindingsLease.Transfer();
|
||||
Fault(FrameRootCompositionPoint.ResultPublished);
|
||||
return result;
|
||||
}
|
||||
|
||||
private void Fault(FrameRootCompositionPoint point) =>
|
||||
_faultInjection?.Invoke(point);
|
||||
}
|
||||
|
|
@ -40,10 +40,11 @@ internal interface ISessionPlayerCompositionPhase<in THost, in TContent, in TWor
|
|||
TLive live);
|
||||
}
|
||||
|
||||
internal interface IFrameRootCompositionPhase<in THost, in TSettings, in TWorld, in TInteraction, in TLive, in TSession, out TResult>
|
||||
internal interface IFrameRootCompositionPhase<in THost, in TContent, in TSettings, in TWorld, in TInteraction, in TLive, in TSession, out TResult>
|
||||
{
|
||||
TResult Compose(
|
||||
THost host,
|
||||
TContent content,
|
||||
TSettings settings,
|
||||
TWorld world,
|
||||
TInteraction interaction,
|
||||
|
|
@ -79,7 +80,7 @@ internal sealed class GameWindowCompositionPipeline<
|
|||
private readonly IInteractionUiCompositionPhase<THost, TContent, TSettings, TWorld, TInteraction> _interaction;
|
||||
private readonly ILivePresentationCompositionPhase<THost, TContent, TWorld, TInteraction, TLive> _live;
|
||||
private readonly ISessionPlayerCompositionPhase<THost, TContent, TWorld, TInteraction, TLive, TSession> _session;
|
||||
private readonly IFrameRootCompositionPhase<THost, TSettings, TWorld, TInteraction, TLive, TSession, TFrame> _frame;
|
||||
private readonly IFrameRootCompositionPhase<THost, TContent, TSettings, TWorld, TInteraction, TLive, TSession, TFrame> _frame;
|
||||
private readonly ISessionStartCompositionPhase<TFrame> _start;
|
||||
|
||||
public GameWindowCompositionPipeline(
|
||||
|
|
@ -90,7 +91,7 @@ internal sealed class GameWindowCompositionPipeline<
|
|||
IInteractionUiCompositionPhase<THost, TContent, TSettings, TWorld, TInteraction> interaction,
|
||||
ILivePresentationCompositionPhase<THost, TContent, TWorld, TInteraction, TLive> live,
|
||||
ISessionPlayerCompositionPhase<THost, TContent, TWorld, TInteraction, TLive, TSession> session,
|
||||
IFrameRootCompositionPhase<THost, TSettings, TWorld, TInteraction, TLive, TSession, TFrame> frame,
|
||||
IFrameRootCompositionPhase<THost, TContent, TSettings, TWorld, TInteraction, TLive, TSession, TFrame> frame,
|
||||
ISessionStartCompositionPhase<TFrame> start)
|
||||
{
|
||||
_host = host ?? throw new ArgumentNullException(nameof(host));
|
||||
|
|
@ -113,7 +114,14 @@ internal sealed class GameWindowCompositionPipeline<
|
|||
TInteraction interaction = _interaction.Compose(host, content, settings, world);
|
||||
TLive live = _live.Compose(host, content, world, interaction);
|
||||
TSession session = _session.Compose(host, content, world, interaction, live);
|
||||
TFrame frame = _frame.Compose(host, settings, world, interaction, live, session);
|
||||
TFrame frame = _frame.Compose(
|
||||
host,
|
||||
content,
|
||||
settings,
|
||||
world,
|
||||
interaction,
|
||||
live,
|
||||
session);
|
||||
_start.Start(frame);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,119 @@
|
|||
using AcDream.App.Rendering;
|
||||
using AcDream.App.Rendering.Vfx;
|
||||
using AcDream.App.Rendering.Wb;
|
||||
using AcDream.App.Streaming;
|
||||
using AcDream.App.World;
|
||||
using AcDream.Core.Vfx;
|
||||
|
||||
namespace AcDream.App.Diagnostics;
|
||||
|
||||
/// <summary>
|
||||
/// Samples the canonical owners used by the connected lifecycle gate. This is
|
||||
/// deliberately a read-only view: it owns no counters and keeps no mirrored
|
||||
/// resource state between checkpoints.
|
||||
/// </summary>
|
||||
internal sealed class WorldLifecycleResourceSnapshotSource
|
||||
{
|
||||
private readonly GpuWorldState _world;
|
||||
private readonly LiveEntityAnimationRuntimeView<LiveEntityAnimationState>
|
||||
_animations;
|
||||
private readonly RenderFrameDiagnosticsController _renderDiagnostics;
|
||||
private readonly LiveEntityRuntime _liveEntities;
|
||||
private readonly StreamingController _streaming;
|
||||
private readonly ParticleSystem _particles;
|
||||
private readonly ParticleHookSink _particleSink;
|
||||
private readonly EntityEffectController _effects;
|
||||
private readonly LiveEntityLightController _lights;
|
||||
private readonly PhysicsScriptRunner _scripts;
|
||||
private readonly WbMeshAdapter _meshes;
|
||||
private readonly TextureCache _textures;
|
||||
private readonly WbDrawDispatcher _dispatcher;
|
||||
private readonly FrameProfiler _frameProfiler;
|
||||
|
||||
public WorldLifecycleResourceSnapshotSource(
|
||||
GpuWorldState world,
|
||||
LiveEntityAnimationRuntimeView<LiveEntityAnimationState> animations,
|
||||
RenderFrameDiagnosticsController renderDiagnostics,
|
||||
LiveEntityRuntime liveEntities,
|
||||
StreamingController streaming,
|
||||
ParticleSystem particles,
|
||||
ParticleHookSink particleSink,
|
||||
EntityEffectController effects,
|
||||
LiveEntityLightController lights,
|
||||
PhysicsScriptRunner scripts,
|
||||
WbMeshAdapter meshes,
|
||||
TextureCache textures,
|
||||
WbDrawDispatcher dispatcher,
|
||||
FrameProfiler frameProfiler)
|
||||
{
|
||||
_world = world ?? throw new ArgumentNullException(nameof(world));
|
||||
_animations = animations
|
||||
?? throw new ArgumentNullException(nameof(animations));
|
||||
_renderDiagnostics = renderDiagnostics
|
||||
?? throw new ArgumentNullException(nameof(renderDiagnostics));
|
||||
_liveEntities = liveEntities
|
||||
?? throw new ArgumentNullException(nameof(liveEntities));
|
||||
_streaming = streaming
|
||||
?? throw new ArgumentNullException(nameof(streaming));
|
||||
_particles = particles
|
||||
?? throw new ArgumentNullException(nameof(particles));
|
||||
_particleSink = particleSink
|
||||
?? throw new ArgumentNullException(nameof(particleSink));
|
||||
_effects = effects ?? throw new ArgumentNullException(nameof(effects));
|
||||
_lights = lights ?? throw new ArgumentNullException(nameof(lights));
|
||||
_scripts = scripts ?? throw new ArgumentNullException(nameof(scripts));
|
||||
_meshes = meshes ?? throw new ArgumentNullException(nameof(meshes));
|
||||
_textures = textures ?? throw new ArgumentNullException(nameof(textures));
|
||||
_dispatcher = dispatcher
|
||||
?? throw new ArgumentNullException(nameof(dispatcher));
|
||||
_frameProfiler = frameProfiler
|
||||
?? throw new ArgumentNullException(nameof(frameProfiler));
|
||||
}
|
||||
|
||||
public WorldLifecycleResourceSnapshot Capture()
|
||||
{
|
||||
ObjectMeshManager meshManager = _meshes.MeshManager
|
||||
?? throw new InvalidOperationException(
|
||||
"Lifecycle snapshots require the composed modern mesh manager.");
|
||||
var mesh = meshManager.Diagnostics;
|
||||
RenderFrameDiagnosticsSnapshot render = _renderDiagnostics.Snapshot;
|
||||
GCMemoryInfo memory = GC.GetGCMemoryInfo();
|
||||
return new WorldLifecycleResourceSnapshot(
|
||||
LoadedLandblocks: _world.LoadedLandblockIds.Count,
|
||||
WorldEntities: _world.Entities.Count,
|
||||
AnimatedEntities: _animations.Count,
|
||||
VisibleLandblocks: render.VisibleLandblocks,
|
||||
TotalLandblocks: render.TotalLandblocks,
|
||||
LiveEntities: _liveEntities.Count,
|
||||
MaterializedLiveEntities: _liveEntities.MaterializedCount,
|
||||
PendingLiveTeardowns: _liveEntities.PendingTeardownCount,
|
||||
PendingLandblockRetirements: _streaming.PendingRetirementCount,
|
||||
ParticleEmitters: _particles.ActiveEmitterCount,
|
||||
Particles: _particles.ActiveParticleCount,
|
||||
ParticleBindings: _particleSink.ActiveBindingCount,
|
||||
ParticleOwners: _particleSink.TrackedOwnerCount,
|
||||
EffectOwners: _effects.ReadyOwnerCount,
|
||||
LightOwners: _lights.TrackedOwnerCount,
|
||||
ScriptOwners: _scripts.ActiveOwnerCount,
|
||||
ActiveScripts: _scripts.ActiveScriptCount,
|
||||
MeshRenderData: mesh.RenderData,
|
||||
MeshAtlasArrays: mesh.AtlasArrays,
|
||||
MeshEstimatedBytes: mesh.EstimatedBytes,
|
||||
StagedMeshUploads: _meshes.StagedUploadBacklog,
|
||||
StagedMeshBytes: _meshes.StagedUploadBytes,
|
||||
TrackedGpuBytes: GpuMemoryTracker.AllocatedBytes,
|
||||
TrackedGpuBuffers: GpuMemoryTracker.BufferCount,
|
||||
TrackedGpuTextures: GpuMemoryTracker.TextureCount,
|
||||
OwnedCompositeTextures: _textures.OwnedBindlessTextureCount,
|
||||
CompositeTextureOwners: _textures.TextureOwnerCount,
|
||||
ActiveParticleTextures: _textures.ActiveParticleTextureCount,
|
||||
ParticleTextureOwners: _textures.ParticleTextureOwnerCount,
|
||||
CompositeWarmupPending:
|
||||
_dispatcher.LastCompositeWarmupPendingCount,
|
||||
ManagedBytes: GC.GetTotalMemory(forceFullCollection: false),
|
||||
ManagedCommittedBytes: memory.TotalCommittedBytes,
|
||||
Fps: render.Fps,
|
||||
FrameMilliseconds: render.FrameMilliseconds,
|
||||
LastFrameProfile: _frameProfiler.LastReport);
|
||||
}
|
||||
}
|
||||
|
|
@ -29,12 +29,28 @@ internal sealed class GameFrameGraphSlot
|
|||
public bool IsPublished => Volatile.Read(ref _pair) is not null;
|
||||
|
||||
public void Publish(IGameUpdateFrameRoot update, IGameRenderFrameRoot render)
|
||||
{
|
||||
_ = PublishCore(update, render);
|
||||
}
|
||||
|
||||
public IDisposable PublishOwned(
|
||||
IGameUpdateFrameRoot update,
|
||||
IGameRenderFrameRoot render)
|
||||
{
|
||||
FrameGraphPair pair = PublishCore(update, render);
|
||||
return new Publication(this, pair);
|
||||
}
|
||||
|
||||
private FrameGraphPair PublishCore(
|
||||
IGameUpdateFrameRoot update,
|
||||
IGameRenderFrameRoot render)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(update);
|
||||
ArgumentNullException.ThrowIfNull(render);
|
||||
var pair = new FrameGraphPair(update, render);
|
||||
if (Interlocked.CompareExchange(ref _pair, pair, null) is not null)
|
||||
throw new InvalidOperationException("A game frame graph is already published.");
|
||||
return pair;
|
||||
}
|
||||
|
||||
public bool Tick(UpdateFrameInput input)
|
||||
|
|
@ -64,4 +80,22 @@ internal sealed class GameFrameGraphSlot
|
|||
{
|
||||
Interlocked.Exchange(ref _pair, null);
|
||||
}
|
||||
|
||||
private void Withdraw(FrameGraphPair expected) =>
|
||||
Interlocked.CompareExchange(ref _pair, null, expected);
|
||||
|
||||
private sealed class Publication : IDisposable
|
||||
{
|
||||
private GameFrameGraphSlot? _owner;
|
||||
private readonly FrameGraphPair _expected;
|
||||
|
||||
public Publication(GameFrameGraphSlot owner, FrameGraphPair expected)
|
||||
{
|
||||
_owner = owner;
|
||||
_expected = expected;
|
||||
}
|
||||
|
||||
public void Dispose() =>
|
||||
Interlocked.Exchange(ref _owner, null)?.Withdraw(_expected);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,8 @@ public sealed class GameWindow :
|
|||
IGameWindowWorldRenderPublication,
|
||||
IGameWindowInteractionRetainedUiPublication,
|
||||
IGameWindowLivePresentationPublication,
|
||||
IGameWindowSessionPlayerPublication
|
||||
IGameWindowSessionPlayerPublication,
|
||||
IGameWindowFrameRootPublication
|
||||
{
|
||||
private static double ClientTimerNow() =>
|
||||
System.Diagnostics.Stopwatch.GetTimestamp()
|
||||
|
|
@ -103,6 +104,8 @@ public sealed class GameWindow :
|
|||
private SessionPlayerRuntimeBindings? _sessionPlayerBindings;
|
||||
private AcDream.App.Diagnostics.FrameScreenshotController? _frameScreenshots;
|
||||
private AcDream.App.Diagnostics.WorldLifecycleAutomationController? _worldLifecycleAutomation;
|
||||
private FrameRootRuntimeBindings? _frameRootBindings;
|
||||
private IDisposable? _frameGraphPublication;
|
||||
private AcDream.App.Rendering.GpuFrameFlightController? _gpuFrameFlights;
|
||||
private readonly AcDream.App.Rendering.GameFrameGraphSlot _frameGraphs = new();
|
||||
private readonly AcDream.App.Rendering.GameRenderResourceLifetime
|
||||
|
|
@ -1040,6 +1043,23 @@ public sealed class GameWindow :
|
|||
_sessionPlayerBindings = result.RuntimeBindings;
|
||||
}
|
||||
|
||||
void IGameWindowFrameRootPublication.PublishFrameRoots(
|
||||
FrameRootResult result)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(result);
|
||||
if (_worldLifecycleAutomation is not null
|
||||
|| _frameRootBindings is not null
|
||||
|| _frameGraphPublication is not null)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"The GameWindow composition shell already owns frame roots.");
|
||||
}
|
||||
|
||||
_worldLifecycleAutomation = result.LifecycleAutomation;
|
||||
_frameRootBindings = result.RuntimeBindings;
|
||||
_frameGraphPublication = result.FrameGraphPublication;
|
||||
}
|
||||
|
||||
private static void PublishCompositionOwner<T>(
|
||||
ref T? destination,
|
||||
T value,
|
||||
|
|
@ -1355,268 +1375,53 @@ public sealed class GameWindow :
|
|||
interactionUi,
|
||||
livePresentation);
|
||||
|
||||
var teleportRenderState =
|
||||
new AcDream.App.Rendering.LocalPlayerTeleportRenderStateSource(
|
||||
sessionPlayer.LocalTeleport);
|
||||
var renderLoginState = new AcDream.App.Rendering.RenderLoginStateSource(
|
||||
_options.LiveMode,
|
||||
_localPlayerMode);
|
||||
var renderFrameGlState = new AcDream.App.Rendering.RenderFrameGlStateController(
|
||||
new AcDream.App.Rendering.SilkRenderFrameGlStateApi(_gl!));
|
||||
var renderFrameLivePreparation =
|
||||
new AcDream.App.Rendering.RuntimeRenderFrameLivePreparation(
|
||||
_textureCache,
|
||||
_wbMeshAdapter,
|
||||
_worldReveal,
|
||||
teleportRenderState,
|
||||
renderLoginState,
|
||||
new AcDream.App.Rendering.LiveLoginRevealCellSource(
|
||||
_liveEntities!,
|
||||
_localPlayerIdentity),
|
||||
_particleRenderer,
|
||||
_frameProfiler,
|
||||
_frameDiag);
|
||||
var renderFrameResources =
|
||||
new AcDream.App.Rendering.RenderFrameResourceController(
|
||||
_gpuFrameFlights!,
|
||||
new AcDream.App.Rendering.RuntimeRenderFrameBeginResources(
|
||||
_textureCache,
|
||||
_wbDrawDispatcher,
|
||||
_envCellRenderer,
|
||||
_portalDepthMask,
|
||||
_textRenderer,
|
||||
_uiHost?.TextRenderer,
|
||||
_clipFrame,
|
||||
_terrain,
|
||||
_sceneLightingUbo,
|
||||
_frameProfiler,
|
||||
_gl!),
|
||||
new AcDream.App.Rendering.RuntimeRenderFrameClearPhase(
|
||||
_gl!,
|
||||
WorldTime,
|
||||
Weather,
|
||||
teleportRenderState,
|
||||
_particleVisibility,
|
||||
worldRenderDiagnostics,
|
||||
renderFrameGlState),
|
||||
renderFrameLivePreparation);
|
||||
var renderWeatherFrame =
|
||||
new AcDream.App.Rendering.RenderWeatherFrameController(
|
||||
WorldTime,
|
||||
Weather);
|
||||
var skyPesFrame = new AcDream.App.Rendering.SkyPesFrameController(
|
||||
_scriptRunner!,
|
||||
_particleSink!,
|
||||
_effectPoses,
|
||||
_entityEffects);
|
||||
var worldFrameEnvironment =
|
||||
new AcDream.App.Rendering.RuntimeWorldFrameEnvironmentPreparation(
|
||||
FrameRootResult frameRoots = new FrameRootCompositionPhase(
|
||||
new FrameRootDependencies(
|
||||
_options,
|
||||
WorldTime,
|
||||
Lighting,
|
||||
_wbDrawDispatcher,
|
||||
_envCellRenderer,
|
||||
_sceneLightingUbo,
|
||||
_renderRange,
|
||||
skyPesFrame);
|
||||
var worldRenderFrameBuilder =
|
||||
new AcDream.App.Rendering.WorldRenderFrameBuilder(
|
||||
new AcDream.App.Rendering.RuntimeWorldFrameCameraSource(
|
||||
_cameraController!,
|
||||
sessionPlayer.LocalTeleport),
|
||||
new AcDream.App.Rendering.RuntimeWorldFrameVisibilityPreparation(
|
||||
_retailSelectionScene,
|
||||
_particleVisibility,
|
||||
_terrain,
|
||||
_worldReveal,
|
||||
_envCellFrustum),
|
||||
new AcDream.App.Rendering.RuntimeWorldFrameSettingsPreview(
|
||||
_runtimeSettings,
|
||||
_audioEngine,
|
||||
_cameraController!,
|
||||
_displayFramePacing),
|
||||
new AcDream.App.Rendering.RuntimeWorldFrameRootSource(
|
||||
_physicsEngine,
|
||||
_cellVisibility,
|
||||
_localPlayerMode,
|
||||
_chaseCameraInput,
|
||||
_playerControllerSlot,
|
||||
_liveWorldOrigin),
|
||||
worldFrameEnvironment,
|
||||
new AcDream.App.Rendering.RuntimeWorldFrameAnimatedEntitySource(
|
||||
_animatedEntities,
|
||||
_staticAnimationScheduler,
|
||||
_equippedChildRenderer),
|
||||
new AcDream.App.Rendering.RuntimeWorldFrameBuildingSource(
|
||||
_landblockPresentationPipeline!,
|
||||
_cellVisibility));
|
||||
var terrainDrawDiagnostics =
|
||||
new AcDream.App.Rendering.TerrainDrawDiagnosticsController(
|
||||
_frameDiag,
|
||||
worldRenderDiagnostics,
|
||||
new AcDream.App.Rendering.RuntimeFramePipelineDiagnosticFactsSource(
|
||||
_terrain,
|
||||
_landblockPresentationPipeline,
|
||||
renderFrameLivePreparation,
|
||||
_wbDrawDispatcher,
|
||||
_streamingController,
|
||||
_liveEntities!,
|
||||
_worldState),
|
||||
_renderDiagnosticLog);
|
||||
var retailPViewCells = new AcDream.App.Rendering.RetailPViewCellSource(
|
||||
_cellVisibility);
|
||||
var retailPViewPassExecutor =
|
||||
new AcDream.App.Rendering.RetailPViewPassExecutor(
|
||||
_gl!,
|
||||
renderFrameGlState,
|
||||
new AcDream.App.Rendering.SilkRetailPViewFramebufferSource(
|
||||
_window!),
|
||||
_clipFrame!,
|
||||
_terrain,
|
||||
_envCellRenderer!,
|
||||
_wbDrawDispatcher!,
|
||||
_skyRenderer,
|
||||
_particleSystem,
|
||||
_particleRenderer,
|
||||
_portalDepthMask,
|
||||
_retailAlphaQueue,
|
||||
worldRenderDiagnostics,
|
||||
terrainDrawDiagnostics);
|
||||
var worldSceneDiagnostics =
|
||||
new AcDream.App.Rendering.WorldSceneDiagnosticsController(
|
||||
worldRenderDiagnostics,
|
||||
new AcDream.App.Rendering.RuntimeWorldScenePViewDiagnosticSource(
|
||||
_playerControllerSlot,
|
||||
_physicsEngine,
|
||||
_cellVisibility),
|
||||
_worldSceneDebugState,
|
||||
_debugLines,
|
||||
_window!,
|
||||
_input!,
|
||||
WorldTime,
|
||||
Weather,
|
||||
Lighting,
|
||||
_worldEnvironment,
|
||||
_physicsEngine,
|
||||
_cellVisibility,
|
||||
_localPlayerMode,
|
||||
_localPlayerIdentity,
|
||||
_chaseCameraInput,
|
||||
_playerControllerSlot,
|
||||
_debugVmRenderFacts,
|
||||
_debugVm is not null);
|
||||
var worldScenePasses = new AcDream.App.Rendering.WorldScenePassExecutor(
|
||||
_gl!,
|
||||
renderFrameGlState,
|
||||
_clipFrame!,
|
||||
_wbDrawDispatcher!,
|
||||
_envCellRenderer!,
|
||||
_terrain,
|
||||
terrainDrawDiagnostics,
|
||||
_skyRenderer,
|
||||
_particleSystem,
|
||||
_particleRenderer);
|
||||
var worldSceneRenderer = new AcDream.App.Rendering.WorldSceneRenderer(
|
||||
renderFrameResources,
|
||||
renderLoginState,
|
||||
_worldEnvironment,
|
||||
worldRenderFrameBuilder,
|
||||
new AcDream.App.Rendering.RuntimeWorldSceneEntitySource(_worldState),
|
||||
_retailSelectionScene,
|
||||
_retailAlphaQueue,
|
||||
_particleVisibility,
|
||||
new AcDream.App.Rendering.WorldScenePViewRenderer(
|
||||
new AcDream.App.Rendering.RetailPViewRenderer(),
|
||||
retailPViewPassExecutor,
|
||||
retailPViewPassExecutor),
|
||||
retailPViewCells,
|
||||
worldScenePasses,
|
||||
_renderRange,
|
||||
worldSceneDiagnostics);
|
||||
if (_frameScreenshots is { } screenshots
|
||||
&& _options.AutomationArtifactDirectory is { } artifactDirectory)
|
||||
{
|
||||
void UiProbeLog(string message) =>
|
||||
Console.WriteLine("[UI-PROBE] " + message);
|
||||
_worldLifecycleAutomation =
|
||||
new AcDream.App.Diagnostics.WorldLifecycleAutomationController(
|
||||
() => _worldReveal?.Snapshot ?? default,
|
||||
() => _worldReveal?.PortalMaterializationCount ?? 0,
|
||||
CaptureWorldLifecycleResourceSnapshot,
|
||||
screenshots,
|
||||
artifactDirectory,
|
||||
UiProbeLog);
|
||||
interactionUi.LateBindings.AdoptLateOwnerBinding(
|
||||
"world lifecycle automation",
|
||||
interactionUi.LateBindings.Automation.Bind(
|
||||
_worldLifecycleAutomation));
|
||||
}
|
||||
AcDream.App.Rendering.IRetainedGameplayUiFrame? retainedGameplayUi =
|
||||
_options.RetailUi && _retailUiRuntime is not null
|
||||
? new AcDream.App.Rendering.RetainedGameplayUiFrame(
|
||||
_retailUiRuntime,
|
||||
_input)
|
||||
: null;
|
||||
AcDream.App.Rendering.IPrivateFrameScreenshot? privateScreenshot =
|
||||
_frameScreenshots is not null
|
||||
? new AcDream.App.Rendering.PrivateFrameScreenshot(
|
||||
_frameScreenshots)
|
||||
: null;
|
||||
var privatePresentation =
|
||||
new AcDream.App.Rendering.PrivatePresentationRenderer(
|
||||
new AcDream.App.Rendering.LocalPlayerPortalViewport(
|
||||
sessionPlayer.LocalTeleport,
|
||||
_cameraController!),
|
||||
renderFrameResources,
|
||||
_paperdollFramePresenter,
|
||||
retainedGameplayUi,
|
||||
_devToolsFramePresenter,
|
||||
privateScreenshot);
|
||||
var framePreparation =
|
||||
new AcDream.App.Rendering.RenderFramePreparationController(
|
||||
renderFrameResources,
|
||||
_devToolsFramePresenter,
|
||||
renderWeatherFrame);
|
||||
var renderFrameOrchestrator =
|
||||
new AcDream.App.Rendering.RenderFrameOrchestrator(
|
||||
_gpuFrameFlights!,
|
||||
framePreparation,
|
||||
worldSceneRenderer,
|
||||
privatePresentation,
|
||||
_renderFrameDiagnostics!,
|
||||
(AcDream.App.Rendering.IRenderFrameFailureRecovery?)
|
||||
_devToolsFramePresenter
|
||||
?? AcDream.App.Rendering.NullRenderFrameFailureRecovery.Instance);
|
||||
var liveFrameCoordinator = new AcDream.App.World.RetailLiveFrameCoordinator(
|
||||
sessionPlayer.LiveObjectFrame,
|
||||
_worldState,
|
||||
sessionPlayer.LiveSession,
|
||||
sessionPlayer.LocalPlayerFrame,
|
||||
sessionPlayer.LiveSpatialReconciler);
|
||||
var cameraFrame = new AcDream.App.Rendering.CameraFrameController(
|
||||
_cameraController!,
|
||||
_inputCapture,
|
||||
_cameraInput,
|
||||
sessionPlayer.LocalPlayerFrameRuntime,
|
||||
_chaseCameraInput,
|
||||
sessionPlayer.LocalPlayerFrame,
|
||||
sessionPlayer.LiveSpatialReconciler,
|
||||
new AcDream.App.Combat.CombatCameraTargetSource(
|
||||
_liveWorldOrigin,
|
||||
_particleVisibility,
|
||||
_effectPoses,
|
||||
_renderRange,
|
||||
_runtimeSettings,
|
||||
Combat,
|
||||
_displayFramePacing,
|
||||
_worldSceneDebugState,
|
||||
_retailAlphaQueue,
|
||||
_frameProfiler,
|
||||
_frameDiag,
|
||||
_renderDiagnosticLog,
|
||||
_debugVmRenderFacts,
|
||||
_inputCapture,
|
||||
_cameraInput,
|
||||
_selection,
|
||||
_worldSelectionQuery!));
|
||||
var updateFrameOrchestrator = new AcDream.App.Update.UpdateFrameOrchestrator(
|
||||
new AcDream.App.Update.LiveEntityTeardownFramePhase(
|
||||
livePresentation.LiveEntities),
|
||||
new AcDream.App.Update.ConsoleUpdateFrameFailureSink(),
|
||||
_updateFrameClock,
|
||||
new AcDream.App.Update.PhysicsScriptClockPublisher(_scriptRunner!),
|
||||
sessionPlayer.StreamingFrame,
|
||||
sessionPlayer.GameplayInput,
|
||||
liveFrameCoordinator,
|
||||
new AcDream.App.Update.LiveEntityLivenessFramePhase(
|
||||
sessionPlayer.Liveness,
|
||||
new AcDream.App.Update.StopwatchClientMonotonicTimeSource()),
|
||||
sessionPlayer.LocalTeleport,
|
||||
new AcDream.App.Update.PlayerModeAutoEntryFramePhase(
|
||||
sessionPlayer.PlayerModeAutoEntry),
|
||||
cameraFrame);
|
||||
_frameGraphs.Publish(updateFrameOrchestrator, renderFrameOrchestrator);
|
||||
_animatedEntities,
|
||||
_updateFrameClock,
|
||||
Combat,
|
||||
_frameGraphs,
|
||||
Console.WriteLine),
|
||||
this).Compose(
|
||||
hostInputCamera,
|
||||
contentEffectsAudio,
|
||||
settingsDevTools,
|
||||
worldRender,
|
||||
interactionUi,
|
||||
livePresentation,
|
||||
sessionPlayer);
|
||||
|
||||
AcDream.App.Net.LiveSessionStartResult liveStart =
|
||||
sessionPlayer.SessionHost.Start(_options);
|
||||
frameRoots.SessionHost.Start(_options);
|
||||
switch (liveStart.Status)
|
||||
{
|
||||
case AcDream.App.Net.LiveSessionStartStatus.MissingCredentials:
|
||||
|
|
@ -1649,54 +1454,6 @@ public sealed class GameWindow :
|
|||
out _);
|
||||
}
|
||||
|
||||
private AcDream.App.Diagnostics.WorldLifecycleResourceSnapshot
|
||||
CaptureWorldLifecycleResourceSnapshot()
|
||||
{
|
||||
var meshManager = _wbMeshAdapter?.MeshManager;
|
||||
var mesh = meshManager?.Diagnostics ?? default;
|
||||
AcDream.App.Rendering.RenderFrameDiagnosticsSnapshot render =
|
||||
_renderFrameDiagnostics?.Snapshot
|
||||
?? AcDream.App.Rendering.RenderFrameDiagnosticsSnapshot.Initial;
|
||||
GCMemoryInfo memory = GC.GetGCMemoryInfo();
|
||||
return new AcDream.App.Diagnostics.WorldLifecycleResourceSnapshot(
|
||||
LoadedLandblocks: _worldState.LoadedLandblockIds.Count,
|
||||
WorldEntities: _worldState.Entities.Count,
|
||||
AnimatedEntities: _animatedEntities.Count,
|
||||
VisibleLandblocks: render.VisibleLandblocks,
|
||||
TotalLandblocks: render.TotalLandblocks,
|
||||
LiveEntities: _liveEntities?.Count ?? 0,
|
||||
MaterializedLiveEntities: _liveEntities?.MaterializedCount ?? 0,
|
||||
PendingLiveTeardowns: _liveEntities?.PendingTeardownCount ?? 0,
|
||||
PendingLandblockRetirements:
|
||||
_streamingController?.PendingRetirementCount ?? 0,
|
||||
ParticleEmitters: _particleSystem?.ActiveEmitterCount ?? 0,
|
||||
Particles: _particleSystem?.ActiveParticleCount ?? 0,
|
||||
ParticleBindings: _particleSink?.ActiveBindingCount ?? 0,
|
||||
ParticleOwners: _particleSink?.TrackedOwnerCount ?? 0,
|
||||
EffectOwners: _entityEffects?.ReadyOwnerCount ?? 0,
|
||||
LightOwners: _liveEntityLights?.TrackedOwnerCount ?? 0,
|
||||
ScriptOwners: _scriptRunner?.ActiveOwnerCount ?? 0,
|
||||
ActiveScripts: _scriptRunner?.ActiveScriptCount ?? 0,
|
||||
MeshRenderData: mesh.RenderData,
|
||||
MeshAtlasArrays: mesh.AtlasArrays,
|
||||
MeshEstimatedBytes: mesh.EstimatedBytes,
|
||||
StagedMeshUploads: _wbMeshAdapter?.StagedUploadBacklog ?? 0,
|
||||
StagedMeshBytes: _wbMeshAdapter?.StagedUploadBytes ?? 0,
|
||||
TrackedGpuBytes: AcDream.App.Rendering.Wb.GpuMemoryTracker.AllocatedBytes,
|
||||
TrackedGpuBuffers: AcDream.App.Rendering.Wb.GpuMemoryTracker.BufferCount,
|
||||
TrackedGpuTextures: AcDream.App.Rendering.Wb.GpuMemoryTracker.TextureCount,
|
||||
OwnedCompositeTextures: _textureCache?.OwnedBindlessTextureCount ?? 0,
|
||||
CompositeTextureOwners: _textureCache?.TextureOwnerCount ?? 0,
|
||||
ActiveParticleTextures: _textureCache?.ActiveParticleTextureCount ?? 0,
|
||||
ParticleTextureOwners: _textureCache?.ParticleTextureOwnerCount ?? 0,
|
||||
CompositeWarmupPending: _wbDrawDispatcher?.LastCompositeWarmupPendingCount ?? 0,
|
||||
ManagedBytes: GC.GetTotalMemory(forceFullCollection: false),
|
||||
ManagedCommittedBytes: memory.TotalCommittedBytes,
|
||||
Fps: render.Fps,
|
||||
FrameMilliseconds: render.FrameMilliseconds,
|
||||
LastFrameProfile: _frameProfiler.LastReport);
|
||||
}
|
||||
|
||||
// IsEntityCurrentlyMoving REMOVED (2026-07-09): it powered a cache-bypass
|
||||
// narrowing that dropped settled-open doors / faded-out walls back onto the
|
||||
// stale Tier-1 rest-pose cache entry (the door/fade "flip-back"). See the
|
||||
|
|
@ -1871,7 +1628,20 @@ public sealed class GameWindow :
|
|||
[
|
||||
new("world frame composition", () =>
|
||||
{
|
||||
_frameGraphs.Withdraw();
|
||||
IDisposable? publication = _frameGraphPublication;
|
||||
if (publication is null)
|
||||
return;
|
||||
publication.Dispose();
|
||||
_frameGraphPublication = null;
|
||||
}),
|
||||
new("frame-root bindings", () =>
|
||||
{
|
||||
FrameRootRuntimeBindings? bindings = _frameRootBindings;
|
||||
if (bindings is null)
|
||||
return;
|
||||
bindings.Dispose();
|
||||
_frameRootBindings = null;
|
||||
_worldLifecycleAutomation = null;
|
||||
}),
|
||||
new("session/player bindings", () =>
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue