949 lines
39 KiB
C#
949 lines
39 KiB
C#
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<
|
|
GameWindowPlatformResult<GL, Silk.NET.Input.IInputContext>,
|
|
HostInputCameraResult,
|
|
ContentEffectsAudioResult,
|
|
SettingsDevToolsResult,
|
|
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(
|
|
GameWindowPlatformResult<GL, Silk.NET.Input.IInputContext> platform,
|
|
HostInputCameraResult host,
|
|
ContentEffectsAudioResult content,
|
|
SettingsDevToolsResult settings,
|
|
WorldRenderResult world,
|
|
InteractionRetainedUiResult interaction)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(platform);
|
|
ArgumentNullException.ThrowIfNull(host);
|
|
ArgumentNullException.ThrowIfNull(content);
|
|
ArgumentNullException.ThrowIfNull(settings);
|
|
ArgumentNullException.ThrowIfNull(world);
|
|
ArgumentNullException.ThrowIfNull(interaction);
|
|
if (!ReferenceEquals(_dependencies.Gl, platform.Graphics))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Live-presentation dependencies do not match the ordered platform result.");
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
}
|