refactor(world): extract live entity hydration

This commit is contained in:
Erik 2026-07-21 17:32:45 +02:00
parent 27dcd0ecb7
commit d10c5f2d54
17 changed files with 4310 additions and 1269 deletions

View file

@ -0,0 +1,150 @@
using AcDream.App.Rendering.Vfx;
using AcDream.App.Streaming;
using AcDream.Core.Net;
namespace AcDream.App.World;
internal sealed class DelegateLiveEntitySpawnRelationshipSink(
Action<WorldSession.EntitySpawn> onSpawn) : ILiveEntitySpawnRelationshipSink
{
private readonly Action<WorldSession.EntitySpawn> _onSpawn =
onSpawn ?? throw new ArgumentNullException(nameof(onSpawn));
public void OnSpawn(WorldSession.EntitySpawn spawn) => _onSpawn(spawn);
}
internal sealed class LiveEntityReadyPublisher : ILiveEntityReadyPublisher
{
private readonly LiveEntityRuntime _runtime;
private readonly Func<LiveEntityRecord, bool> _prepareEffects;
private readonly Func<LiveEntityRecord, bool> _presentState;
private readonly Func<LiveEntityRecord, bool> _replayEffects;
public LiveEntityReadyPublisher(
LiveEntityRuntime runtime,
EntityEffectController effects,
LiveEntityPresentationController presentation)
: this(
runtime,
record => effects.PrepareLiveEntityOwner(record.ServerGuid),
record => presentation.OnLiveEntityReady(record.ServerGuid),
record => effects.ReplayPendingForLiveEntity(record.ServerGuid))
{
ArgumentNullException.ThrowIfNull(effects);
ArgumentNullException.ThrowIfNull(presentation);
}
internal LiveEntityReadyPublisher(
LiveEntityRuntime runtime,
Func<LiveEntityRecord, bool> prepareEffects,
Func<LiveEntityRecord, bool> presentState,
Func<LiveEntityRecord, bool> replayEffects)
{
_runtime = runtime ?? throw new ArgumentNullException(nameof(runtime));
_prepareEffects = prepareEffects
?? throw new ArgumentNullException(nameof(prepareEffects));
_presentState = presentState
?? throw new ArgumentNullException(nameof(presentState));
_replayEffects = replayEffects
?? throw new ArgumentNullException(nameof(replayEffects));
}
public bool Publish(LiveEntityRecord expectedRecord)
{
ArgumentNullException.ThrowIfNull(expectedRecord);
ulong createIntegrationVersion = expectedRecord.CreateIntegrationVersion;
if (!_runtime.IsCurrentCreateIntegration(
expectedRecord,
createIntegrationVersion)
|| !_prepareEffects(expectedRecord)
|| !_runtime.IsCurrentCreateIntegration(
expectedRecord,
createIntegrationVersion))
return false;
if (!_presentState(expectedRecord)
|| !_runtime.IsCurrentCreateIntegration(
expectedRecord,
createIntegrationVersion))
{
return false;
}
return _replayEffects(expectedRecord)
&& _runtime.IsCurrentCreateIntegration(
expectedRecord,
createIntegrationVersion);
}
}
/// <summary>
/// Converts the first accepted local-player Position into the shared streaming
/// origin. Retail <c>SmartBox::UseTime @ 0x00455410</c> blocks completion until
/// destination cells are ready; already-resident landblocks are returned to
/// hydration for the same synchronous recovery edge.
/// </summary>
internal sealed class LiveEntityWorldOriginCoordinator : ILiveEntityWorldOriginCoordinator
{
private readonly LiveWorldOriginState _origin;
private readonly StreamingController _streaming;
private readonly GpuWorldState _worldState;
private readonly WorldRevealCoordinator _worldReveal;
private readonly Func<uint> _playerGuid;
private readonly Func<uint, bool> _isSealedDungeonCell;
private readonly Action<string>? _diagnostic;
public LiveEntityWorldOriginCoordinator(
LiveWorldOriginState origin,
StreamingController streaming,
GpuWorldState worldState,
WorldRevealCoordinator worldReveal,
Func<uint> playerGuid,
Func<uint, bool> isSealedDungeonCell,
Action<string>? diagnostic = null)
{
_origin = origin ?? throw new ArgumentNullException(nameof(origin));
_streaming = streaming ?? throw new ArgumentNullException(nameof(streaming));
_worldState = worldState ?? throw new ArgumentNullException(nameof(worldState));
_worldReveal = worldReveal ?? throw new ArgumentNullException(nameof(worldReveal));
_playerGuid = playerGuid ?? throw new ArgumentNullException(nameof(playerGuid));
_isSealedDungeonCell = isSealedDungeonCell
?? throw new ArgumentNullException(nameof(isSealedDungeonCell));
_diagnostic = diagnostic;
}
public bool IsKnown => _origin.IsKnown;
public LiveEntityOriginInitialization TryInitialize(
WorldSession.EntitySpawn spawn)
{
if (spawn.Guid != _playerGuid()
|| spawn.Position is not { } position)
{
return new(_origin.IsKnown, Array.Empty<uint>());
}
int lbX = (int)((position.LandblockId >> 24) & 0xFFu);
int lbY = (int)((position.LandblockId >> 16) & 0xFFu);
int oldCenterX = _origin.CenterX;
int oldCenterY = _origin.CenterY;
if (!_origin.TryInitialize(lbX, lbY))
return new(true, Array.Empty<uint>());
if (lbX != oldCenterX || lbY != oldCenterY)
{
_diagnostic?.Invoke(
$"live: first player position — recentering streaming from ({oldCenterX},{oldCenterY}) "
+ $"to ({lbX},{lbY}) @0x{position.LandblockId:X8}");
}
_streaming.InitializeKnownLoginCenter(
lbX,
lbY,
isSealedDungeon: _isSealedDungeonCell(position.LandblockId));
_worldReveal.Begin(WorldRevealKind.Login);
return new(
true,
_worldState.LoadedLandblockIds.ToArray());
}
}