refactor(app): compose live presentation startup
This commit is contained in:
parent
aa6ffa5176
commit
88f32dc4e2
23 changed files with 1767 additions and 626 deletions
938
src/AcDream.App/Composition/LivePresentationComposition.cs
Normal file
938
src/AcDream.App/Composition/LivePresentationComposition.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue