refactor(app): compose live presentation startup

This commit is contained in:
Erik 2026-07-22 17:55:15 +02:00
parent aa6ffa5176
commit 88f32dc4e2
23 changed files with 1767 additions and 626 deletions

View file

@ -0,0 +1,938 @@
using System.Numerics;
using AcDream.App.Diagnostics;
using AcDream.App.Input;
using AcDream.App.Interaction;
using AcDream.App.Physics;
using AcDream.App.Rendering;
using AcDream.App.Rendering.Selection;
using AcDream.App.Rendering.Sky;
using AcDream.App.Rendering.Vfx;
using AcDream.App.Rendering.Wb;
using AcDream.App.Settings;
using AcDream.App.Streaming;
using AcDream.App.UI;
using AcDream.App.UI.Layout;
using AcDream.App.World;
using AcDream.Core.Audio;
using AcDream.Core.Items;
using AcDream.Core.Lighting;
using AcDream.Core.Physics;
using AcDream.Core.Plugins;
using AcDream.Core.Rendering;
using AcDream.Core.Selection;
using AcDream.Core.Vfx;
using AcDream.Core.World;
using DatReaderWriter;
using DatReaderWriter.DBObjs;
using Silk.NET.OpenGL;
using Silk.NET.Windowing;
namespace AcDream.App.Composition;
internal sealed record LivePresentationDependencies(
RuntimeOptions Options,
GL Gl,
IWindow Window,
object DatLock,
RuntimeSettingsController Settings,
HostQuiescenceGate HostQuiescence,
PhysicsEngine PhysicsEngine,
PhysicsDataCache PhysicsDataCache,
WorldGameState WorldGameState,
WorldEvents WorldEvents,
SelectionState Selection,
ClientObjectTable Objects,
LiveEntityRuntimeSlot RuntimeSlot,
DeferredLiveEntityMotionRuntimeBindings MotionBindings,
DeferredEntityEffectAdvanceSource EffectAdvance,
EntityEffectPoseRegistry EffectPoses,
RemotePhysicsUpdater RemotePhysicsUpdater,
LocalPlayerShadowState LocalPlayerShadow,
LiveEntityAnimationRuntimeView<LiveEntityAnimationState> AnimatedEntities,
AnimationPresentationDiagnostics AnimationDiagnostics,
EntityClassificationCache ClassificationCache,
TranslucencyFadeManager TranslucencyFades,
RetailAlphaQueue RetailAlphaQueue,
CellVisibility CellVisibility,
LiveWorldOriginState WorldOrigin,
LocalPlayerIdentityState PlayerIdentity,
LocalPlayerControllerSlot PlayerController,
PointerPositionState PointerPosition,
PlayerApproachCompletionState PlayerApproachCompletions,
GameRenderResourceLifetime RenderResourceLifetime,
TransferableResourceSlot<PortalTunnelPresentation> PortalTunnelFallback,
AnimationHookRouter HookRouter,
IRenderFrameDiagnosticLog RenderDiagnosticLog,
WorldTimeService WorldTime,
DeferredCanonicalWorldEntityCountSource? DevWorldEntities,
DeferredRenderFrameDiagnosticsSource? DevFrameDiagnostics,
DeferredRenderFrameDiagnosticsSource UiFrameDiagnostics,
Action<string>? Toast);
internal sealed record LivePresentationResult(
DeferredLiveEntityRuntimeComponentLifecycle ComponentLifecycle,
LiveEntityMotionRuntimeController MotionRuntime,
DeferredLiveEntityParentAcceptance ParentAcceptance,
EntitySpawnAdapter EntitySpawnAdapter,
EntityScriptActivator EntityScriptActivator,
RetailStaticAnimatingObjectScheduler StaticAnimationScheduler,
GpuWorldState WorldState,
LiveEntityRuntime LiveEntities,
ProjectileController ProjectileController,
LiveEntityProjectionWithdrawalController ProjectionWithdrawal,
LiveEntityLightController Lights,
LiveEntityAnimationScheduler AnimationScheduler,
LiveEntityAnimationPresenter AnimationPresenter,
EquippedChildRenderController EquippedChildren,
EntityEffectController EntityEffects,
LiveEntityPresentationController Presentation,
RemoteTeleportController RemoteTeleport,
WbDrawDispatcher DrawDispatcher,
RetailSelectionScene SelectionScene,
WorldSelectionQuery SelectionQuery,
SelectionInteractionController SelectionInteractions,
RetainedUiGameplayBinding? RetainedGameplay,
PaperdollViewportRenderer? PaperdollRenderer,
PaperdollFramePresenter? PaperdollPresenter,
WbFrustum EnvCellFrustum,
EnvCellRenderer EnvCellRenderer,
LandblockPresentationPipeline LandblockPipeline,
ClipFrame ClipFrame,
PortalDepthMaskRenderer PortalDepthMask,
SkyRenderer SkyRenderer,
ParticleRenderer ParticleRenderer,
RenderFrameDiagnosticsController FrameDiagnostics,
LivePresentationRuntimeBindings RuntimeBindings,
DeferredLiveEntityLandblockLoadedSink LandblockLoaded);
internal interface IGameWindowLivePresentationPublication
{
void PublishLivePresentation(LivePresentationResult result);
}
internal enum LivePresentationCompositionPoint
{
CanonicalRuntimeCreated,
CanonicalRuntimeBound,
MotionRuntimeBound,
ProjectionVisibilityBound,
CorePresentationCreated,
EffectRoutingBound,
SelectionAndRadarBound,
RetainedGameplayBound,
PaperdollCreated,
EnvironmentCellsCreated,
LandblockPipelineCreated,
PortalResourcesCreated,
SkyAndParticlesCreated,
DiagnosticsBound,
ResultPublished,
}
internal sealed class LivePresentationCompositionPhase
: ILivePresentationCompositionPhase<
HostInputCameraResult,
ContentEffectsAudioResult,
WorldRenderResult,
InteractionRetainedUiResult,
LivePresentationResult>
{
private readonly LivePresentationDependencies _dependencies;
private readonly IGameWindowLivePresentationPublication _publication;
private readonly Action<LivePresentationCompositionPoint>? _faultInjection;
public LivePresentationCompositionPhase(
LivePresentationDependencies dependencies,
IGameWindowLivePresentationPublication publication,
Action<LivePresentationCompositionPoint>? faultInjection = null)
{
_dependencies = dependencies
?? throw new ArgumentNullException(nameof(dependencies));
_publication = publication
?? throw new ArgumentNullException(nameof(publication));
_faultInjection = faultInjection;
}
public LivePresentationResult Compose(
HostInputCameraResult host,
ContentEffectsAudioResult content,
WorldRenderResult world,
InteractionRetainedUiResult interaction)
{
ArgumentNullException.ThrowIfNull(host);
ArgumentNullException.ThrowIfNull(content);
ArgumentNullException.ThrowIfNull(world);
ArgumentNullException.ThrowIfNull(interaction);
return ComposeCore(host, content, world, interaction);
}
private LivePresentationResult ComposeCore(
HostInputCameraResult host,
ContentEffectsAudioResult content,
WorldRenderResult world,
InteractionRetainedUiResult interaction)
{
LivePresentationDependencies d = _dependencies;
WorldRenderFoundation foundation = world.Foundation;
var scope = new CompositionAcquisitionScope();
LivePresentationRuntimeBindings? bindings = null;
bool bindingsOwnedByScope = false;
try
{
var componentLifecycle =
new DeferredLiveEntityRuntimeComponentLifecycle();
var wbSpawnAdapter = new LandblockSpawnAdapter(foundation.MeshAdapter);
AnimationSequencer SequencerFactory(WorldEntity entity)
{
Setup? setup = content.Dats.Get<Setup>(entity.SourceGfxObjOrSetupId);
if (setup is not null)
{
uint motionTableId = (uint)setup.DefaultMotionTable;
if (motionTableId != 0
&& content.Dats.Get<MotionTable>(motionTableId) is { } motionTable)
{
return new AnimationSequencer(
setup,
motionTable,
content.AnimationLoader);
}
return new AnimationSequencer(
setup,
new MotionTable(),
content.AnimationLoader);
}
return new AnimationSequencer(
new Setup(),
new MotionTable(),
NullAnimationLoader.Instance);
}
var entitySpawnAdapter = new EntitySpawnAdapter(
foundation.TextureCache,
SequencerFactory,
foundation.MeshAdapter);
EntityEffectController? entityEffects = null;
LiveEntityRuntime? liveEntities = null;
var staticRootCommitter = new StaticLiveRootCommitter(
d.RuntimeSlot,
d.PhysicsEngine.ShadowObjects,
d.WorldOrigin,
d.EffectPoses);
var staticResidency = new LiveStaticAnimationResidency(d.RuntimeSlot);
var staticAnimationScheduler =
new RetailStaticAnimatingObjectScheduler(
content.AnimationLoader,
content.AnimationHookFrames.Capture,
d.EffectPoses.Publish,
staticResidency.IsResident,
(entity, body) => _ = staticRootCommitter.Commit(entity, body),
staticResidency.ProjectionVersion);
ScriptActivationInfo? ResolveActivation(WorldEntity entity)
{
try
{
Setup? setup = content.Dats.Get<Setup>(
entity.SourceGfxObjOrSetupId);
if (setup is null)
return null;
uint scriptId = setup.DefaultScript.DataId;
if (entity.IndexedPartTransforms.Count == 0)
{
var indexed = IndexedSetupPartPoseBuilder.Build(setup, entity);
entity.SetIndexedPartPoses(indexed.Poses, indexed.Available);
}
bool usesStaticAnimationWorkset = entity.ServerGuid == 0
|| (liveEntities?.TryGetRecord(
entity.ServerGuid,
out LiveEntityRecord liveRecord) == true
&& (liveRecord.FinalPhysicsState
& PhysicsStateFlags.Static) != 0);
return new ScriptActivationInfo(
scriptId,
entity.IndexedPartTransforms,
EntityEffectProfile.CreateDatStatic(setup),
entity.IndexedPartAvailable,
setup,
(uint)setup.DefaultAnimation,
usesStaticAnimationWorkset);
}
catch
{
return null;
}
}
var entityScriptActivator = new EntityScriptActivator(
content.ScriptRunner,
content.ParticleSink,
d.EffectPoses,
ResolveActivation,
(ownerId, entity, profile) =>
entityEffects?.OnDatStaticEntityReady(ownerId, entity, profile),
ownerId =>
{
entityEffects?.OnDatStaticEntityRemoved(ownerId);
content.LightingSink.UnregisterOwner(ownerId);
d.TranslucencyFades.ClearEntity(ownerId);
},
(entity, info) => staticAnimationScheduler.Register(entity, info),
staticAnimationScheduler.Unregister,
(entity, info) => staticAnimationScheduler.Rebind(entity, info));
var worldState = new GpuWorldState(
wbSpawnAdapter,
d.ClassificationCache.InvalidateLandblock,
entityScriptActivator);
bindings = new LivePresentationRuntimeBindings();
if (d.DevWorldEntities is { } devWorldEntities)
{
bindings.Adopt(
"developer world-entity source",
devWorldEntities.BindOwned(worldState));
}
liveEntities = new LiveEntityRuntime(
worldState,
new CompositeLiveEntityResourceLifecycle(
new(
entity => _ = entitySpawnAdapter.OnCreate(entity),
entity => _ = entitySpawnAdapter.OnRemove(entity)),
new(
entityScriptActivator.OnCreate,
entityScriptActivator.OnRemove)),
componentLifecycle);
var liveRuntimeLease = scope.Own(
"canonical live-entity runtime",
liveEntities,
static runtime => runtime.Clear());
Fault(LivePresentationCompositionPoint.CanonicalRuntimeCreated);
bindings.Adopt(
"canonical live-runtime slot",
d.RuntimeSlot.BindOwned(liveEntities));
Fault(LivePresentationCompositionPoint.CanonicalRuntimeBound);
var selectionInteractionSource =
new DeferredSelectionInteractionSource();
var motionRuntime = new LiveEntityMotionRuntimeController(
liveEntities,
d.PhysicsDataCache,
() => selectionInteractionSource.Current,
d.Selection,
d.WorldOrigin);
bindings.Adopt(
"live motion runtime",
d.MotionBindings.BindOwned(motionRuntime));
Fault(LivePresentationCompositionPoint.MotionRuntimeBound);
Action<LiveEntityRecord, bool> wbVisibility = (record, visible) =>
{
if (record.WorldEntity is { } entity)
entitySpawnAdapter.SetPresentationResident(entity, visible);
};
bindings.BindProjectionVisibility(
liveEntities,
wbVisibility,
"WB projection visibility");
Action<LiveEntityRecord, bool> particleVisibility = (record, visible) =>
{
if (record.WorldEntity is { } entity)
content.ParticleSink.SetEntityPresentationVisible(entity.Id, visible);
};
bindings.BindProjectionVisibility(
liveEntities,
particleVisibility,
"particle projection visibility");
Fault(LivePresentationCompositionPoint.ProjectionVisibilityBound);
var projectileController = new ProjectileController(
liveEntities,
d.PhysicsEngine,
new DatProjectileSetupResolver(content.Dats, d.DatLock),
new EntityRootPosePublisher(d.EffectPoses),
d.WorldOrigin)
{
DiagnosticSink = message =>
Console.Error.WriteLine($"projectile: {message}"),
};
var projectionWithdrawal =
new LiveEntityProjectionWithdrawalController(
liveEntities,
projectileController,
d.WorldGameState,
d.WorldEvents,
d.PhysicsEngine.ShadowObjects,
d.EffectPoses,
d.LocalPlayerShadow);
var lightsLease = scope.Acquire(
"live-entity lights",
() => new LiveEntityLightController(
liveEntities,
d.EffectPoses,
content.LightingSink,
setupId => content.Dats.Get<Setup>(setupId)),
static value => value.Dispose());
var ordinaryPhysicsUpdater = new LiveEntityOrdinaryPhysicsUpdater(
d.PhysicsEngine,
d.MotionBindings.GetSetupCylinder);
var animationScheduler = new LiveEntityAnimationScheduler(
liveEntities,
d.PlayerIdentity,
d.RemotePhysicsUpdater,
ordinaryPhysicsUpdater,
projectileController,
new EntityRootPosePublisher(d.EffectPoses),
new AnimationHookCaptureSink(content.AnimationHookFrames));
var animationPresenter = new LiveEntityAnimationPresenter(
liveEntities,
staticAnimationScheduler,
d.EffectPoses,
new LiveAnimationPresentationContext(
liveEntities,
d.PlayerIdentity,
d.PlayerController),
d.AnimationDiagnostics,
d.Options.HidePartIndex);
var parentAcceptance = new DeferredLiveEntityParentAcceptance();
var equippedLease = scope.Acquire(
"equipped-child renderer",
() => new EquippedChildRenderController(
content.Dats,
d.DatLock,
d.Objects,
liveEntities,
d.EffectPoses,
parentAcceptance.TryAccept,
(childRecord, positionVersion, projectionVersion) =>
projectionWithdrawal.WithdrawExact(
childRecord,
positionVersion,
projectionVersion,
d.PlayerIdentity.ServerGuid)),
static value => value.Dispose());
Fault(LivePresentationCompositionPoint.CorePresentationCreated);
var tableResolver = new PhysicsScriptTableResolver(
id => content.Dats.Get<PhysicsScriptTable>(id));
entityEffects = new EntityEffectController(
liveEntities,
content.ScriptRunner,
tableResolver,
d.EffectPoses,
(parentLocalId, partIndex) =>
equippedLease.Resource.FindChildLocalIdAtPart(
parentLocalId,
partIndex),
equippedLease.Resource.FindParentLocalId,
ownerId => content.Audio?.EntitySoundTables.Remove(ownerId),
(ownerId, soundTableDid) =>
{
content.Audio?.EntitySoundTables.Remove(ownerId);
if (soundTableDid is { } did)
content.Audio?.EntitySoundTables.Set(ownerId, did);
});
bindings.Adopt(
"entity-effect advance",
d.EffectAdvance.BindOwned(entityEffects));
entityEffects.DiagnosticSink = message =>
Console.Error.WriteLine($"vfx: {message}");
var partArrayLifecycle = new LiveEntityPartArrayLifecycle(
d.AnimatedEntities);
var presentationLease = scope.Acquire(
"live-entity presentation",
() => new LiveEntityPresentationController(
liveEntities,
d.PhysicsEngine.ShadowObjects,
entityEffects.PlayTypedFromHiddenTransition,
equippedLease.Resource.SetDirectChildrenNoDraw,
d.MotionBindings.ClearTargetForHiddenEntity,
d.WorldOrigin.GetCenter,
partArrayLifecycle.HandleEnterWorld),
static value => value.Dispose());
var remoteShadowPlacement = new RemoteShadowPlacementSynchronizer(
d.RemotePhysicsUpdater,
d.WorldOrigin);
var remoteTeleportPresentation =
new RemoteTeleportPlacementPresentation(presentationLease.Resource);
var remoteTeleportLease = scope.Acquire(
"remote teleport",
() => new RemoteTeleportController(
d.PhysicsEngine,
liveEntities,
d.MotionBindings.GetSetupCylinder,
d.WorldOrigin.CellLocalForSeed,
remoteShadowPlacement.Sync,
remoteTeleportPresentation.Complete,
remoteTeleportPresentation.Begin),
static value => value.Dispose());
bindings.BindProjectionPoseReady(
equippedLease.Resource,
lightsLease.Resource.OnAttachedPoseReady);
bindings.Adopt(
"entity-effect animation hooks",
content.HookRegistrations.RegisterOwned(entityEffects));
Fault(LivePresentationCompositionPoint.EffectRoutingBound);
return CompletePresentation(
host,
content,
world,
interaction,
componentLifecycle,
motionRuntime,
parentAcceptance,
entitySpawnAdapter,
entityScriptActivator,
staticAnimationScheduler,
worldState,
liveEntities,
projectileController,
projectionWithdrawal,
lightsLease,
animationScheduler,
animationPresenter,
equippedLease,
entityEffects,
presentationLease,
remoteTeleportLease,
selectionInteractionSource,
bindings,
scope,
ref bindingsOwnedByScope,
liveRuntimeLease);
}
catch (Exception failure)
{
if (bindings is not null && !bindingsOwnedByScope)
{
scope.Own(
"live-presentation runtime bindings",
bindings,
static value => value.Dispose());
bindingsOwnedByScope = true;
}
scope.RollbackAndThrow(failure);
throw new System.Diagnostics.UnreachableException();
}
}
private LivePresentationResult CompletePresentation(
HostInputCameraResult host,
ContentEffectsAudioResult content,
WorldRenderResult world,
InteractionRetainedUiResult interaction,
DeferredLiveEntityRuntimeComponentLifecycle componentLifecycle,
LiveEntityMotionRuntimeController motionRuntime,
DeferredLiveEntityParentAcceptance parentAcceptance,
EntitySpawnAdapter entitySpawnAdapter,
EntityScriptActivator entityScriptActivator,
RetailStaticAnimatingObjectScheduler staticAnimationScheduler,
GpuWorldState worldState,
LiveEntityRuntime liveEntities,
ProjectileController projectileController,
LiveEntityProjectionWithdrawalController projectionWithdrawal,
CompositionAcquisitionScope.CompositionAcquisitionLease<LiveEntityLightController> lightsLease,
LiveEntityAnimationScheduler animationScheduler,
LiveEntityAnimationPresenter animationPresenter,
CompositionAcquisitionScope.CompositionAcquisitionLease<EquippedChildRenderController> equippedLease,
EntityEffectController entityEffects,
CompositionAcquisitionScope.CompositionAcquisitionLease<LiveEntityPresentationController> presentationLease,
CompositionAcquisitionScope.CompositionAcquisitionLease<RemoteTeleportController> remoteTeleportLease,
DeferredSelectionInteractionSource selectionInteractionSource,
LivePresentationRuntimeBindings bindings,
CompositionAcquisitionScope scope,
ref bool bindingsOwnedByScope,
CompositionAcquisitionScope.CompositionAcquisitionLease<LiveEntityRuntime> liveRuntimeLease)
{
LivePresentationDependencies d = _dependencies;
WorldRenderFoundation foundation = world.Foundation;
var selectionScene = new RetailSelectionScene(
new RetailSelectionGeometryCache(content.Dats, d.DatLock));
var dispatcherLease = scope.Acquire(
"WB draw dispatcher",
() => new WbDrawDispatcher(
d.Gl,
foundation.MeshShader,
foundation.TextureCache,
foundation.MeshAdapter,
entitySpawnAdapter,
foundation.Bindless,
d.ClassificationCache,
d.TranslucencyFades,
selectionScene,
d.RetailAlphaQueue),
static value => value.Dispose());
var selectionQuery = new WorldSelectionQuery(
liveEntities,
d.Objects,
selectionScene,
() => d.PlayerIdentity.ServerGuid,
interaction.LateBindings.SelectionCamera.Snapshot,
() => new Vector2(
d.PointerPosition.X,
d.PointerPosition.Y),
() => d.PlayerController.Controller is { } player
? new PlayerInteractionPose(player.CellId, player.Position)
: null,
d.MotionBindings.GetSetupCylinder,
setupId =>
{
lock (d.DatLock)
{
if (!content.Dats.TryGet<Setup>(setupId, out Setup? setup)
|| setup.SelectionSphere is not { } sphere)
{
return null;
}
return (sphere.Origin, sphere.Radius);
}
});
var radarSnapshotProvider = new RadarSnapshotProvider(
d.Objects,
() => liveEntities.WorldEntities,
() => liveEntities.Snapshots,
playerGuid: () => d.PlayerIdentity.ServerGuid,
playerYawRadians: () => d.PlayerController.Controller?.Yaw ?? 0f,
playerCellId: () => d.PlayerController.Controller?.CellId ?? 0u,
selectedGuid: () => d.Selection.SelectedObjectId,
coordinatesOnRadar: () => d.Settings.Gameplay.CoordinatesOnRadar,
uiLocked: () => d.Settings.Gameplay.LockUI,
playerEntities: () => liveEntities.MaterializedWorldEntities,
spatialQuery: () => worldState);
bindings.Adopt(
"radar snapshot",
interaction.LateBindings.Radar.Bind(radarSnapshotProvider));
var selectionInteractions = new SelectionInteractionController(
d.Selection,
selectionQuery,
interaction.ItemInteraction,
new WorldSessionSelectionInteractionTransport(
() => interaction.LateBindings.Session.CurrentSession),
new PlayerInteractionMovementSink(
() => d.PlayerController.Controller,
d.PlayerApproachCompletions),
d.Toast,
d.PlayerApproachCompletions);
selectionInteractionSource.Bind(selectionInteractions);
bindings.Adopt(
"world selection",
interaction.LateBindings.Selection.Bind(
selectionQuery,
selectionInteractions));
Fault(LivePresentationCompositionPoint.SelectionAndRadarBound);
CompositionAcquisitionScope.CompositionAcquisitionLease<
RetainedUiGameplayBinding>? retainedGameplayLease = null;
if (interaction.RetainedUi is { } retainedUi)
{
retainedGameplayLease = scope.Acquire(
"retained gameplay binding",
() => RetainedUiGameplayBinding.Create(
retainedUi.Host.Root,
(item, x, y) =>
selectionInteractions.PlaceDraggedItem(item, x, y),
d.HostQuiescence),
static value => value.Dispose());
retainedGameplayLease.Resource.Attach();
}
Fault(LivePresentationCompositionPoint.RetainedGameplayBound);
dispatcherLease.Resource.AlphaToCoverage =
d.Settings.ResolvedQuality.AlphaToCoverage;
CompositionAcquisitionScope.CompositionAcquisitionLease<
PaperdollViewportRenderer>? paperdollLease = null;
PaperdollFramePresenter? paperdollPresenter = null;
if (interaction.RetainedUi?.Runtime.PaperdollViewportWidget is { } viewport
&& interaction.RetainedUi.Runtime.InventoryFrame is { } inventoryFrame)
{
paperdollLease = scope.Acquire(
"paperdoll viewport",
() => new PaperdollViewportRenderer(
d.Gl,
dispatcherLease.Resource,
foundation.SceneLighting,
foundation.TextureCache),
static value => value.Dispose());
IUiViewportRenderer? previousRenderer = viewport.Renderer;
viewport.Renderer = paperdollLease.Resource;
bindings.AdoptRelease(
"paperdoll viewport target",
() =>
{
if (ReferenceEquals(viewport.Renderer, paperdollLease.Resource))
viewport.Renderer = previousRenderer;
});
paperdollPresenter = new PaperdollFramePresenter(
paperdollLease.Resource,
new RetailPaperdollFrameView(
viewport,
new PaperdollInventoryVisibility(inventoryFrame)),
new RetailPaperdollDollFactory(
new LivePaperdollEntityLookup(liveEntities),
d.PlayerIdentity,
new RetailPaperdollPoseApplicator(
content.Dats,
content.AnimationLoader,
d.DatLock)));
}
Fault(LivePresentationCompositionPoint.PaperdollCreated);
var envCellFrustum = new WbFrustum();
var envCellLease = scope.Acquire(
"environment-cell renderer",
() => new EnvCellRenderer(
d.Gl,
foundation.MeshAdapter.MeshManager!,
envCellFrustum),
static value => value.Dispose());
envCellLease.Resource.Initialize(foundation.MeshShader);
Fault(LivePresentationCompositionPoint.EnvironmentCellsCreated);
var landblockRenderPublisher = new LandblockRenderPublisher(
(landblockId, meshData, origin) =>
foundation.Terrain.AddLandblockWithMesh(
landblockId,
meshData,
origin),
foundation.Terrain.RemoveLandblock,
d.CellVisibility,
worldState,
envCellLease.Resource.CommitLandblock,
build => EnvCellMeshPreparationScheduler.Schedule(
build,
foundation.MeshAdapter.MeshManager!),
envCellLease.Resource.RemoveLandblock);
var landblockPhysicsPublisher = new LandblockPhysicsPublisher(
d.PhysicsEngine,
world.TerrainBuild.HeightTable);
var landblockStaticPublisher =
new LandblockStaticPresentationPublisher(
content.LightingSink,
d.TranslucencyFades,
d.WorldGameState,
d.WorldEvents);
var landblockRetirementOwner =
new LandblockPresentationRetirementOwner(
landblockRenderPublisher,
landblockPhysicsPublisher,
landblockStaticPublisher,
content.LightingSink,
d.TranslucencyFades);
var landblockLoaded = new DeferredLiveEntityLandblockLoadedSink();
var landblockPipeline = new LandblockPresentationPipeline(
landblockRenderPublisher,
landblockPhysicsPublisher,
landblockStaticPublisher,
worldState,
landblockRetirementOwner,
landblockLoaded.OnLandblockLoaded,
landblockRenderPublisher.PrepareAfterRenderPins);
Fault(LivePresentationCompositionPoint.LandblockPipelineCreated);
var clipFrameLease = scope.Acquire(
"portal clip frame",
ClipFrame.NoClip,
static value => value.Dispose());
var portalDepthLease = scope.Acquire(
"portal depth mask",
() => new PortalDepthMaskRenderer(d.Gl),
static value => value.Dispose());
PortalTunnelPresentation portalTunnel;
try
{
portalTunnel = d.PortalTunnelFallback.AcquirePrepared(
() => PortalTunnelPresentation.CreateRequired(
d.Gl,
content.Dats,
content.AnimationLoader,
d.HookRouter,
dispatcherLease.Resource,
foundation.SceneLighting,
foundation.MeshAdapter),
static tunnel => tunnel.PrepareResources());
}
catch (Exception acquisitionFailure)
{
try
{
d.PortalTunnelFallback.ReleaseFallback();
}
catch (Exception cleanupFailure)
{
throw new AggregateException(
"Portal-tunnel construction and fallback rollback both failed.",
acquisitionFailure,
cleanupFailure);
}
throw;
}
var portalTunnelLease = scope.Own(
"portal tunnel fallback",
portalTunnel,
_ => d.PortalTunnelFallback.ReleaseFallback());
Fault(LivePresentationCompositionPoint.PortalResourcesCreated);
AcDream.App.Rendering.Shader skyShader =
d.RenderResourceLifetime.AcquireSkyShader(
() => new AcDream.App.Rendering.Shader(
d.Gl,
Path.Combine(foundation.ShadersDirectory, "sky.vert"),
Path.Combine(foundation.ShadersDirectory, "sky.frag")));
var skyShaderLease = scope.Own(
"sky shader lifetime",
skyShader,
_ => d.RenderResourceLifetime.ReleaseSkyShader());
var skyLease = scope.Acquire(
"sky renderer",
() => new SkyRenderer(
d.Gl,
content.Dats,
skyShader,
foundation.TextureCache,
foundation.Samplers),
static value => value.Dispose());
var particleLease = scope.Acquire(
"particle renderer",
() => new ParticleRenderer(
d.Gl,
foundation.ShadersDirectory,
content.ParticleSystem,
foundation.TextureCache,
content.Dats,
foundation.MeshAdapter,
d.RetailAlphaQueue),
static value => value.Dispose());
Fault(LivePresentationCompositionPoint.SkyAndParticlesCreated);
IRenderFrameResourceDiagnosticsSource? resourceDiagnostics =
d.Options.UiProbeDump
? new RuntimeRenderFrameResourceDiagnosticsSource(
content.ParticleSystem,
content.ParticleSink,
dispatcherLease.Resource,
envCellLease.Resource,
particleLease.Resource,
interaction.RetainedUi?.Host.TextRenderer,
portalDepthLease.Resource,
clipFrameLease.Resource,
foundation.Terrain,
foundation.SceneLighting,
foundation.MeshAdapter,
foundation.TextureCache)
: null;
var frameDiagnostics = new RenderFrameDiagnosticsController(
new RuntimeRenderFrameTitleFactsSource(
worldState,
d.AnimatedEntities,
d.WorldTime),
new SilkRenderFrameTitleSink(d.Window),
d.RenderDiagnosticLog,
d.Options.UiProbeDump,
resourceDiagnostics);
if (d.DevFrameDiagnostics is { } devFrameDiagnostics)
{
bindings.Adopt(
"developer frame diagnostics",
devFrameDiagnostics.BindOwned(frameDiagnostics));
}
bindings.Adopt(
"retained-UI frame diagnostics",
d.UiFrameDiagnostics.BindOwned(frameDiagnostics));
Fault(LivePresentationCompositionPoint.DiagnosticsBound);
var bindingsLease = scope.Own(
"live-presentation runtime bindings",
bindings,
static value => value.Dispose());
bindingsOwnedByScope = true;
var result = new LivePresentationResult(
componentLifecycle,
motionRuntime,
parentAcceptance,
entitySpawnAdapter,
entityScriptActivator,
staticAnimationScheduler,
worldState,
liveEntities,
projectileController,
projectionWithdrawal,
lightsLease.Resource,
animationScheduler,
animationPresenter,
equippedLease.Resource,
entityEffects,
presentationLease.Resource,
remoteTeleportLease.Resource,
dispatcherLease.Resource,
selectionScene,
selectionQuery,
selectionInteractions,
retainedGameplayLease?.Resource,
paperdollLease?.Resource,
paperdollPresenter,
envCellFrustum,
envCellLease.Resource,
landblockPipeline,
clipFrameLease.Resource,
portalDepthLease.Resource,
skyLease.Resource,
particleLease.Resource,
frameDiagnostics,
bindings,
landblockLoaded);
_publication.PublishLivePresentation(result);
liveRuntimeLease.Transfer();
lightsLease.Transfer();
equippedLease.Transfer();
presentationLease.Transfer();
remoteTeleportLease.Transfer();
dispatcherLease.Transfer();
retainedGameplayLease?.Transfer();
paperdollLease?.Transfer();
envCellLease.Transfer();
clipFrameLease.Transfer();
portalDepthLease.Transfer();
portalTunnelLease.Transfer();
skyLease.Transfer();
skyShaderLease.Transfer();
particleLease.Transfer();
bindingsLease.Transfer();
Fault(LivePresentationCompositionPoint.ResultPublished);
scope.Complete();
return result;
}
private void Fault(LivePresentationCompositionPoint point) =>
_faultInjection?.Invoke(point);
private sealed class NullAnimationLoader : IAnimationLoader
{
public static NullAnimationLoader Instance { get; } = new();
public Animation? LoadAnimation(uint id) => null;
}
private sealed class DeferredSelectionInteractionSource
{
public SelectionInteractionController? Current { get; private set; }
public void Bind(SelectionInteractionController value)
{
ArgumentNullException.ThrowIfNull(value);
if (Current is not null)
{
throw new InvalidOperationException(
"Live motion selection interactions are already bound.");
}
Current = value;
}
}
}