fix(runtime): restore interaction completion ownership
Preserve prepublication local motion completion, require the PartArray enter-world lifecycle port, and balance deferred Use busy ownership across dispatch and cancellation. Reconcile the completed GameWindow connected gates and add regression coverage. Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
parent
5c955c36ec
commit
f9736ece6c
26 changed files with 675 additions and 68 deletions
22
src/AcDream.App/World/LiveEntityPartArrayEnterWorldPort.cs
Normal file
22
src/AcDream.App/World/LiveEntityPartArrayEnterWorldPort.cs
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
namespace AcDream.App.World;
|
||||
|
||||
/// <summary>
|
||||
/// Strongly typed retail PartArray world-entry boundary.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <c>CPhysicsObj::set_hidden @ 0x00514C60</c> invokes
|
||||
/// <c>CPartArray::HandleEnterWorld @ 0x00517D70</c> on both Hidden edges.
|
||||
/// This dedicated port prevents that mandatory lifecycle callback from being
|
||||
/// confused with neighboring <see cref="Action{T}"/> presentation callbacks.
|
||||
/// </remarks>
|
||||
public sealed class LiveEntityPartArrayEnterWorldPort
|
||||
{
|
||||
private readonly Action<uint> _handleEnterWorld;
|
||||
|
||||
public LiveEntityPartArrayEnterWorldPort(Action<uint> handleEnterWorld) =>
|
||||
_handleEnterWorld = handleEnterWorld
|
||||
?? throw new ArgumentNullException(nameof(handleEnterWorld));
|
||||
|
||||
public void HandleEnterWorld(uint localEntityId) =>
|
||||
_handleEnterWorld(localEntityId);
|
||||
}
|
||||
|
|
@ -27,7 +27,7 @@ public sealed class LiveEntityPresentationController : IDisposable
|
|||
private readonly Action<uint> _clearInvalidTarget;
|
||||
private readonly Func<(int X, int Y)> _liveCenter;
|
||||
private readonly Action<uint>? _onShadowRestored;
|
||||
private readonly Action<uint> _handlePartArrayEnterWorld;
|
||||
private readonly LiveEntityPartArrayEnterWorldPort _partArrayEnterWorld;
|
||||
private readonly Dictionary<uint, ushort> _readyGenerationByGuid = new();
|
||||
private readonly Dictionary<uint, ushort> _suspendedShadowGenerationByGuid = new();
|
||||
private readonly Dictionary<uint, ushort> _activePlacementGenerationByGuid = new();
|
||||
|
|
@ -38,20 +38,21 @@ public sealed class LiveEntityPresentationController : IDisposable
|
|||
LiveEntityRuntime liveEntities,
|
||||
ShadowObjectRegistry shadows,
|
||||
Func<uint, uint, float, bool> playTyped,
|
||||
LiveEntityPartArrayEnterWorldPort partArrayEnterWorld,
|
||||
Action<uint, bool>? setDirectChildrenNoDraw = null,
|
||||
Action<uint>? clearInvalidTarget = null,
|
||||
Func<(int X, int Y)>? liveCenter = null,
|
||||
Action<uint>? onShadowRestored = null,
|
||||
Action<uint>? handlePartArrayEnterWorld = null)
|
||||
Action<uint>? onShadowRestored = null)
|
||||
{
|
||||
_liveEntities = liveEntities ?? throw new ArgumentNullException(nameof(liveEntities));
|
||||
_shadows = shadows ?? throw new ArgumentNullException(nameof(shadows));
|
||||
_playTyped = playTyped ?? throw new ArgumentNullException(nameof(playTyped));
|
||||
_partArrayEnterWorld = partArrayEnterWorld
|
||||
?? throw new ArgumentNullException(nameof(partArrayEnterWorld));
|
||||
_setDirectChildrenNoDraw = setDirectChildrenNoDraw ?? ((_, _) => { });
|
||||
_clearInvalidTarget = clearInvalidTarget ?? (_ => { });
|
||||
_liveCenter = liveCenter ?? (() => (0, 0));
|
||||
_onShadowRestored = onShadowRestored;
|
||||
_handlePartArrayEnterWorld = handlePartArrayEnterWorld ?? (_ => { });
|
||||
_liveEntities.ProjectionVisibilityChanged += OnProjectionVisibilityChanged;
|
||||
}
|
||||
|
||||
|
|
@ -262,7 +263,7 @@ public sealed class LiveEntityPresentationController : IDisposable
|
|||
// timeline boundary: it strips link animations and
|
||||
// aborts every pending completion through
|
||||
// MotionTableManager::HandleEnterWorld @ 0x0051BDD0.
|
||||
_handlePartArrayEnterWorld(entity.Id);
|
||||
_partArrayEnterWorld.HandleEnterWorld(entity.Id);
|
||||
if (!IsCurrent(record, entity))
|
||||
return false;
|
||||
_clearInvalidTarget(record.ServerGuid);
|
||||
|
|
@ -279,7 +280,7 @@ public sealed class LiveEntityPresentationController : IDisposable
|
|||
return false;
|
||||
// Retail invokes the same PartArray boundary before
|
||||
// CObjCell::unhide_object restores cell visibility.
|
||||
_handlePartArrayEnterWorld(entity.Id);
|
||||
_partArrayEnterWorld.HandleEnterWorld(entity.Id);
|
||||
if (!IsCurrent(record, entity))
|
||||
return false;
|
||||
bool restored = !IsPlacementActive(record)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue