Move the coupled Spellbook and LocalPlayerState into one Runtime-owned character graph, route content, live-session, retained UI, reset, and shutdown through that exact owner, and delete the duplicate desired-component snapshot from inventory state. Preserve synchronous retail update and reset ordering while adding independent-instance and retryable-failure coverage. Co-authored-by: Codex <codex@openai.com>
999 lines
38 KiB
C#
999 lines
38 KiB
C#
using AcDream.App.Combat;
|
|
using AcDream.App.Input;
|
|
using AcDream.App.Interaction;
|
|
using AcDream.App.Diagnostics;
|
|
using AcDream.App.Net;
|
|
using AcDream.App.Physics;
|
|
using AcDream.App.Rendering;
|
|
using AcDream.App.Rendering.Scene;
|
|
using AcDream.App.Rendering.Vfx;
|
|
using AcDream.App.Rendering.Wb;
|
|
using AcDream.App.Runtime;
|
|
using AcDream.App.Settings;
|
|
using AcDream.App.Streaming;
|
|
using AcDream.App.Update;
|
|
using AcDream.App.World;
|
|
using AcDream.Content;
|
|
using AcDream.Core.Combat;
|
|
using AcDream.Core.Items;
|
|
using AcDream.Core.Chat;
|
|
using AcDream.Core.Physics;
|
|
using AcDream.Core.Physics.Motion;
|
|
using AcDream.Core.Plugins;
|
|
using AcDream.Core.Rendering;
|
|
using AcDream.Core.Selection;
|
|
using AcDream.Core.Player;
|
|
using AcDream.Core.Social;
|
|
using AcDream.Core.Spells;
|
|
using AcDream.Core.World;
|
|
using AcDream.Runtime.Entities;
|
|
using AcDream.Runtime.Gameplay;
|
|
using AcDream.Runtime.Session;
|
|
using Silk.NET.Windowing;
|
|
|
|
namespace AcDream.App.Composition;
|
|
|
|
internal sealed record SessionPlayerDependencies(
|
|
RuntimeOptions Options,
|
|
IWindow Window,
|
|
object DatLock,
|
|
RuntimeSettingsController Settings,
|
|
SettingsDevToolsResult SettingsDevTools,
|
|
WorldEnvironmentController WorldEnvironment,
|
|
WorldSceneDebugState WorldSceneDebugState,
|
|
RuntimeDiagnosticCommandSlot RuntimeDiagnosticCommands,
|
|
LiveCombatModeCommandSlot CombatModeCommands,
|
|
HostQuiescenceGate HostQuiescence,
|
|
PhysicsEngine PhysicsEngine,
|
|
PhysicsDataCache PhysicsDataCache,
|
|
WorldGameState WorldGameState,
|
|
WorldEvents WorldEvents,
|
|
SelectionState Selection,
|
|
RuntimeEntityObjectLifetime EntityObjects,
|
|
EntityClassificationCache ClassificationCache,
|
|
LiveEntityRuntimeSlot RuntimeSlot,
|
|
LiveEntityAnimationRuntimeView<LiveEntityAnimationState> AnimatedEntities,
|
|
RemoteMovementObservationTracker RemoteMovementObservations,
|
|
RemotePhysicsUpdater RemotePhysicsUpdater,
|
|
RemoteInboundMotionDispatcher RemoteInboundMotion,
|
|
RetailInboundEventDispatcher InboundEntityEvents,
|
|
DeferredLiveEntityMotionRuntimeBindings MotionBindings,
|
|
LocalPlayerIdentityState PlayerIdentity,
|
|
LocalPlayerControllerSlot PlayerController,
|
|
LocalPlayerPhysicsHostSlot PlayerHost,
|
|
LocalPlayerModeState PlayerMode,
|
|
ChaseCameraInputState ChaseCameraInput,
|
|
LocalPlayerOutboundController PlayerOutbound,
|
|
DeferredLocalPlayerTeleportNetworkSink TeleportSink,
|
|
LiveWorldOriginState WorldOrigin,
|
|
WorldRenderRangeState RenderRange,
|
|
LocalPlayerShadowState PlayerShadow,
|
|
LocalPlayerSkillState PlayerSkills,
|
|
ViewportAspectState ViewportAspect,
|
|
PlayerApproachCompletionState PlayerApproachCompletions,
|
|
PlayerCharacterOptionsState CharacterOptions,
|
|
RuntimeInventoryState Inventory,
|
|
PointerPositionState PointerPosition,
|
|
DispatcherMovementInputSource MovementInput,
|
|
IInputCaptureSource InputCapture,
|
|
AcDream.App.Rendering.Vfx.ParticleVisibilityController ParticleVisibility,
|
|
TranslucencyFadeManager TranslucencyFades,
|
|
EntityEffectPoseRegistry EffectPoses,
|
|
UpdateFrameClock UpdateClock,
|
|
MovementTruthDiagnosticController MovementDiagnostics,
|
|
CombatAttackOperationsSlot CombatAttackOperations,
|
|
CombatState Combat,
|
|
CombatFeedbackSlot CombatFeedback,
|
|
RuntimeCommunicationState Communication,
|
|
RuntimeCharacterState Character,
|
|
TransferableResourceSlot<PortalTunnelPresentation> PortalTunnelFallback,
|
|
Action<string> Log);
|
|
|
|
internal sealed record SessionPlayerResult(
|
|
LandblockStreamer Streamer,
|
|
StreamingController Streaming,
|
|
StreamingOriginRecenterCoordinator StreamingOriginRecenter,
|
|
WorldRevealCoordinator WorldReveal,
|
|
DatSpawnClaimHydrationClassifier SpawnClaimHydration,
|
|
LiveSessionController LiveSession,
|
|
LiveEntityHydrationController Hydration,
|
|
LiveEntityNetworkUpdateController NetworkUpdates,
|
|
LiveEntityLivenessController Liveness,
|
|
LiveEntitySessionController SessionEvents,
|
|
GameplayInputFrameController GameplayInput,
|
|
StreamingFrameController StreamingFrame,
|
|
LocalPlayerAnimationController LocalPlayerAnimation,
|
|
LocalPlayerShadowSynchronizer LocalPlayerShadow,
|
|
LiveLocalPlayerFrameRuntime LocalPlayerFrameRuntime,
|
|
RetailLocalPlayerFrameController LocalPlayerFrame,
|
|
LiveSpatialPresentationReconciler LiveSpatialReconciler,
|
|
LiveObjectFrameController LiveObjectFrame,
|
|
PlayerModeController PlayerMode,
|
|
PlayerModeAutoEntry PlayerModeAutoEntry,
|
|
LocalPlayerTeleportController LocalTeleport,
|
|
LiveSessionHost SessionHost,
|
|
CurrentGameRuntimeAdapter GameRuntime,
|
|
GameplayInputActionRouter? GameplayActions,
|
|
SessionPlayerRuntimeBindings RuntimeBindings);
|
|
|
|
internal interface IGameWindowSessionPlayerPublication
|
|
{
|
|
void PublishSessionPlayer(SessionPlayerResult result);
|
|
}
|
|
|
|
internal enum SessionPlayerCompositionPoint
|
|
{
|
|
StreamingRadiiResolved,
|
|
StreamerCreated,
|
|
StreamerStarted,
|
|
StreamingCreated,
|
|
RuntimeSettingsBound,
|
|
WorldRevealCreated,
|
|
LiveSessionCreated,
|
|
HydrationCreated,
|
|
LiveEntityGraphBound,
|
|
CombatOperationsBound,
|
|
GameplayInputBound,
|
|
UpdateLeavesCreated,
|
|
PlayerModeBound,
|
|
PortalTransferred,
|
|
TeleportBound,
|
|
SessionHostCreated,
|
|
CommandsBound,
|
|
GameplayActionsAttached,
|
|
ResultPublished,
|
|
}
|
|
|
|
internal sealed class SessionPlayerCompositionPhase
|
|
: ISessionPlayerCompositionPhase<
|
|
HostInputCameraResult,
|
|
ContentEffectsAudioResult,
|
|
SettingsDevToolsResult,
|
|
WorldRenderResult,
|
|
InteractionRetainedUiResult,
|
|
LivePresentationResult,
|
|
SessionPlayerResult>
|
|
{
|
|
private readonly SessionPlayerDependencies _dependencies;
|
|
private readonly IGameWindowSessionPlayerPublication _publication;
|
|
private readonly Action<SessionPlayerCompositionPoint>? _faultInjection;
|
|
|
|
public SessionPlayerCompositionPhase(
|
|
SessionPlayerDependencies dependencies,
|
|
IGameWindowSessionPlayerPublication publication,
|
|
Action<SessionPlayerCompositionPoint>? faultInjection = null)
|
|
{
|
|
_dependencies = dependencies
|
|
?? throw new ArgumentNullException(nameof(dependencies));
|
|
_publication = publication
|
|
?? throw new ArgumentNullException(nameof(publication));
|
|
_faultInjection = faultInjection;
|
|
}
|
|
|
|
public SessionPlayerResult Compose(
|
|
HostInputCameraResult host,
|
|
ContentEffectsAudioResult content,
|
|
SettingsDevToolsResult settings,
|
|
WorldRenderResult world,
|
|
InteractionRetainedUiResult interaction,
|
|
LivePresentationResult live)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(host);
|
|
ArgumentNullException.ThrowIfNull(content);
|
|
ArgumentNullException.ThrowIfNull(settings);
|
|
ArgumentNullException.ThrowIfNull(world);
|
|
ArgumentNullException.ThrowIfNull(interaction);
|
|
ArgumentNullException.ThrowIfNull(live);
|
|
if (!ReferenceEquals(_dependencies.SettingsDevTools, settings))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Session/player dependencies do not match the ordered settings result.");
|
|
}
|
|
|
|
var scope = new CompositionAcquisitionScope();
|
|
SessionPlayerRuntimeBindings? bindings = null;
|
|
bool bindingsOwnedByScope = false;
|
|
try
|
|
{
|
|
SessionPlayerResult result = ComposeCore(
|
|
host,
|
|
content,
|
|
world,
|
|
interaction,
|
|
live,
|
|
scope,
|
|
ref bindings,
|
|
ref bindingsOwnedByScope);
|
|
scope.Complete();
|
|
return result;
|
|
}
|
|
catch (Exception failure)
|
|
{
|
|
if (bindings is not null && !bindingsOwnedByScope)
|
|
{
|
|
scope.Own(
|
|
"session/player runtime bindings",
|
|
bindings,
|
|
static value => value.Dispose());
|
|
bindingsOwnedByScope = true;
|
|
}
|
|
|
|
scope.RollbackAndThrow(failure);
|
|
throw new System.Diagnostics.UnreachableException();
|
|
}
|
|
}
|
|
|
|
private SessionPlayerResult ComposeCore(
|
|
HostInputCameraResult host,
|
|
ContentEffectsAudioResult content,
|
|
WorldRenderResult world,
|
|
InteractionRetainedUiResult interaction,
|
|
LivePresentationResult live,
|
|
CompositionAcquisitionScope scope,
|
|
ref SessionPlayerRuntimeBindings? bindings,
|
|
ref bool bindingsOwnedByScope)
|
|
{
|
|
SessionPlayerDependencies d = _dependencies;
|
|
WorldRenderFoundation foundation = world.Foundation;
|
|
int nearRadius = d.Settings.ResolvedQuality.NearRadius;
|
|
int farRadius = d.Settings.ResolvedQuality.FarRadius;
|
|
if (d.Options.LegacyStreamRadius is { } legacyRadius)
|
|
{
|
|
nearRadius = legacyRadius;
|
|
farRadius = Math.Max(legacyRadius, farRadius);
|
|
}
|
|
d.RenderRange.NearRadius = nearRadius;
|
|
d.RenderRange.FarRadius = farRadius;
|
|
d.Log(
|
|
$"streaming: nearRadius={nearRadius} " +
|
|
$"(window={2 * nearRadius + 1}x{2 * nearRadius + 1}) " +
|
|
$"farRadius={farRadius} " +
|
|
$"(window={2 * farRadius + 1}x{2 * farRadius + 1})");
|
|
Fault(SessionPlayerCompositionPoint.StreamingRadiiResolved);
|
|
|
|
IPreparedCollisionSource preparedCollisions =
|
|
content.PreparedAssets as IPreparedCollisionSource ??
|
|
throw new NotSupportedException(
|
|
"Production prepared assets must expose the matching " +
|
|
"prepared-collision catalog.");
|
|
var landblockBuildFactory = new LandblockBuildFactory(
|
|
content.Dats,
|
|
preparedCollisions,
|
|
d.DatLock,
|
|
world.TerrainBuild.HeightTable,
|
|
d.Options.DumpSceneryZ);
|
|
var streamerLease = scope.Acquire(
|
|
"landblock streamer",
|
|
() => LandblockStreamer.CreateForRequests(
|
|
landblockBuildFactory.Build,
|
|
(id, landblock) =>
|
|
{
|
|
if (landblock is null)
|
|
return null;
|
|
uint x = (id >> 24) & 0xFFu;
|
|
uint y = (id >> 16) & 0xFFu;
|
|
return AcDream.Core.Terrain.LandblockMesh.Build(
|
|
landblock.Heightmap,
|
|
x,
|
|
y,
|
|
world.TerrainBuild.HeightTable,
|
|
world.TerrainBuild.Blending,
|
|
world.TerrainBuild.SurfaceCache);
|
|
}),
|
|
static value => value.Dispose());
|
|
Fault(SessionPlayerCompositionPoint.StreamerCreated);
|
|
streamerLease.Resource.Start();
|
|
Fault(SessionPlayerCompositionPoint.StreamerStarted);
|
|
|
|
var streaming = new StreamingController(
|
|
enqueueLoad: (id, kind, generation) => streamerLease.Resource.EnqueueLoad(
|
|
new LandblockBuildRequest(
|
|
id,
|
|
kind,
|
|
generation,
|
|
new LandblockBuildOrigin(
|
|
d.WorldOrigin.CenterX,
|
|
d.WorldOrigin.CenterY))),
|
|
enqueueUnload: streamerLease.Resource.EnqueueUnload,
|
|
completionSource: streamerLease.Resource,
|
|
state: live.WorldState,
|
|
nearRadius: nearRadius,
|
|
farRadius: farRadius,
|
|
presentationPipeline: live.LandblockPipeline,
|
|
clearPendingLoads: streamerLease.Resource.ClearPendingLoads,
|
|
workBudgetOptions: d.Options.StreamingWorkBudgets);
|
|
var streamingOriginRecenter = new StreamingOriginRecenterCoordinator(
|
|
streaming,
|
|
d.WorldOrigin);
|
|
streaming.MaxCompletionsPerFrame =
|
|
d.Settings.ResolvedQuality.MaxCompletionsPerFrame;
|
|
Fault(SessionPlayerCompositionPoint.StreamingCreated);
|
|
|
|
bindings = new SessionPlayerRuntimeBindings();
|
|
var settingsTargets = new RuntimeSettingsTargets(
|
|
new SilkRuntimeDisplayWindowTarget(d.Window),
|
|
live.DrawDispatcher,
|
|
foundation.TerrainAtlas,
|
|
streaming,
|
|
d.RenderRange,
|
|
interaction.RetainedUi?.Host.Root,
|
|
d.Log);
|
|
bindings.Adopt(
|
|
"runtime settings targets",
|
|
d.Settings.BindRuntimeTargetsOwned(settingsTargets));
|
|
Fault(SessionPlayerCompositionPoint.RuntimeSettingsBound);
|
|
|
|
var spawnClaimClassifier = new DatSpawnClaimHydrationClassifier(
|
|
content.Dats,
|
|
d.DatLock);
|
|
var worldQuiescence = new WorldGenerationQuiescence(
|
|
live.WorldAvailability,
|
|
d.Selection,
|
|
live.WorldState,
|
|
guid => live.LiveEntities.TryGetProjectionKey(
|
|
guid,
|
|
out AcDream.Runtime.Entities.RuntimeEntityKey key)
|
|
? key
|
|
: null,
|
|
content.Audio?.Engine);
|
|
var compositeWarmupSource =
|
|
new CompositeWarmupEntitySource(live.WorldState);
|
|
var revealRenderResources = new WorldRevealRenderResourceScheduler(
|
|
foundation.MeshAdapter.SetDestinationRevealUploadPriority,
|
|
foundation.TextureCache.SetDestinationRevealUploadPriority);
|
|
var worldReveal = new WorldRevealCoordinator(
|
|
streaming.IsRenderNeighborhoodResident,
|
|
d.PhysicsEngine.IsSpawnCellReady,
|
|
d.PhysicsEngine.IsNeighborhoodTerrainResident,
|
|
() => live.DrawDispatcher.CompositeTexturesReady,
|
|
(destinationCell, radius) =>
|
|
{
|
|
compositeWarmupSource.Refresh(destinationCell, radius);
|
|
live.DrawDispatcher.PrepareCompositeTextures(
|
|
compositeWarmupSource.Entities,
|
|
compositeWarmupSource.Generation,
|
|
destinationCell,
|
|
radius);
|
|
},
|
|
() =>
|
|
{
|
|
compositeWarmupSource.Reset();
|
|
live.DrawDispatcher.InvalidateCompositeWarmupReadiness();
|
|
},
|
|
spawnClaimClassifier.IsUnhydratable,
|
|
d.Log,
|
|
worldQuiescence,
|
|
streaming,
|
|
revealRenderResources);
|
|
Fault(SessionPlayerCompositionPoint.WorldRevealCreated);
|
|
|
|
return CompleteSessionPlayer(
|
|
host,
|
|
content,
|
|
world,
|
|
interaction,
|
|
live,
|
|
scope,
|
|
streamerLease,
|
|
streaming,
|
|
streamingOriginRecenter,
|
|
worldReveal,
|
|
spawnClaimClassifier,
|
|
bindings,
|
|
ref bindingsOwnedByScope);
|
|
}
|
|
|
|
private SessionPlayerResult CompleteSessionPlayer(
|
|
HostInputCameraResult host,
|
|
ContentEffectsAudioResult content,
|
|
WorldRenderResult world,
|
|
InteractionRetainedUiResult interaction,
|
|
LivePresentationResult live,
|
|
CompositionAcquisitionScope scope,
|
|
CompositionAcquisitionScope.CompositionAcquisitionLease<LandblockStreamer>
|
|
streamerLease,
|
|
StreamingController streaming,
|
|
StreamingOriginRecenterCoordinator streamingOriginRecenter,
|
|
WorldRevealCoordinator worldReveal,
|
|
DatSpawnClaimHydrationClassifier spawnClaimClassifier,
|
|
SessionPlayerRuntimeBindings bindings,
|
|
ref bool bindingsOwnedByScope)
|
|
{
|
|
SessionPlayerDependencies d = _dependencies;
|
|
var networkUpdateBridge = new DeferredLiveEntityNetworkUpdateSink();
|
|
var sealedDungeonCells = new DatSealedDungeonCellClassifier(
|
|
content.Dats,
|
|
d.DatLock);
|
|
var projectionMaterializer = new DatLiveEntityProjectionMaterializer(
|
|
d.Options,
|
|
content.Dats,
|
|
live.LiveEntities,
|
|
content.CollisionAssets,
|
|
content.AnimationLoader,
|
|
live.EntitySpawnAdapter,
|
|
world.Foundation.TextureCache,
|
|
d.ClassificationCache,
|
|
d.EffectPoses,
|
|
live.EquippedChildren,
|
|
d.WorldGameState,
|
|
d.WorldEvents,
|
|
d.PhysicsEngine.ShadowObjects,
|
|
content.CollisionBuilder,
|
|
live.ProjectileController,
|
|
live.AnimationPresenter,
|
|
live.StaticAnimationScheduler,
|
|
d.WorldOrigin,
|
|
d.UpdateClock);
|
|
var originCoordinator = new LiveEntityWorldOriginCoordinator(
|
|
d.WorldOrigin,
|
|
streaming,
|
|
live.WorldState,
|
|
worldReveal,
|
|
d.PlayerIdentity,
|
|
sealedDungeonCells,
|
|
d.Log);
|
|
|
|
var liveSessionLease = scope.Acquire(
|
|
"live-session controller",
|
|
static () => new LiveSessionController(),
|
|
static value => value.Dispose());
|
|
LiveSessionController liveSession = liveSessionLease.Resource;
|
|
var liveSessionCommands = new LiveSessionCommandSurface();
|
|
var liveSessionSource = new LiveSessionAppSource(
|
|
liveSession,
|
|
liveSessionCommands);
|
|
bindings.Adopt(
|
|
"retained-UI live session",
|
|
interaction.LateBindings.Session.Bind(liveSessionSource));
|
|
var localPhysicsTimestamps =
|
|
new LiveSessionLocalPhysicsTimestampPublisher(
|
|
d.PlayerIdentity,
|
|
liveSessionSource);
|
|
Fault(SessionPlayerCompositionPoint.LiveSessionCreated);
|
|
|
|
var teardown = new LiveEntityRuntimeTeardownController(
|
|
live.LiveEntities,
|
|
live.Presentation,
|
|
live.EntityEffects,
|
|
live.RemoteTeleport,
|
|
live.SelectionInteractions,
|
|
d.Selection,
|
|
d.AnimatedEntities,
|
|
d.RemoteMovementObservations,
|
|
d.TranslucencyFades,
|
|
live.ProjectionWithdrawal,
|
|
live.EquippedChildren,
|
|
d.PhysicsEngine.ShadowObjects,
|
|
live.Lights,
|
|
d.ClassificationCache,
|
|
d.PlayerIdentity);
|
|
var dormantLiveEntities = new DormantLiveEntityStore();
|
|
var deletion = new LiveEntityDeletionController(
|
|
live.LiveEntities,
|
|
d.EntityObjects,
|
|
teardown,
|
|
d.PlayerIdentity,
|
|
dormantLiveEntities,
|
|
d.Options.DumpLiveSpawns ? d.Log : null);
|
|
var hydration = new LiveEntityHydrationController(
|
|
live.LiveEntities,
|
|
d.EntityObjects,
|
|
d.DatLock,
|
|
projectionMaterializer,
|
|
new LiveEntityRelationshipProjection(live.EquippedChildren),
|
|
new LiveEntityReadyPublisher(
|
|
live.LiveEntities,
|
|
live.EntityEffects,
|
|
live.Presentation,
|
|
live.RenderSceneShadow?.LiveProjections),
|
|
originCoordinator,
|
|
networkUpdateBridge,
|
|
localPhysicsTimestamps,
|
|
d.PlayerIdentity,
|
|
deletion,
|
|
dormantLiveEntities,
|
|
d.Options.DumpLiveSpawns ? d.Log : null);
|
|
bindings.Adopt(
|
|
"landblock-loaded hydration",
|
|
live.LandblockLoaded.Bind(hydration));
|
|
Fault(SessionPlayerCompositionPoint.HydrationCreated);
|
|
|
|
var networkUpdates = new LiveEntityNetworkUpdateController(
|
|
live.LiveEntities,
|
|
d.EntityObjects.Objects,
|
|
hydration,
|
|
live.EntityEffects,
|
|
live.Presentation,
|
|
live.Lights,
|
|
live.EquippedChildren,
|
|
live.ProjectileController,
|
|
live.RemoteTeleport,
|
|
d.AnimatedEntities,
|
|
new LiveEntityRemoteMotionRuntimeView<RemoteMotion>(d.RuntimeSlot),
|
|
d.RemoteMovementObservations,
|
|
d.RemotePhysicsUpdater,
|
|
d.RemoteInboundMotion,
|
|
live.MotionRuntime,
|
|
d.PhysicsEngine,
|
|
content.Dats,
|
|
content.AnimationLoader,
|
|
interaction.CombatTarget,
|
|
d.WorldOrigin,
|
|
d.TeleportSink,
|
|
d.PlayerController,
|
|
d.PlayerOutbound,
|
|
d.PlayerHost,
|
|
d.PlayerIdentity,
|
|
d.UpdateClock,
|
|
liveSessionSource,
|
|
localPhysicsTimestamps.Publish,
|
|
d.MovementDiagnostics);
|
|
var liveness = new LiveEntityLivenessController(
|
|
live.LiveEntities,
|
|
d.PlayerIdentity,
|
|
deletion,
|
|
dormantLiveEntities);
|
|
var sessionEvents = new LiveEntitySessionController(
|
|
d.InboundEntityEvents,
|
|
hydration,
|
|
networkUpdates,
|
|
d.TeleportSink,
|
|
live.EntityEffects);
|
|
bindings.Adopt(
|
|
"live parent acceptance",
|
|
live.ParentAcceptance.BindOwned(
|
|
hydration.TryAcceptParentForProjection));
|
|
bindings.Adopt(
|
|
"same-generation network updates",
|
|
networkUpdateBridge.BindOwned(networkUpdates));
|
|
bindings.BindEntityReady(
|
|
live.EquippedChildren,
|
|
candidate => _ = hydration.OnEntityReady(candidate));
|
|
bindings.BindAppearanceApplied(
|
|
hydration,
|
|
guid =>
|
|
{
|
|
if (guid == d.PlayerIdentity.ServerGuid)
|
|
live.PaperdollPresenter?.MarkDirty();
|
|
});
|
|
Fault(SessionPlayerCompositionPoint.LiveEntityGraphBound);
|
|
|
|
d.WorldOrigin.SetPlaceholder(
|
|
world.TerrainBuild.InitialCenterX,
|
|
world.TerrainBuild.InitialCenterY);
|
|
bindings.Adopt(
|
|
"combat attack operations",
|
|
d.CombatAttackOperations.BindOwned(
|
|
new LiveCombatAttackOperations(
|
|
d.Combat,
|
|
new CombatAttackTargetSource(
|
|
d.Selection,
|
|
live.LiveEntities,
|
|
d.EntityObjects.Objects,
|
|
d.PlayerIdentity),
|
|
d.Settings,
|
|
d.PlayerController,
|
|
d.PlayerOutbound,
|
|
liveSessionSource,
|
|
liveSessionSource,
|
|
d.CombatFeedback)));
|
|
Fault(SessionPlayerCompositionPoint.CombatOperationsBound);
|
|
|
|
MouseLookController? mouseLook =
|
|
host.MouseSource is not null && host.MouseLookCursor is not null
|
|
? new MouseLookController(
|
|
host.MouseSource,
|
|
d.PointerPosition,
|
|
d.PlayerMode,
|
|
d.PlayerController,
|
|
host.CameraController,
|
|
d.ChaseCameraInput,
|
|
d.MovementInput,
|
|
d.PlayerOutbound,
|
|
liveSessionSource,
|
|
host.MouseLookCursor,
|
|
new EnvironmentInputMonotonicClock())
|
|
: null;
|
|
var gameplayInput = new GameplayInputFrameController(
|
|
host.InputDispatcher,
|
|
d.MovementInput,
|
|
mouseLook,
|
|
new CombatAttackInputFrameAdapter(interaction.CombatAttack));
|
|
if (host.CameraPointerInput is { } cameraPointer)
|
|
{
|
|
bindings.Adopt(
|
|
"camera pointer gameplay frame",
|
|
cameraPointer.BindGameplayFrameOwned(gameplayInput));
|
|
}
|
|
Fault(SessionPlayerCompositionPoint.GameplayInputBound);
|
|
|
|
var streamingFrame = new StreamingFrameController(
|
|
d.Options.LiveMode,
|
|
d.PlayerMode,
|
|
d.PlayerController,
|
|
liveSessionSource,
|
|
d.WorldOrigin,
|
|
networkUpdates,
|
|
new FlyCameraStreamingObserverSource(host.CameraController),
|
|
new PhysicsStreamingDungeonCellSource(d.PhysicsEngine),
|
|
streamingOriginRecenter,
|
|
streaming,
|
|
new LiveProjectionRescueRebucketter(
|
|
live.WorldState,
|
|
live.LiveEntities));
|
|
var liveEffectFrame = new LiveEffectFrameController(
|
|
d.TranslucencyFades,
|
|
content.AnimationHookFrames,
|
|
live.EntityEffects,
|
|
content.ParticleSink,
|
|
live.Lights,
|
|
d.ParticleVisibility,
|
|
content.ParticleSystem,
|
|
content.ScriptRunner,
|
|
d.UpdateClock,
|
|
new SettingsParticleRangeSource(d.Settings));
|
|
var liveSpatialReconciler = new LiveSpatialPresentationReconciler(
|
|
live.EntityEffects,
|
|
live.EquippedChildren,
|
|
content.ParticleSink,
|
|
live.Lights,
|
|
live.RenderSceneShadow?.LiveProjections);
|
|
var localPlayerAnimation = new LocalPlayerAnimationController(
|
|
live.LiveEntities,
|
|
d.PlayerIdentity,
|
|
d.AnimatedEntities,
|
|
live.AnimationPresenter,
|
|
content.AnimationHookFrames);
|
|
var localPlayerShadow = new LocalPlayerShadowSynchronizer(
|
|
d.PhysicsEngine,
|
|
live.LiveEntities,
|
|
d.PlayerIdentity,
|
|
d.WorldOrigin,
|
|
d.PlayerShadow);
|
|
var localPlayerProjection = new LocalPlayerProjectionController(
|
|
new LiveLocalPlayerProjectionRuntime(
|
|
live.LiveEntities,
|
|
d.PlayerIdentity,
|
|
d.WorldOrigin,
|
|
localPlayerShadow));
|
|
var localPlayerFrameRuntime = new LiveLocalPlayerFrameRuntime(
|
|
host.CameraController,
|
|
d.PlayerMode,
|
|
d.PlayerController,
|
|
d.ChaseCameraInput,
|
|
d.MovementInput,
|
|
d.InputCapture,
|
|
live.LiveEntities,
|
|
d.PlayerIdentity,
|
|
d.PlayerHost,
|
|
localPlayerProjection,
|
|
d.PlayerOutbound,
|
|
liveSessionSource);
|
|
var localPlayerFrame = new RetailLocalPlayerFrameController(
|
|
localPlayerFrameRuntime,
|
|
d.MovementInput);
|
|
var liveObjectFrame = new LiveObjectFrameController(
|
|
d.InboundEntityEvents,
|
|
localPlayerFrame,
|
|
live.SelectionInteractions,
|
|
live.LiveEntities,
|
|
d.PlayerIdentity,
|
|
d.WorldOrigin,
|
|
live.AnimationScheduler,
|
|
live.StaticAnimationScheduler,
|
|
live.AnimationPresenter,
|
|
d.AnimatedEntities,
|
|
live.EquippedChildren,
|
|
liveEffectFrame,
|
|
live.RenderSceneShadow?.LiveProjections,
|
|
live.RenderSceneShadow?.StaticProjections);
|
|
Fault(SessionPlayerCompositionPoint.UpdateLeavesCreated);
|
|
|
|
var playerMode = new PlayerModeController(
|
|
d.PlayerMode,
|
|
d.PlayerController,
|
|
d.PlayerHost,
|
|
d.ChaseCameraInput,
|
|
host.CameraController,
|
|
d.PhysicsEngine,
|
|
live.LiveEntities,
|
|
d.PlayerIdentity,
|
|
d.WorldOrigin,
|
|
d.MotionBindings,
|
|
content.Dats,
|
|
d.DatLock,
|
|
content.CollisionAssets,
|
|
d.AnimatedEntities,
|
|
localPlayerAnimation,
|
|
localPlayerShadow,
|
|
d.PlayerApproachCompletions,
|
|
gameplayInput,
|
|
liveSessionSource,
|
|
d.MovementDiagnostics,
|
|
d.PlayerSkills,
|
|
d.ViewportAspect);
|
|
if (d.SettingsDevTools.DevTools is { } devTools)
|
|
{
|
|
bindings.Adopt(
|
|
"developer player mode",
|
|
devTools.LateBindings.PlayerModeCommands.BindOwned(playerMode));
|
|
bindings.Adopt(
|
|
"developer command bus",
|
|
devTools.CommandBus.BindOwned(liveSessionSource));
|
|
}
|
|
var playerModeAutoEntry = new PlayerModeAutoEntry(
|
|
new LivePlayerModeAutoEntryContext(
|
|
liveSessionSource,
|
|
live.LiveEntities,
|
|
d.PlayerIdentity,
|
|
worldReveal,
|
|
d.PlayerMode,
|
|
playerMode));
|
|
playerMode.BindAutoEntry(playerModeAutoEntry);
|
|
Fault(SessionPlayerCompositionPoint.PlayerModeBound);
|
|
|
|
LocalPlayerTeleportController localTeleport =
|
|
d.PortalTunnelFallback.Transfer(
|
|
portalTunnel => new LocalPlayerTeleportController(
|
|
new LiveLocalPlayerTeleportAuthority(
|
|
live.LiveEntities,
|
|
d.PlayerIdentity),
|
|
gameplayInput,
|
|
playerMode,
|
|
new LocalPlayerTeleportStreamingOperations(
|
|
d.WorldOrigin,
|
|
streamingOriginRecenter,
|
|
streaming,
|
|
sealedDungeonCells),
|
|
worldReveal,
|
|
new LocalPlayerTeleportPlacement(
|
|
d.PhysicsEngine,
|
|
live.LiveEntities,
|
|
d.PlayerIdentity,
|
|
d.PlayerController,
|
|
d.PlayerHost,
|
|
d.ChaseCameraInput,
|
|
d.WorldOrigin,
|
|
liveSpatialReconciler),
|
|
new LocalPlayerTeleportSession(liveSessionSource),
|
|
new LocalPlayerTeleportPresentation(portalTunnel)));
|
|
var teleportLease = scope.Own(
|
|
"local-player teleport",
|
|
localTeleport,
|
|
static value => value.Dispose());
|
|
Fault(SessionPlayerCompositionPoint.PortalTransferred);
|
|
bindings.Adopt(
|
|
"local teleport network sink",
|
|
d.TeleportSink.BindOwned(localTeleport));
|
|
bindings.Adopt(
|
|
"selection view plane",
|
|
interaction.LateBindings.SelectionViewPlane.Bind(localTeleport));
|
|
Fault(SessionPlayerCompositionPoint.TeleportBound);
|
|
|
|
IDisposable componentLifecycleBinding =
|
|
live.ComponentLifecycle.BindOwned(teardown);
|
|
IDisposable componentLifecycleAdoption;
|
|
try
|
|
{
|
|
componentLifecycleAdoption = live.RuntimeBindings.AdoptOwned(
|
|
"live runtime component teardown",
|
|
componentLifecycleBinding);
|
|
}
|
|
catch
|
|
{
|
|
componentLifecycleBinding.Dispose();
|
|
throw;
|
|
}
|
|
var componentLifecycleLease = scope.Own(
|
|
"live runtime component teardown adoption",
|
|
componentLifecycleAdoption,
|
|
static value => value.Dispose());
|
|
|
|
AcDream.UI.Abstractions.Panels.Vitals.VitalsVM? vitals =
|
|
d.SettingsDevTools.DevTools?.Vitals
|
|
?? interaction.RetainedUi?.Vitals;
|
|
var sessionRuntimeFactory = new LiveSessionRuntimeFactory(
|
|
new LiveSessionPlayerRuntime(
|
|
d.PlayerIdentity,
|
|
d.PlayerController,
|
|
d.PlayerSkills,
|
|
d.WorldOrigin,
|
|
d.CharacterOptions),
|
|
new LiveSessionDomainRuntime(
|
|
d.EntityObjects,
|
|
d.Character,
|
|
d.Combat,
|
|
d.Inventory,
|
|
d.Communication),
|
|
new LiveSessionUiRuntime(
|
|
interaction.RetainedUi?.Runtime,
|
|
vitals,
|
|
interaction.RetainedUi?.CharacterSheet,
|
|
interaction.RetainedUi?.Magic,
|
|
live.PaperdollPresenter),
|
|
new LiveSessionInteractionRuntime(
|
|
d.Settings,
|
|
gameplayInput,
|
|
playerMode,
|
|
playerModeAutoEntry,
|
|
interaction.ItemInteraction,
|
|
interaction.CombatAttack,
|
|
live.SelectionInteractions),
|
|
new LiveSessionWorldRuntime(
|
|
content.Dats,
|
|
live.WorldState,
|
|
live.LiveEntities,
|
|
sessionEvents,
|
|
d.WorldEnvironment,
|
|
d.TeleportSink,
|
|
spawnClaimClassifier,
|
|
live.EquippedChildren,
|
|
live.SelectionScene,
|
|
d.ParticleVisibility,
|
|
d.InboundEntityEvents,
|
|
liveness,
|
|
networkUpdates,
|
|
hydration,
|
|
live.RemoteTeleport,
|
|
live.EntityEffects,
|
|
content.AnimationHookFrames,
|
|
live.Presentation,
|
|
d.RemoteMovementObservations,
|
|
live.RenderSceneShadow),
|
|
liveSessionCommands,
|
|
d.Log);
|
|
LiveSessionHost sessionHost = sessionRuntimeFactory.Create(
|
|
liveSession,
|
|
new LiveSessionConnectOptions(
|
|
d.Options.LiveMode,
|
|
d.Options.LiveHost,
|
|
d.Options.LivePort,
|
|
d.Options.LiveUser ?? string.Empty,
|
|
d.Options.LivePass ?? string.Empty));
|
|
Fault(SessionPlayerCompositionPoint.SessionHostCreated);
|
|
|
|
Action<string>? debugToast = d.SettingsDevTools.DevTools is { } devOwner
|
|
? message => devOwner.Debug.AddToast(message)
|
|
: null;
|
|
var combatCommand = new LiveCombatModeCommandController(
|
|
new LiveSessionCombatModeAuthority(sessionHost),
|
|
new LocalPlayerCombatEquipmentSource(
|
|
d.EntityObjects.Objects,
|
|
d.PlayerIdentity),
|
|
d.Combat,
|
|
new ItemInteractionCombatModeIntentSink(
|
|
interaction.ItemInteraction),
|
|
d.Log,
|
|
debugToast,
|
|
text => d.Communication.Chat.OnSystemMessage(text, 0x1Au));
|
|
bindings.Adopt(
|
|
"live combat-mode commands",
|
|
d.CombatModeCommands.BindOwned(combatCommand));
|
|
var gameRuntime = new CurrentGameRuntimeAdapter(
|
|
liveSession,
|
|
sessionHost,
|
|
liveSessionCommands,
|
|
d.PlayerIdentity,
|
|
live.LiveEntities,
|
|
d.EntityObjects,
|
|
d.Communication,
|
|
d.PlayerController,
|
|
worldReveal,
|
|
d.UpdateClock,
|
|
d.Selection,
|
|
live.SelectionInteractions,
|
|
gameplayInput,
|
|
combatCommand);
|
|
bindings.Adopt("current game runtime adapter", gameRuntime);
|
|
|
|
var nearbyDiagnostics = new NearbyWorldDiagnosticDumper(
|
|
new RuntimeNearbyWorldDiagnosticSource(
|
|
d.PlayerMode,
|
|
d.PlayerController,
|
|
host.CameraController,
|
|
d.WorldOrigin,
|
|
live.WorldState,
|
|
d.PhysicsEngine),
|
|
d.Log);
|
|
var runtimeDiagnostics = new RuntimeDiagnosticCommandController(
|
|
d.WorldEnvironment,
|
|
d.WorldSceneDebugState,
|
|
host.CameraPointerInput,
|
|
nearbyDiagnostics,
|
|
debugToast);
|
|
bindings.Adopt(
|
|
"runtime diagnostic commands",
|
|
d.RuntimeDiagnosticCommands.BindOwned(runtimeDiagnostics));
|
|
Fault(SessionPlayerCompositionPoint.CommandsBound);
|
|
|
|
CompositionAcquisitionScope.CompositionAcquisitionLease<
|
|
GameplayInputActionRouter>? gameplayActionsLease = null;
|
|
if (host.InputDispatcher is { } dispatcher)
|
|
{
|
|
CameraPointerInputController pointer = host.CameraPointerInput
|
|
?? throw new InvalidOperationException(
|
|
"Gameplay action routing requires the composed pointer owner.");
|
|
var commands = new GameplayInputCommandController(
|
|
new RetainedGameplayWindowCommands(
|
|
interaction.RetainedUi?.Runtime),
|
|
new DevToolsGameplayCommands(
|
|
d.SettingsDevTools.DevTools?.Presenter),
|
|
runtimeDiagnostics,
|
|
new PlayerModeGameplayCommands(
|
|
d.PlayerMode,
|
|
playerMode),
|
|
new ItemTargetModeCommands(interaction.ItemInteraction),
|
|
new GameplayCameraModeCommands(host.CameraController),
|
|
gameRuntime,
|
|
gameRuntime.Combat,
|
|
new GameplayWindowCommands(d.Window.Close));
|
|
var targets = new RuntimeGameplayInputPriorityTargets(
|
|
gameplayInput,
|
|
pointer,
|
|
interaction.RetainedUi?.Runtime,
|
|
live.SelectionInteractions,
|
|
gameRuntime,
|
|
gameRuntime.Selection,
|
|
gameRuntime.MovementCommands,
|
|
commands);
|
|
GameplayInputActionRouter gameplayActions =
|
|
GameplayInputActionRouter.Create(
|
|
dispatcher,
|
|
d.Combat,
|
|
targets,
|
|
d.HostQuiescence,
|
|
d.Log);
|
|
gameplayActionsLease = scope.Own(
|
|
"gameplay input actions",
|
|
gameplayActions,
|
|
static value => value.Dispose());
|
|
gameplayActions.Attach();
|
|
}
|
|
Fault(SessionPlayerCompositionPoint.GameplayActionsAttached);
|
|
|
|
var bindingsLease = scope.Own(
|
|
"session/player runtime bindings",
|
|
bindings,
|
|
static value => value.Dispose());
|
|
bindingsOwnedByScope = true;
|
|
var result = new SessionPlayerResult(
|
|
streamerLease.Resource,
|
|
streaming,
|
|
streamingOriginRecenter,
|
|
worldReveal,
|
|
spawnClaimClassifier,
|
|
liveSession,
|
|
hydration,
|
|
networkUpdates,
|
|
liveness,
|
|
sessionEvents,
|
|
gameplayInput,
|
|
streamingFrame,
|
|
localPlayerAnimation,
|
|
localPlayerShadow,
|
|
localPlayerFrameRuntime,
|
|
localPlayerFrame,
|
|
liveSpatialReconciler,
|
|
liveObjectFrame,
|
|
playerMode,
|
|
playerModeAutoEntry,
|
|
teleportLease.Resource,
|
|
sessionHost,
|
|
gameRuntime,
|
|
gameplayActionsLease?.Resource,
|
|
bindings);
|
|
_publication.PublishSessionPlayer(result);
|
|
|
|
streamerLease.Transfer();
|
|
liveSessionLease.Transfer();
|
|
teleportLease.Transfer();
|
|
gameplayActionsLease?.Transfer();
|
|
componentLifecycleLease.Transfer();
|
|
bindingsLease.Transfer();
|
|
Fault(SessionPlayerCompositionPoint.ResultPublished);
|
|
return result;
|
|
}
|
|
|
|
private void Fault(SessionPlayerCompositionPoint point) =>
|
|
_faultInjection?.Invoke(point);
|
|
}
|