refactor(update): cut over the frame orchestrator
Reduce GameWindow.OnUpdate to one typed orchestration call and remove transitive window callbacks from teardown, liveness, teleport placement, live presentation, auto-entry, and entity packet routing. Preserve the frozen retail object/network order while making the production owner graph explicit and testable.
This commit is contained in:
parent
947c61d2d7
commit
e91f310279
18 changed files with 864 additions and 348 deletions
|
|
@ -1,11 +1,14 @@
|
|||
using System;
|
||||
using AcDream.App.Net;
|
||||
using AcDream.App.Streaming;
|
||||
using AcDream.App.World;
|
||||
|
||||
namespace AcDream.App.Input;
|
||||
|
||||
/// <summary>
|
||||
/// Phase K.2 — one-shot guard that auto-enters player mode after a
|
||||
/// successful login once every prerequisite is satisfied. Owned by
|
||||
/// <c>GameWindow</c> and ticked each frame from <c>OnUpdate</c>.
|
||||
/// successful login once every prerequisite is satisfied. The update-frame
|
||||
/// orchestrator ticks it through a typed production context.
|
||||
///
|
||||
/// <para>
|
||||
/// Why is this its own class? The auto-entry has four independent
|
||||
|
|
@ -33,20 +36,108 @@ namespace AcDream.App.Input;
|
|||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// All preconditions are passed in as predicates so the class doesn't
|
||||
/// pull in <c>WorldSession</c>, <c>PlayerMovementController</c>, or
|
||||
/// any GameWindow-internal types — the unit test wires them to plain
|
||||
/// boolean fields.
|
||||
/// Production preconditions come from focused runtime owners rather than the
|
||||
/// window host. The public delegate constructor remains a narrow test seam for
|
||||
/// plain boolean fixtures.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
internal interface IPlayerModeAutoEntryContext
|
||||
{
|
||||
bool IsLiveInWorld { get; }
|
||||
bool IsPlayerEntityPresent { get; }
|
||||
bool IsPlayerControllerReady { get; }
|
||||
bool IsWorldReady { get; }
|
||||
bool IsPlayerModeActive { get; }
|
||||
void EnterPlayerMode();
|
||||
}
|
||||
|
||||
/// <summary>Production auto-entry context over canonical runtime owners.</summary>
|
||||
internal sealed class LivePlayerModeAutoEntryContext
|
||||
: IPlayerModeAutoEntryContext
|
||||
{
|
||||
private readonly ILiveInWorldSource _session;
|
||||
private readonly LiveEntityRuntime _liveEntities;
|
||||
private readonly ILocalPlayerIdentitySource _identity;
|
||||
private readonly WorldRevealCoordinator _worldReveal;
|
||||
private readonly ILocalPlayerModeSource _mode;
|
||||
private readonly PlayerModeController _playerMode;
|
||||
|
||||
public LivePlayerModeAutoEntryContext(
|
||||
ILiveInWorldSource session,
|
||||
LiveEntityRuntime liveEntities,
|
||||
ILocalPlayerIdentitySource identity,
|
||||
WorldRevealCoordinator worldReveal,
|
||||
ILocalPlayerModeSource mode,
|
||||
PlayerModeController playerMode)
|
||||
{
|
||||
_session = session ?? throw new ArgumentNullException(nameof(session));
|
||||
_liveEntities = liveEntities ?? throw new ArgumentNullException(nameof(liveEntities));
|
||||
_identity = identity ?? throw new ArgumentNullException(nameof(identity));
|
||||
_worldReveal = worldReveal ?? throw new ArgumentNullException(nameof(worldReveal));
|
||||
_mode = mode ?? throw new ArgumentNullException(nameof(mode));
|
||||
_playerMode = playerMode ?? throw new ArgumentNullException(nameof(playerMode));
|
||||
}
|
||||
|
||||
public bool IsLiveInWorld => _session.IsInWorld;
|
||||
|
||||
public bool IsPlayerEntityPresent =>
|
||||
_liveEntities.MaterializedWorldEntities.ContainsKey(_identity.ServerGuid);
|
||||
|
||||
public bool IsPlayerControllerReady => true;
|
||||
|
||||
public bool IsWorldReady =>
|
||||
_liveEntities.TryGetSnapshot(
|
||||
_identity.ServerGuid,
|
||||
out AcDream.Core.Net.WorldSession.EntitySpawn player)
|
||||
&& player.Position is { LandblockId: not 0u } position
|
||||
&& _worldReveal.Evaluate(position.LandblockId).IsReady;
|
||||
|
||||
public bool IsPlayerModeActive => _mode.IsPlayerMode;
|
||||
|
||||
public void EnterPlayerMode() => _playerMode.EnterFromAutoEntry();
|
||||
}
|
||||
|
||||
public sealed class PlayerModeAutoEntry
|
||||
{
|
||||
private readonly Func<bool> _isLiveInWorld;
|
||||
private readonly Func<bool> _isPlayerEntityPresent;
|
||||
private readonly Func<bool> _isPlayerControllerReady;
|
||||
private readonly Func<bool> _isWorldReady;
|
||||
private readonly Func<bool> _isPlayerModeActive;
|
||||
private readonly Action _enterPlayerMode;
|
||||
private sealed class DelegateContext : IPlayerModeAutoEntryContext
|
||||
{
|
||||
private readonly Func<bool> _isLiveInWorld;
|
||||
private readonly Func<bool> _isPlayerEntityPresent;
|
||||
private readonly Func<bool> _isPlayerControllerReady;
|
||||
private readonly Func<bool> _isWorldReady;
|
||||
private readonly Action _enterPlayerMode;
|
||||
private readonly Func<bool> _isPlayerModeActive;
|
||||
|
||||
public DelegateContext(
|
||||
Func<bool> isLiveInWorld,
|
||||
Func<bool> isPlayerEntityPresent,
|
||||
Func<bool> isPlayerControllerReady,
|
||||
Func<bool> isWorldReady,
|
||||
Action enterPlayerMode,
|
||||
Func<bool>? isPlayerModeActive)
|
||||
{
|
||||
_isLiveInWorld = isLiveInWorld
|
||||
?? throw new ArgumentNullException(nameof(isLiveInWorld));
|
||||
_isPlayerEntityPresent = isPlayerEntityPresent
|
||||
?? throw new ArgumentNullException(nameof(isPlayerEntityPresent));
|
||||
_isPlayerControllerReady = isPlayerControllerReady
|
||||
?? throw new ArgumentNullException(nameof(isPlayerControllerReady));
|
||||
_isWorldReady = isWorldReady
|
||||
?? throw new ArgumentNullException(nameof(isWorldReady));
|
||||
_enterPlayerMode = enterPlayerMode
|
||||
?? throw new ArgumentNullException(nameof(enterPlayerMode));
|
||||
_isPlayerModeActive = isPlayerModeActive ?? (() => false);
|
||||
}
|
||||
|
||||
public bool IsLiveInWorld => _isLiveInWorld();
|
||||
public bool IsPlayerEntityPresent => _isPlayerEntityPresent();
|
||||
public bool IsPlayerControllerReady => _isPlayerControllerReady();
|
||||
public bool IsWorldReady => _isWorldReady();
|
||||
public bool IsPlayerModeActive => _isPlayerModeActive();
|
||||
public void EnterPlayerMode() => _enterPlayerMode();
|
||||
}
|
||||
|
||||
private readonly IPlayerModeAutoEntryContext _context;
|
||||
|
||||
private bool _armed;
|
||||
|
||||
|
|
@ -72,20 +163,24 @@ public sealed class PlayerModeAutoEntry
|
|||
/// player transition). Must construct the controller + chase
|
||||
/// camera and switch the active camera; the auto-entry doesn't
|
||||
/// reach inside.</param>
|
||||
internal PlayerModeAutoEntry(IPlayerModeAutoEntryContext context) =>
|
||||
_context = context ?? throw new ArgumentNullException(nameof(context));
|
||||
|
||||
public PlayerModeAutoEntry(
|
||||
Func<bool> isLiveInWorld,
|
||||
Func<bool> isPlayerEntityPresent,
|
||||
Func<bool> isPlayerControllerReady,
|
||||
Func<bool> isWorldReady,
|
||||
Action enterPlayerMode,
|
||||
Action enterPlayerMode,
|
||||
Func<bool>? isPlayerModeActive = null)
|
||||
: this(new DelegateContext(
|
||||
isLiveInWorld,
|
||||
isPlayerEntityPresent,
|
||||
isPlayerControllerReady,
|
||||
isWorldReady,
|
||||
enterPlayerMode,
|
||||
isPlayerModeActive))
|
||||
{
|
||||
_isLiveInWorld = isLiveInWorld ?? throw new ArgumentNullException(nameof(isLiveInWorld));
|
||||
_isPlayerEntityPresent = isPlayerEntityPresent ?? throw new ArgumentNullException(nameof(isPlayerEntityPresent));
|
||||
_isPlayerControllerReady = isPlayerControllerReady ?? throw new ArgumentNullException(nameof(isPlayerControllerReady));
|
||||
_isWorldReady = isWorldReady ?? throw new ArgumentNullException(nameof(isWorldReady));
|
||||
_enterPlayerMode = enterPlayerMode ?? throw new ArgumentNullException(nameof(enterPlayerMode));
|
||||
_isPlayerModeActive = isPlayerModeActive ?? (() => false);
|
||||
}
|
||||
|
||||
/// <summary>True iff <see cref="TryEnter"/> would still fire if the
|
||||
|
|
@ -115,18 +210,18 @@ public sealed class PlayerModeAutoEntry
|
|||
public bool TryEnter()
|
||||
{
|
||||
if (!_armed) return false;
|
||||
if (_isPlayerModeActive())
|
||||
if (_context.IsPlayerModeActive)
|
||||
{
|
||||
_armed = false;
|
||||
return false;
|
||||
}
|
||||
if (!_isLiveInWorld()) return false;
|
||||
if (!_isPlayerEntityPresent()) return false;
|
||||
if (!_isPlayerControllerReady()) return false;
|
||||
if (!_isWorldReady()) return false;
|
||||
if (!_context.IsLiveInWorld) return false;
|
||||
if (!_context.IsPlayerEntityPresent) return false;
|
||||
if (!_context.IsPlayerControllerReady) return false;
|
||||
if (!_context.IsWorldReady) return false;
|
||||
|
||||
_armed = false;
|
||||
_enterPlayerMode();
|
||||
_context.EnterPlayerMode();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue