refactor(app): compose live presentation startup
This commit is contained in:
parent
aa6ffa5176
commit
88f32dc4e2
23 changed files with 1767 additions and 626 deletions
|
|
@ -20,7 +20,8 @@ public sealed class GameWindow :
|
|||
IGameWindowContentEffectsAudioPublication,
|
||||
IGameWindowSettingsDevToolsPublication,
|
||||
IGameWindowWorldRenderPublication,
|
||||
IGameWindowInteractionRetainedUiPublication
|
||||
IGameWindowInteractionRetainedUiPublication,
|
||||
IGameWindowLivePresentationPublication
|
||||
{
|
||||
private static double ClientTimerNow() =>
|
||||
System.Diagnostics.Stopwatch.GetTimestamp()
|
||||
|
|
@ -97,6 +98,7 @@ public sealed class GameWindow :
|
|||
_debugVmRenderFacts = new();
|
||||
private AcDream.App.Rendering.RenderFrameDiagnosticsController?
|
||||
_renderFrameDiagnostics;
|
||||
private LivePresentationRuntimeBindings? _livePresentationBindings;
|
||||
private AcDream.App.Diagnostics.FrameScreenshotController? _frameScreenshots;
|
||||
private AcDream.App.Diagnostics.WorldLifecycleAutomationController? _worldLifecycleAutomation;
|
||||
private AcDream.App.Rendering.GpuFrameFlightController? _gpuFrameFlights;
|
||||
|
|
@ -924,6 +926,76 @@ public sealed class GameWindow :
|
|||
}
|
||||
}
|
||||
|
||||
void IGameWindowLivePresentationPublication.PublishLivePresentation(
|
||||
LivePresentationResult result)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(result);
|
||||
if (_liveEntities is not null
|
||||
|| _wbEntitySpawnAdapter is not null
|
||||
|| _entityScriptActivator is not null
|
||||
|| _staticAnimationScheduler is not null
|
||||
|| _wbDrawDispatcher is not null
|
||||
|| _retailSelectionScene is not null
|
||||
|| _worldSelectionQuery is not null
|
||||
|| _selectionInteractions is not null
|
||||
|| _retainedUiGameplayBinding is not null
|
||||
|| _equippedChildRenderer is not null
|
||||
|| _entityEffects is not null
|
||||
|| _liveEntityPresentation is not null
|
||||
|| _remoteTeleportController is not null
|
||||
|| _projectileController is not null
|
||||
|| _liveEntityProjectionWithdrawal is not null
|
||||
|| _liveEntityLights is not null
|
||||
|| _liveAnimationScheduler is not null
|
||||
|| _animationPresenter is not null
|
||||
|| _paperdollViewportRenderer is not null
|
||||
|| _paperdollFramePresenter is not null
|
||||
|| _envCellRenderer is not null
|
||||
|| _envCellFrustum is not null
|
||||
|| _landblockPresentationPipeline is not null
|
||||
|| _portalDepthMask is not null
|
||||
|| _clipFrame is not null
|
||||
|| _skyRenderer is not null
|
||||
|| _particleRenderer is not null
|
||||
|| _renderFrameDiagnostics is not null
|
||||
|| _livePresentationBindings is not null)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"The GameWindow composition shell already owns live presentation state.");
|
||||
}
|
||||
|
||||
_wbEntitySpawnAdapter = result.EntitySpawnAdapter;
|
||||
_entityScriptActivator = result.EntityScriptActivator;
|
||||
_staticAnimationScheduler = result.StaticAnimationScheduler;
|
||||
_worldState = result.WorldState;
|
||||
_liveEntities = result.LiveEntities;
|
||||
_projectileController = result.ProjectileController;
|
||||
_liveEntityProjectionWithdrawal = result.ProjectionWithdrawal;
|
||||
_liveEntityLights = result.Lights;
|
||||
_liveAnimationScheduler = result.AnimationScheduler;
|
||||
_animationPresenter = result.AnimationPresenter;
|
||||
_equippedChildRenderer = result.EquippedChildren;
|
||||
_entityEffects = result.EntityEffects;
|
||||
_liveEntityPresentation = result.Presentation;
|
||||
_remoteTeleportController = result.RemoteTeleport;
|
||||
_wbDrawDispatcher = result.DrawDispatcher;
|
||||
_retailSelectionScene = result.SelectionScene;
|
||||
_worldSelectionQuery = result.SelectionQuery;
|
||||
_selectionInteractions = result.SelectionInteractions;
|
||||
_retainedUiGameplayBinding = result.RetainedGameplay;
|
||||
_paperdollViewportRenderer = result.PaperdollRenderer;
|
||||
_paperdollFramePresenter = result.PaperdollPresenter;
|
||||
_envCellFrustum = result.EnvCellFrustum;
|
||||
_envCellRenderer = result.EnvCellRenderer;
|
||||
_landblockPresentationPipeline = result.LandblockPipeline;
|
||||
_clipFrame = result.ClipFrame;
|
||||
_portalDepthMask = result.PortalDepthMask;
|
||||
_skyRenderer = result.SkyRenderer;
|
||||
_particleRenderer = result.ParticleRenderer;
|
||||
_renderFrameDiagnostics = result.FrameDiagnostics;
|
||||
_livePresentationBindings = result.RuntimeBindings;
|
||||
}
|
||||
|
||||
private static void PublishCompositionOwner<T>(
|
||||
ref T? destination,
|
||||
T value,
|
||||
|
|
@ -1124,532 +1196,52 @@ public sealed class GameWindow :
|
|||
settingsDevTools,
|
||||
worldRender);
|
||||
|
||||
AcDream.App.World.DeferredLiveEntityParentAcceptance? parentAcceptance = null;
|
||||
// Phase N.4 Task 12: construct LandblockSpawnAdapter under the feature flag
|
||||
// and rebuild _worldState so it threads the adapter in. _worldState starts
|
||||
// as an unadorned GpuWorldState (field initializer); here we replace it with
|
||||
// one that carries the adapter so AddLandblock/RemoveLandblock notify WB.
|
||||
// Phase N.4 Task 17: also construct EntitySpawnAdapter for server-spawned
|
||||
// per-instance content under the same flag.
|
||||
// N.5 mandatory path: spawn adapters + dispatcher always construct.
|
||||
// _wbMeshAdapter, _meshShader, _textureCache, and _bindlessSupport are
|
||||
// all guaranteed non-null here (startup throws above if any are missing).
|
||||
var liveEntityComponentLifecycle =
|
||||
new AcDream.App.World.DeferredLiveEntityRuntimeComponentLifecycle();
|
||||
AcDream.App.Physics.LiveEntityMotionRuntimeController?
|
||||
liveEntityMotionRuntime = null;
|
||||
{
|
||||
var wbSpawnAdapter = new AcDream.App.Rendering.Wb.LandblockSpawnAdapter(_wbMeshAdapter!);
|
||||
// Sequencer factory: look up Setup + MotionTable from dats and build
|
||||
// an AnimationSequencer. Falls back to a no-op sequencer when the
|
||||
// entity has no motion table (static props, etc.). Uses _animLoader
|
||||
// which is initialised earlier in OnLoad; it is non-null here.
|
||||
var capturedDats = _dats;
|
||||
var capturedAnimLoader = _animLoader;
|
||||
AcDream.Core.Physics.AnimationSequencer SequencerFactory(AcDream.Core.World.WorldEntity e)
|
||||
{
|
||||
if (capturedDats is not null && capturedAnimLoader is not null)
|
||||
{
|
||||
var setup = capturedDats.Get<DatReaderWriter.DBObjs.Setup>(e.SourceGfxObjOrSetupId);
|
||||
if (setup is not null)
|
||||
{
|
||||
uint mtableId = (uint)setup.DefaultMotionTable;
|
||||
if (mtableId != 0)
|
||||
{
|
||||
var mtable = capturedDats.Get<DatReaderWriter.DBObjs.MotionTable>(mtableId);
|
||||
if (mtable is not null)
|
||||
return new AcDream.Core.Physics.AnimationSequencer(setup, mtable, capturedAnimLoader);
|
||||
}
|
||||
// Setup exists but no motion table — no-op sequencer.
|
||||
return new AcDream.Core.Physics.AnimationSequencer(
|
||||
setup,
|
||||
new DatReaderWriter.DBObjs.MotionTable(),
|
||||
capturedAnimLoader);
|
||||
}
|
||||
}
|
||||
// Complete fallback: empty setup + empty motion table + null loader.
|
||||
return new AcDream.Core.Physics.AnimationSequencer(
|
||||
new DatReaderWriter.DBObjs.Setup(),
|
||||
new DatReaderWriter.DBObjs.MotionTable(),
|
||||
new NullAnimLoader());
|
||||
}
|
||||
var wbEntitySpawnAdapter = new AcDream.App.Rendering.Wb.EntitySpawnAdapter(
|
||||
_textureCache!, SequencerFactory, _wbMeshAdapter!);
|
||||
_wbEntitySpawnAdapter = wbEntitySpawnAdapter;
|
||||
|
||||
// Phase C.1.5a/b: construct EntityScriptActivator so static entities
|
||||
// (server-spawned AND dat-hydrated) fire Setup.DefaultScript through
|
||||
// the PhysicsScriptRunner on enter-world. C.1.5b adds per-part
|
||||
// transforms from the entity's exact current MeshRefs so
|
||||
// multi-emitter scripts distribute across mesh parts (closes #56).
|
||||
// Animated frames replace this snapshot through EntityEffectPoseRegistry.
|
||||
// _scriptRunner and
|
||||
// _particleSink are initialised earlier in OnLoad (line ~1083); both
|
||||
// are non-null here. The resolver lambda captures _dats and swallows
|
||||
// dat-lookup throws — see C.1.5a spec §6 (error handling) for rationale.
|
||||
AcDream.App.Rendering.Vfx.EntityEffectController? entityEffects = null;
|
||||
var staticLiveRootCommitter = new StaticLiveRootCommitter(
|
||||
_liveEntityRuntimeSlot,
|
||||
_physicsEngine.ShadowObjects,
|
||||
_liveWorldOrigin,
|
||||
_effectPoses);
|
||||
var staticAnimationResidency = new LiveStaticAnimationResidency(
|
||||
_liveEntityRuntimeSlot);
|
||||
_staticAnimationScheduler = new RetailStaticAnimatingObjectScheduler(
|
||||
capturedAnimLoader!,
|
||||
_animationHookFrames!.Capture,
|
||||
_effectPoses.Publish,
|
||||
staticAnimationResidency.IsResident,
|
||||
(entity, body) =>
|
||||
_ = staticLiveRootCommitter.Commit(entity, body),
|
||||
staticAnimationResidency.ProjectionVersion);
|
||||
AcDream.App.Rendering.Vfx.ScriptActivationInfo? ResolveActivation(AcDream.Core.World.WorldEntity e)
|
||||
{
|
||||
try
|
||||
{
|
||||
DatReaderWriter.DBObjs.Setup? setup =
|
||||
capturedDats?.Get<DatReaderWriter.DBObjs.Setup>(
|
||||
e.SourceGfxObjOrSetupId);
|
||||
if (setup is null) return null;
|
||||
uint scriptId = setup.DefaultScript.DataId;
|
||||
if (e.IndexedPartTransforms.Count == 0)
|
||||
{
|
||||
var indexed = AcDream.App.Rendering.Vfx.IndexedSetupPartPoseBuilder.Build(
|
||||
setup,
|
||||
e);
|
||||
e.SetIndexedPartPoses(indexed.Poses, indexed.Available);
|
||||
}
|
||||
IReadOnlyList<System.Numerics.Matrix4x4> parts = e.IndexedPartTransforms;
|
||||
IReadOnlyList<bool> availability = e.IndexedPartAvailable;
|
||||
var profile = AcDream.App.Rendering.Vfx.EntityEffectProfile.CreateDatStatic(setup);
|
||||
bool usesStaticAnimationWorkset = e.ServerGuid == 0
|
||||
|| (_liveEntities?.TryGetRecord(
|
||||
e.ServerGuid,
|
||||
out LiveEntityRecord liveRecord) == true
|
||||
&& (liveRecord.FinalPhysicsState
|
||||
& AcDream.Core.Physics.PhysicsStateFlags.Static) != 0);
|
||||
return new AcDream.App.Rendering.Vfx.ScriptActivationInfo(
|
||||
scriptId,
|
||||
parts,
|
||||
profile,
|
||||
availability,
|
||||
setup,
|
||||
(uint)setup.DefaultAnimation,
|
||||
usesStaticAnimationWorkset);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
var entityScriptActivator = new AcDream.App.Rendering.Vfx.EntityScriptActivator(
|
||||
_scriptRunner!,
|
||||
_particleSink!,
|
||||
_effectPoses,
|
||||
ResolveActivation,
|
||||
(ownerId, entity, profile) =>
|
||||
entityEffects?.OnDatStaticEntityReady(ownerId, entity, profile),
|
||||
ownerId =>
|
||||
{
|
||||
entityEffects?.OnDatStaticEntityRemoved(ownerId);
|
||||
_lightingSink?.UnregisterOwner(ownerId);
|
||||
_translucencyFades.ClearEntity(ownerId);
|
||||
},
|
||||
(entity, info) => _staticAnimationScheduler.Register(entity, info),
|
||||
ownerId => _staticAnimationScheduler.Unregister(ownerId),
|
||||
(entity, info) => _staticAnimationScheduler.Rebind(entity, info));
|
||||
_entityScriptActivator = entityScriptActivator;
|
||||
|
||||
// Phase Post-A.5 #53 (Task 12): wire EntityClassificationCache.InvalidateLandblock
|
||||
// so Tier 1 cache entries get swept on LB demote (Near to Far) and unload.
|
||||
// Per spec §5.3 W3b. The callback receives the canonical landblock id
|
||||
// matching the LandblockHint stored at Populate time.
|
||||
_worldState = new AcDream.App.Streaming.GpuWorldState(
|
||||
wbSpawnAdapter,
|
||||
onLandblockUnloaded: _classificationCache.InvalidateLandblock,
|
||||
entityScriptActivator: entityScriptActivator);
|
||||
_devToolsComposition?.LateBindings.WorldEntities.Bind(_worldState);
|
||||
_liveEntities = new AcDream.App.World.LiveEntityRuntime(
|
||||
_worldState,
|
||||
new AcDream.App.World.CompositeLiveEntityResourceLifecycle(
|
||||
new(
|
||||
entity => wbEntitySpawnAdapter.OnCreate(entity),
|
||||
entity => wbEntitySpawnAdapter.OnRemove(entity)),
|
||||
new(
|
||||
entityScriptActivator.OnCreate,
|
||||
entityScriptActivator.OnRemove)),
|
||||
liveEntityComponentLifecycle);
|
||||
_liveEntityRuntimeSlot.Bind(_liveEntities);
|
||||
liveEntityMotionRuntime =
|
||||
new AcDream.App.Physics.LiveEntityMotionRuntimeController(
|
||||
_liveEntities,
|
||||
LivePresentationResult livePresentation =
|
||||
new LivePresentationCompositionPhase(
|
||||
new LivePresentationDependencies(
|
||||
_options,
|
||||
platform.Graphics,
|
||||
_window!,
|
||||
_datLock,
|
||||
_runtimeSettings,
|
||||
_hostQuiescence,
|
||||
_physicsEngine,
|
||||
_physicsDataCache,
|
||||
() => _selectionInteractions,
|
||||
_selection,
|
||||
_liveWorldOrigin);
|
||||
_liveEntityMotionBindings.Bind(liveEntityMotionRuntime);
|
||||
_liveEntities.ProjectionVisibilityChanged += (record, visible) =>
|
||||
{
|
||||
if (record.WorldEntity is { } entity)
|
||||
wbEntitySpawnAdapter.SetPresentationResident(entity, visible);
|
||||
};
|
||||
_liveEntities.ProjectionVisibilityChanged += (record, visible) =>
|
||||
{
|
||||
if (record.WorldEntity is { } entity)
|
||||
_particleSink!.SetEntityPresentationVisible(entity.Id, visible);
|
||||
};
|
||||
_projectileController = new AcDream.App.Physics.ProjectileController(
|
||||
_liveEntities,
|
||||
_physicsEngine,
|
||||
new AcDream.App.Physics.DatProjectileSetupResolver(_dats!, _datLock),
|
||||
new EntityRootPosePublisher(_effectPoses),
|
||||
_liveWorldOrigin)
|
||||
{
|
||||
DiagnosticSink = message => Console.Error.WriteLine($"projectile: {message}"),
|
||||
};
|
||||
_liveEntityProjectionWithdrawal =
|
||||
new AcDream.App.World.LiveEntityProjectionWithdrawalController(
|
||||
_liveEntities,
|
||||
_projectileController,
|
||||
_worldGameState,
|
||||
_worldEvents,
|
||||
_physicsEngine.ShadowObjects,
|
||||
_effectPoses,
|
||||
_localPlayerShadow);
|
||||
_liveEntityLights = new AcDream.App.Rendering.Vfx.LiveEntityLightController(
|
||||
_liveEntities,
|
||||
_effectPoses,
|
||||
_lightingSink!,
|
||||
setupId => _dats!.Get<DatReaderWriter.DBObjs.Setup>(setupId));
|
||||
|
||||
var ordinaryPhysicsUpdater =
|
||||
new AcDream.App.Physics.LiveEntityOrdinaryPhysicsUpdater(
|
||||
_physicsEngine,
|
||||
_liveEntityMotionBindings.GetSetupCylinder);
|
||||
_liveAnimationScheduler = new LiveEntityAnimationScheduler(
|
||||
_liveEntities,
|
||||
_localPlayerIdentity,
|
||||
_remotePhysicsUpdater,
|
||||
ordinaryPhysicsUpdater,
|
||||
_projectileController,
|
||||
new EntityRootPosePublisher(_effectPoses),
|
||||
new AnimationHookCaptureSink(_animationHookFrames!));
|
||||
_animationPresenter = new LiveEntityAnimationPresenter(
|
||||
_liveEntities,
|
||||
_staticAnimationScheduler,
|
||||
_effectPoses,
|
||||
new LiveAnimationPresentationContext(
|
||||
_liveEntities,
|
||||
_localPlayerIdentity,
|
||||
_playerControllerSlot),
|
||||
_animationDiagnostics,
|
||||
_options.HidePartIndex);
|
||||
|
||||
parentAcceptance = new AcDream.App.World.DeferredLiveEntityParentAcceptance();
|
||||
_equippedChildRenderer = new AcDream.App.Rendering.EquippedChildRenderController(
|
||||
_dats!,
|
||||
_datLock,
|
||||
Objects,
|
||||
_liveEntities,
|
||||
_effectPoses,
|
||||
parentAcceptance.TryAccept,
|
||||
(childRecord, positionVersion, projectionVersion) =>
|
||||
_liveEntityProjectionWithdrawal!.WithdrawExact(
|
||||
childRecord,
|
||||
positionVersion,
|
||||
projectionVersion,
|
||||
_playerServerGuid));
|
||||
|
||||
var tableResolver = new AcDream.Core.Vfx.PhysicsScriptTableResolver(
|
||||
id => _dats!.Get<DatReaderWriter.DBObjs.PhysicsScriptTable>(id));
|
||||
entityEffects = new AcDream.App.Rendering.Vfx.EntityEffectController(
|
||||
_liveEntities,
|
||||
_scriptRunner!,
|
||||
tableResolver,
|
||||
_effectPoses,
|
||||
(parentLocalId, partIndex) =>
|
||||
_equippedChildRenderer.FindChildLocalIdAtPart(parentLocalId, partIndex),
|
||||
childLocalId => _equippedChildRenderer.FindParentLocalId(childLocalId),
|
||||
ownerId => _entitySoundTables?.Remove(ownerId),
|
||||
(ownerId, soundTableDid) =>
|
||||
{
|
||||
_entitySoundTables?.Remove(ownerId);
|
||||
if (soundTableDid is { } did)
|
||||
_entitySoundTables?.Set(ownerId, did);
|
||||
});
|
||||
_entityEffects = entityEffects;
|
||||
_entityEffectAdvance.Bind(entityEffects);
|
||||
entityEffects.DiagnosticSink = message =>
|
||||
Console.Error.WriteLine($"vfx: {message}");
|
||||
var partArrayLifecycle =
|
||||
new AcDream.App.Rendering.LiveEntityPartArrayLifecycle(
|
||||
_animatedEntities);
|
||||
_liveEntityPresentation = new AcDream.App.World.LiveEntityPresentationController(
|
||||
_liveEntities,
|
||||
_physicsEngine.ShadowObjects,
|
||||
entityEffects.PlayTypedFromHiddenTransition,
|
||||
_equippedChildRenderer.SetDirectChildrenNoDraw,
|
||||
_liveEntityMotionBindings.ClearTargetForHiddenEntity,
|
||||
_liveWorldOrigin.GetCenter,
|
||||
handlePartArrayEnterWorld: partArrayLifecycle.HandleEnterWorld);
|
||||
var remoteShadowPlacement =
|
||||
new AcDream.App.Physics.RemoteShadowPlacementSynchronizer(
|
||||
_remotePhysicsUpdater,
|
||||
_liveWorldOrigin);
|
||||
var remoteTeleportPresentation =
|
||||
new AcDream.App.Physics.RemoteTeleportPlacementPresentation(
|
||||
_liveEntityPresentation);
|
||||
_remoteTeleportController = new AcDream.App.Physics.RemoteTeleportController(
|
||||
_physicsEngine,
|
||||
_liveEntities,
|
||||
_liveEntityMotionBindings.GetSetupCylinder,
|
||||
_liveWorldOrigin.CellLocalForSeed,
|
||||
remoteShadowPlacement.Sync,
|
||||
remoteTeleportPresentation.Complete,
|
||||
remoteTeleportPresentation.Begin);
|
||||
_equippedChildRenderer.ProjectionPoseReady += guid =>
|
||||
_liveEntityLights.OnAttachedPoseReady(guid);
|
||||
_hookRegistrations!.Register(entityEffects);
|
||||
|
||||
_wbDrawDispatcher = new AcDream.App.Rendering.Wb.WbDrawDispatcher(
|
||||
_gl, _meshShader!, _textureCache!, _wbMeshAdapter!, _wbEntitySpawnAdapter, _bindlessSupport!,
|
||||
_classificationCache, _translucencyFades,
|
||||
_retailSelectionScene ??= new AcDream.App.Rendering.Selection.RetailSelectionScene(
|
||||
new AcDream.App.Rendering.Selection.RetailSelectionGeometryCache(
|
||||
_dats!, _datLock)),
|
||||
_retailAlphaQueue);
|
||||
_worldSelectionQuery ??= new AcDream.App.Interaction.WorldSelectionQuery(
|
||||
_liveEntities,
|
||||
Objects,
|
||||
_retailSelectionScene,
|
||||
() => _localPlayerIdentity.ServerGuid,
|
||||
interactionUi.LateBindings.SelectionCamera.Snapshot,
|
||||
() => new System.Numerics.Vector2(
|
||||
_pointerPosition.X,
|
||||
_pointerPosition.Y),
|
||||
() => _playerControllerSlot.Controller is { } player
|
||||
? new AcDream.App.Interaction.PlayerInteractionPose(
|
||||
player.CellId,
|
||||
player.Position)
|
||||
: null,
|
||||
_liveEntityMotionBindings.GetSetupCylinder,
|
||||
setupId =>
|
||||
{
|
||||
lock (_datLock)
|
||||
{
|
||||
if (!contentEffectsAudio.Dats.TryGet<DatReaderWriter.DBObjs.Setup>(
|
||||
setupId,
|
||||
out var setup)
|
||||
|| setup.SelectionSphere is not { } sphere)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return (sphere.Origin, sphere.Radius);
|
||||
}
|
||||
});
|
||||
var radarSnapshotProvider = new AcDream.App.UI.Layout.RadarSnapshotProvider(
|
||||
Objects,
|
||||
() => _liveEntities.WorldEntities,
|
||||
() => _liveEntities.Snapshots,
|
||||
playerGuid: () => _localPlayerIdentity.ServerGuid,
|
||||
playerYawRadians: () => _playerControllerSlot.Controller?.Yaw ?? 0f,
|
||||
playerCellId: () => _playerControllerSlot.Controller?.CellId ?? 0u,
|
||||
selectedGuid: () => _selection.SelectedObjectId,
|
||||
coordinatesOnRadar: () =>
|
||||
_runtimeSettings.Gameplay.CoordinatesOnRadar,
|
||||
uiLocked: () => _runtimeSettings.Gameplay.LockUI,
|
||||
playerEntities: () => _liveEntities.MaterializedWorldEntities,
|
||||
spatialQuery: () => _worldState);
|
||||
interactionUi.LateBindings.AdoptLateOwnerBinding(
|
||||
"radar snapshot",
|
||||
interactionUi.LateBindings.Radar.Bind(radarSnapshotProvider));
|
||||
if (_itemInteractionController is { } itemInteractions)
|
||||
{
|
||||
_selectionInteractions ??= new AcDream.App.Interaction.SelectionInteractionController(
|
||||
_selection,
|
||||
_worldSelectionQuery,
|
||||
itemInteractions,
|
||||
new AcDream.App.Interaction.WorldSessionSelectionInteractionTransport(
|
||||
() => interactionUi.LateBindings.Session.CurrentSession),
|
||||
new AcDream.App.Interaction.PlayerInteractionMovementSink(
|
||||
() => _playerControllerSlot.Controller,
|
||||
_playerApproachCompletions),
|
||||
compositionToast,
|
||||
_playerApproachCompletions);
|
||||
interactionUi.LateBindings.AdoptLateOwnerBinding(
|
||||
"world selection",
|
||||
interactionUi.LateBindings.Selection.Bind(
|
||||
_worldSelectionQuery,
|
||||
_selectionInteractions));
|
||||
}
|
||||
if (_uiHost is { } retainedHost
|
||||
&& _selectionInteractions is { } selectionInteractions)
|
||||
{
|
||||
_retainedUiGameplayBinding =
|
||||
AcDream.App.Input.RetainedUiGameplayBinding.Create(
|
||||
retainedHost.Root,
|
||||
(item, x, y) =>
|
||||
selectionInteractions.PlaceDraggedItem(item, x, y),
|
||||
_hostQuiescence);
|
||||
_retainedUiGameplayBinding.Attach();
|
||||
}
|
||||
// A.5 T22.5: apply A2C gate from quality preset.
|
||||
_wbDrawDispatcher.AlphaToCoverage =
|
||||
_runtimeSettings.ResolvedQuality.AlphaToCoverage;
|
||||
|
||||
// Slice 2: now that the dispatcher exists, build the paperdoll doll RTT renderer and hand it to
|
||||
// the viewport widget. Reuses the dispatcher + the scene-lighting UBO + _gl. The widget only
|
||||
// blits the texture; the pre-UI hook drives the 3-D pass (see OnRender).
|
||||
if (_retailUiRuntime?.PaperdollViewportWidget is { } paperdollViewport
|
||||
&& _retailUiRuntime.InventoryFrame is { } paperdollInventoryFrame
|
||||
&& _sceneLightingUbo is not null)
|
||||
{
|
||||
_paperdollViewportRenderer = new AcDream.App.Rendering.PaperdollViewportRenderer(
|
||||
_gl, _wbDrawDispatcher, _sceneLightingUbo, _textureCache!);
|
||||
paperdollViewport.Renderer = _paperdollViewportRenderer;
|
||||
_paperdollFramePresenter =
|
||||
new AcDream.App.Rendering.PaperdollFramePresenter(
|
||||
_paperdollViewportRenderer,
|
||||
new AcDream.App.Rendering.RetailPaperdollFrameView(
|
||||
paperdollViewport,
|
||||
new AcDream.App.Rendering.PaperdollInventoryVisibility(
|
||||
paperdollInventoryFrame)),
|
||||
new AcDream.App.Rendering.RetailPaperdollDollFactory(
|
||||
new AcDream.App.Rendering.LivePaperdollEntityLookup(
|
||||
_liveEntities),
|
||||
_localPlayerIdentity,
|
||||
new AcDream.App.Rendering.RetailPaperdollPoseApplicator(
|
||||
_dats!,
|
||||
_animLoader!,
|
||||
_datLock)));
|
||||
}
|
||||
|
||||
// Phase A8: EnvCellRenderer init. Shares _meshShader (mesh_modern.{vert,frag})
|
||||
// with the dispatcher — both consume the same global mesh buffer (VAO/IBO)
|
||||
// from ObjectMeshManager.GlobalBuffer.
|
||||
_envCellFrustum = new AcDream.App.Rendering.Wb.WbFrustum();
|
||||
_envCellRenderer = new AcDream.App.Rendering.Wb.EnvCellRenderer(
|
||||
_gl, _wbMeshAdapter!.MeshManager!, _envCellFrustum);
|
||||
_envCellRenderer.Initialize(_meshShader!);
|
||||
var landblockRenderPublisher = new AcDream.App.Streaming.LandblockRenderPublisher(
|
||||
publishTerrain: (landblockId, meshData, origin) =>
|
||||
_terrain!.AddLandblockWithMesh(landblockId, meshData, origin),
|
||||
removeTerrain: landblockId => _terrain!.RemoveLandblock(landblockId),
|
||||
cellVisibility: _cellVisibility,
|
||||
worldState: _worldState,
|
||||
commitEnvCells: _envCellRenderer.CommitLandblock,
|
||||
prepareEnvCells: build =>
|
||||
AcDream.App.Rendering.Wb.EnvCellMeshPreparationScheduler.Schedule(
|
||||
build,
|
||||
_wbMeshAdapter.MeshManager!),
|
||||
removeEnvCells: _envCellRenderer.RemoveLandblock);
|
||||
var landblockPhysicsPublisher =
|
||||
new AcDream.App.Streaming.LandblockPhysicsPublisher(
|
||||
_physicsEngine,
|
||||
_heightTable!);
|
||||
var landblockStaticPresentationPublisher =
|
||||
new AcDream.App.Streaming.LandblockStaticPresentationPublisher(
|
||||
_lightingSink!,
|
||||
_translucencyFades,
|
||||
_worldGameState,
|
||||
_worldEvents);
|
||||
var landblockRetirementOwner =
|
||||
new AcDream.App.Streaming.LandblockPresentationRetirementOwner(
|
||||
landblockRenderPublisher,
|
||||
landblockPhysicsPublisher,
|
||||
landblockStaticPresentationPublisher,
|
||||
_lightingSink!,
|
||||
_translucencyFades);
|
||||
_landblockPresentationPipeline =
|
||||
new AcDream.App.Streaming.LandblockPresentationPipeline(
|
||||
landblockRenderPublisher,
|
||||
landblockPhysicsPublisher,
|
||||
landblockStaticPresentationPublisher,
|
||||
_worldState,
|
||||
landblockRetirementOwner,
|
||||
onLandblockLoaded: loadedLandblockId =>
|
||||
_liveEntityHydration!.OnLandblockLoaded(loadedLandblockId),
|
||||
ensureEnvCellMeshes:
|
||||
landblockRenderPublisher.PrepareAfterRenderPins);
|
||||
|
||||
_clipFrame ??= ClipFrame.NoClip();
|
||||
// T1: invisible portal depth writes (seal/punch) — retail
|
||||
// DrawPortalPolyInternal (Ghidra 0x0059bc90).
|
||||
_portalDepthMask = new AcDream.App.Rendering.PortalDepthMaskRenderer(_gl);
|
||||
_portalTunnelFallback.AcquirePrepared(
|
||||
() => AcDream.App.Rendering.PortalTunnelPresentation.CreateRequired(
|
||||
_gl,
|
||||
_dats!,
|
||||
_animLoader!,
|
||||
_hookRouter,
|
||||
_wbDrawDispatcher,
|
||||
_sceneLightingUbo!,
|
||||
_wbMeshAdapter!),
|
||||
static portalTunnel => portalTunnel.PrepareResources());
|
||||
// Publish the presentation before touching the mesh-reference
|
||||
// backend. Partial acquisition remains reachable by staged
|
||||
// shutdown instead of becoming constructor-local leaked state.
|
||||
}
|
||||
|
||||
// Phase G.1 sky renderer — its own shader (sky.vert / sky.frag)
|
||||
// with depth writes off + far plane 1e6 so celestial meshes
|
||||
// never clip. Shares the TextureCache with the static pipeline.
|
||||
var skyShader = _renderResourceLifetime.AcquireSkyShader(
|
||||
() => new Shader(
|
||||
_gl,
|
||||
Path.Combine(shadersDir, "sky.vert"),
|
||||
Path.Combine(shadersDir, "sky.frag")));
|
||||
_skyRenderer = new AcDream.App.Rendering.Sky.SkyRenderer(
|
||||
_gl,
|
||||
contentEffectsAudio.Dats,
|
||||
skyShader,
|
||||
_textureCache!,
|
||||
_samplerCache!);
|
||||
|
||||
// Phase G.1 particle renderer — renders rain / snow / spell auras
|
||||
// spawned into the shared ParticleSystem. Mode-1/no-degrade GfxObjs
|
||||
// retain their authored mesh; Always2D degrade modes use billboards.
|
||||
// Weather uses AttachLocal emitters so its volume follows the player.
|
||||
_particleRenderer = new ParticleRenderer(
|
||||
_gl,
|
||||
shadersDir,
|
||||
contentEffectsAudio.ParticleSystem,
|
||||
_textureCache,
|
||||
contentEffectsAudio.Dats,
|
||||
_wbMeshAdapter,
|
||||
_retailAlphaQueue);
|
||||
|
||||
AcDream.App.Rendering.IRenderFrameResourceDiagnosticsSource? resourceDiagnostics =
|
||||
_options.UiProbeDump
|
||||
? new AcDream.App.Rendering.RuntimeRenderFrameResourceDiagnosticsSource(
|
||||
_particleSystem,
|
||||
_particleSink,
|
||||
_wbDrawDispatcher,
|
||||
_envCellRenderer,
|
||||
_particleRenderer,
|
||||
_uiHost?.TextRenderer,
|
||||
_portalDepthMask,
|
||||
_clipFrame,
|
||||
_terrain,
|
||||
_sceneLightingUbo,
|
||||
_wbMeshAdapter,
|
||||
_textureCache)
|
||||
: null;
|
||||
_renderFrameDiagnostics =
|
||||
new AcDream.App.Rendering.RenderFrameDiagnosticsController(
|
||||
new AcDream.App.Rendering.RuntimeRenderFrameTitleFactsSource(
|
||||
_worldState,
|
||||
Objects,
|
||||
_liveEntityRuntimeSlot,
|
||||
_liveEntityMotionBindings,
|
||||
_entityEffectAdvance,
|
||||
_effectPoses,
|
||||
_remotePhysicsUpdater,
|
||||
_localPlayerShadow,
|
||||
_animatedEntities,
|
||||
WorldTime),
|
||||
new AcDream.App.Rendering.SilkRenderFrameTitleSink(_window!),
|
||||
_renderDiagnosticLog,
|
||||
_options.UiProbeDump,
|
||||
resourceDiagnostics);
|
||||
_devToolsComposition?.LateBindings.FrameDiagnostics.Bind(
|
||||
_renderFrameDiagnostics);
|
||||
interactionUi.LateBindings.AdoptLateOwnerBinding(
|
||||
"render-frame diagnostics",
|
||||
_uiFrameDiagnostics.BindOwned(_renderFrameDiagnostics));
|
||||
_animationDiagnostics,
|
||||
_classificationCache,
|
||||
_translucencyFades,
|
||||
_retailAlphaQueue,
|
||||
_cellVisibility,
|
||||
_liveWorldOrigin,
|
||||
_localPlayerIdentity,
|
||||
_playerControllerSlot,
|
||||
_pointerPosition,
|
||||
_playerApproachCompletions,
|
||||
_renderResourceLifetime,
|
||||
_portalTunnelFallback,
|
||||
_hookRouter,
|
||||
_renderDiagnosticLog,
|
||||
WorldTime,
|
||||
settingsDevTools.DevTools?.LateBindings.WorldEntities,
|
||||
settingsDevTools.DevTools?.LateBindings.FrameDiagnostics,
|
||||
_uiFrameDiagnostics,
|
||||
compositionToast),
|
||||
this).Compose(
|
||||
hostInputCamera,
|
||||
contentEffectsAudio,
|
||||
worldRender,
|
||||
interactionUi);
|
||||
|
||||
// Apply radii from the same immutable quality snapshot used for the
|
||||
// window's MSAA, terrain anisotropy, and dispatcher A2C setup.
|
||||
|
|
@ -1792,7 +1384,7 @@ public sealed class GameWindow :
|
|||
_liveSessionController);
|
||||
var liveEntityTeardown =
|
||||
new AcDream.App.World.LiveEntityRuntimeTeardownController(
|
||||
_liveEntities,
|
||||
livePresentation.LiveEntities,
|
||||
_liveEntityPresentation!,
|
||||
_entityEffects!,
|
||||
_remoteTeleportController!,
|
||||
|
|
@ -1807,23 +1399,23 @@ public sealed class GameWindow :
|
|||
_liveEntityLights!,
|
||||
_classificationCache,
|
||||
_localPlayerIdentity);
|
||||
liveEntityComponentLifecycle.Bind(liveEntityTeardown);
|
||||
livePresentation.ComponentLifecycle.Bind(liveEntityTeardown);
|
||||
var liveEntityDeletion =
|
||||
new AcDream.App.World.LiveEntityDeletionController(
|
||||
_liveEntities,
|
||||
livePresentation.LiveEntities,
|
||||
Objects,
|
||||
liveEntityTeardown,
|
||||
_localPlayerIdentity,
|
||||
_options.DumpLiveSpawns ? Console.WriteLine : null);
|
||||
_liveEntityHydration = new AcDream.App.World.LiveEntityHydrationController(
|
||||
_liveEntities,
|
||||
livePresentation.LiveEntities,
|
||||
Objects,
|
||||
_datLock,
|
||||
projectionMaterializer,
|
||||
new AcDream.App.World.LiveEntityRelationshipProjection(
|
||||
_equippedChildRenderer),
|
||||
livePresentation.EquippedChildren),
|
||||
new AcDream.App.World.LiveEntityReadyPublisher(
|
||||
_liveEntities,
|
||||
livePresentation.LiveEntities,
|
||||
_entityEffects!,
|
||||
_liveEntityPresentation!),
|
||||
originCoordinator,
|
||||
|
|
@ -1832,9 +1424,12 @@ public sealed class GameWindow :
|
|||
_localPlayerIdentity,
|
||||
liveEntityDeletion,
|
||||
_options.DumpLiveSpawns ? Console.WriteLine : null);
|
||||
livePresentation.RuntimeBindings.Adopt(
|
||||
"landblock-loaded hydration",
|
||||
livePresentation.LandblockLoaded.Bind(_liveEntityHydration));
|
||||
_liveEntityNetworkUpdates =
|
||||
new AcDream.App.Physics.LiveEntityNetworkUpdateController(
|
||||
_liveEntities,
|
||||
livePresentation.LiveEntities,
|
||||
Objects,
|
||||
_liveEntityHydration,
|
||||
_entityEffects!,
|
||||
|
|
@ -1849,7 +1444,7 @@ public sealed class GameWindow :
|
|||
_remoteMovementObservations,
|
||||
_remotePhysicsUpdater,
|
||||
_remoteInboundMotion,
|
||||
liveEntityMotionRuntime!,
|
||||
livePresentation.MotionRuntime,
|
||||
_physicsEngine,
|
||||
_dats!,
|
||||
_animLoader!,
|
||||
|
|
@ -1865,7 +1460,7 @@ public sealed class GameWindow :
|
|||
localPhysicsTimestamps.Publish,
|
||||
_movementTruthDiagnostics);
|
||||
_liveEntityLiveness = new AcDream.App.World.LiveEntityLivenessController(
|
||||
_liveEntities,
|
||||
livePresentation.LiveEntities,
|
||||
_localPlayerIdentity,
|
||||
liveEntityDeletion);
|
||||
_liveEntitySessionEvents = new AcDream.App.Net.LiveEntitySessionController(
|
||||
|
|
@ -1874,9 +1469,10 @@ public sealed class GameWindow :
|
|||
_liveEntityNetworkUpdates,
|
||||
_localPlayerTeleportSink,
|
||||
_entityEffects!);
|
||||
parentAcceptance!.Bind(_liveEntityHydration.TryAcceptParentForProjection);
|
||||
livePresentation.ParentAcceptance.Bind(
|
||||
_liveEntityHydration.TryAcceptParentForProjection);
|
||||
networkUpdateBridge.Bind(_liveEntityNetworkUpdates);
|
||||
_equippedChildRenderer.EntityReady += candidate =>
|
||||
livePresentation.EquippedChildren.EntityReady += candidate =>
|
||||
_liveEntityHydration.OnEntityReady(candidate);
|
||||
_liveEntityHydration.AppearanceApplied += guid =>
|
||||
{
|
||||
|
|
@ -1940,7 +1536,7 @@ public sealed class GameWindow :
|
|||
_streamingController!,
|
||||
new AcDream.App.Streaming.LiveProjectionRescueRebucketter(
|
||||
_worldState,
|
||||
_liveEntities));
|
||||
livePresentation.LiveEntities));
|
||||
var liveEffectFrame = new AcDream.App.Update.LiveEffectFrameController(
|
||||
_translucencyFades,
|
||||
_animationHookFrames!,
|
||||
|
|
@ -1961,7 +1557,7 @@ public sealed class GameWindow :
|
|||
_liveEntityLights!);
|
||||
_localPlayerAnimation =
|
||||
new AcDream.App.Input.LocalPlayerAnimationController(
|
||||
_liveEntities,
|
||||
livePresentation.LiveEntities,
|
||||
_localPlayerIdentity,
|
||||
_animatedEntities,
|
||||
_animationPresenter,
|
||||
|
|
@ -1969,14 +1565,14 @@ public sealed class GameWindow :
|
|||
_localPlayerShadowSynchronizer =
|
||||
new AcDream.App.Physics.LocalPlayerShadowSynchronizer(
|
||||
_physicsEngine,
|
||||
_liveEntities,
|
||||
livePresentation.LiveEntities,
|
||||
_localPlayerIdentity,
|
||||
_liveWorldOrigin,
|
||||
_localPlayerShadow);
|
||||
var localPlayerProjection =
|
||||
new AcDream.App.Input.LocalPlayerProjectionController(
|
||||
new AcDream.App.Input.LiveLocalPlayerProjectionRuntime(
|
||||
_liveEntities,
|
||||
livePresentation.LiveEntities,
|
||||
_localPlayerIdentity,
|
||||
_liveWorldOrigin,
|
||||
_localPlayerShadowSynchronizer));
|
||||
|
|
@ -1988,7 +1584,7 @@ public sealed class GameWindow :
|
|||
_chaseCameraInput,
|
||||
_movementInput,
|
||||
_inputCapture,
|
||||
_liveEntities,
|
||||
livePresentation.LiveEntities,
|
||||
_localPlayerIdentity,
|
||||
_playerHostSlot,
|
||||
localPlayerProjection,
|
||||
|
|
@ -2002,11 +1598,11 @@ public sealed class GameWindow :
|
|||
_inboundEntityEvents,
|
||||
localPlayerFrame,
|
||||
_selectionInteractions,
|
||||
_liveEntities,
|
||||
livePresentation.LiveEntities,
|
||||
_localPlayerIdentity,
|
||||
_liveWorldOrigin,
|
||||
_liveAnimationScheduler,
|
||||
_staticAnimationScheduler,
|
||||
livePresentation.StaticAnimationScheduler,
|
||||
_animationPresenter,
|
||||
_animatedEntities,
|
||||
_equippedChildRenderer!,
|
||||
|
|
@ -2018,7 +1614,7 @@ public sealed class GameWindow :
|
|||
_chaseCameraInput,
|
||||
_cameraController!,
|
||||
_physicsEngine,
|
||||
_liveEntities,
|
||||
livePresentation.LiveEntities,
|
||||
_localPlayerIdentity,
|
||||
_liveWorldOrigin,
|
||||
_liveEntityMotionBindings,
|
||||
|
|
@ -2040,7 +1636,7 @@ public sealed class GameWindow :
|
|||
_playerModeAutoEntry = new AcDream.App.Input.PlayerModeAutoEntry(
|
||||
new AcDream.App.Input.LivePlayerModeAutoEntryContext(
|
||||
_liveSessionController,
|
||||
_liveEntities,
|
||||
livePresentation.LiveEntities,
|
||||
_localPlayerIdentity,
|
||||
_worldReveal
|
||||
?? throw new InvalidOperationException(
|
||||
|
|
@ -2051,7 +1647,7 @@ public sealed class GameWindow :
|
|||
_localPlayerTeleport = _portalTunnelFallback.Transfer(
|
||||
portalTunnel => new AcDream.App.Streaming.LocalPlayerTeleportController(
|
||||
new AcDream.App.Streaming.LiveLocalPlayerTeleportAuthority(
|
||||
_liveEntities,
|
||||
livePresentation.LiveEntities,
|
||||
_localPlayerIdentity),
|
||||
_gameplayInputFrame,
|
||||
_playerModeController,
|
||||
|
|
@ -2063,7 +1659,7 @@ public sealed class GameWindow :
|
|||
_worldReveal,
|
||||
new AcDream.App.Streaming.LocalPlayerTeleportPlacement(
|
||||
_physicsEngine,
|
||||
_liveEntities,
|
||||
livePresentation.LiveEntities,
|
||||
_localPlayerIdentity,
|
||||
_playerControllerSlot,
|
||||
_playerHostSlot,
|
||||
|
|
@ -2323,7 +1919,7 @@ public sealed class GameWindow :
|
|||
_worldSelectionQuery!));
|
||||
var updateFrameOrchestrator = new AcDream.App.Update.UpdateFrameOrchestrator(
|
||||
new AcDream.App.Update.LiveEntityTeardownFramePhase(
|
||||
_liveEntities),
|
||||
livePresentation.LiveEntities),
|
||||
new AcDream.App.Update.ConsoleUpdateFrameFailureSink(),
|
||||
_updateFrameClock,
|
||||
new AcDream.App.Update.PhysicsScriptClockPublisher(_scriptRunner!),
|
||||
|
|
@ -3039,6 +2635,15 @@ public sealed class GameWindow :
|
|||
]),
|
||||
new ResourceShutdownStage("effect dispatch edges",
|
||||
[
|
||||
new("live-presentation bindings", () =>
|
||||
{
|
||||
LivePresentationRuntimeBindings? bindings =
|
||||
_livePresentationBindings;
|
||||
if (bindings is null)
|
||||
return;
|
||||
bindings.Dispose();
|
||||
_livePresentationBindings = null;
|
||||
}),
|
||||
new("entity-effect advance source", () =>
|
||||
{
|
||||
if (_entityEffects is { } effects)
|
||||
|
|
@ -3232,15 +2837,4 @@ public sealed class GameWindow :
|
|||
_window = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fallback <see cref="AcDream.Core.Physics.IAnimationLoader"/> for the
|
||||
/// <see cref="AcDream.App.Rendering.Wb.EntitySpawnAdapter"/> sequencer
|
||||
/// factory when neither <c>_dats</c> nor the entity's setup is available.
|
||||
/// Returns null for all animation lookups so the sequencer silently has
|
||||
/// no data (same behaviour as a new empty Setup).
|
||||
/// </summary>
|
||||
private sealed class NullAnimLoader : AcDream.Core.Physics.IAnimationLoader
|
||||
{
|
||||
public DatReaderWriter.DBObjs.Animation? LoadAnimation(uint id) => null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue