using AcDream.App.Input; using AcDream.Core.Net; using AcDream.Core.Net.Messages; using AcDream.Runtime.Entities; namespace AcDream.App.World; internal interface ILiveEntityPruneSink { bool Prune(LiveEntityPruneCandidate candidate); } /// /// Owns authoritative and expiry-driven live-object deletion through one /// generation-safe runtime transaction. /// internal sealed class LiveEntityDeletionController : ILiveEntityPruneSink { private readonly LiveEntityRuntime _runtime; private readonly RuntimeEntityObjectLifetime _entityObjects; private readonly ILiveEntityTeardownCoordinator _teardown; private readonly ILocalPlayerIdentitySource _identity; private readonly DormantLiveEntityStore _dormant; private readonly Action? _diagnostic; public LiveEntityDeletionController( LiveEntityRuntime runtime, RuntimeEntityObjectLifetime entityObjects, ILiveEntityTeardownCoordinator teardown, ILocalPlayerIdentitySource identity, DormantLiveEntityStore? dormant = null, Action? diagnostic = null) { _runtime = runtime ?? throw new ArgumentNullException(nameof(runtime)); _entityObjects = entityObjects ?? throw new ArgumentNullException(nameof(entityObjects)); _teardown = teardown ?? throw new ArgumentNullException(nameof(teardown)); _identity = identity ?? throw new ArgumentNullException(nameof(identity)); _dormant = dormant ?? new DormantLiveEntityStore(); _diagnostic = diagnostic; } /// /// Retail SmartBox::HandleDeleteObject @ 0x00451EA0 rejects the /// player, requires the exact Instance timestamp, then performs one /// symmetric logical teardown. /// public bool Delete(DeleteObject.Parsed delete) { if (delete.Guid == _identity.ServerGuid) return false; bool hasActiveRecord = _runtime.TryGetRecord(delete.Guid, out _); if (!hasActiveRecord) _teardown.ForgetUnknownOwner(delete.Guid); bool removed = _runtime.UnregisterLiveEntity( delete, isLocalPlayer: false, removeRetainedObject: true); bool removedDormant = _dormant.RemoveExact(delete); if (!removed && removedDormant) _entityObjects.ApplyAcceptedDormantDelete(delete); if (removed) { _dormant.RemoveExact(delete); _diagnostic?.Invoke( $"live: delete guid=0x{delete.Guid:X8} instSeq={delete.InstanceSequence}"); } else if (removedDormant) { _diagnostic?.Invoke( $"live: delete dormant guid=0x{delete.Guid:X8} instSeq={delete.InstanceSequence}"); } return removed || removedDormant; } public bool Prune(LiveEntityPruneCandidate candidate) { if (!_runtime.TryGetRecord( candidate.Key, out LiveEntityRecord record) || record.ServerGuid != candidate.ServerGuid) { return false; } WorldSession.EntitySpawn snapshot = record.Snapshot; bool removed = _runtime.UnregisterLiveEntity( new DeleteObject.Parsed( candidate.ServerGuid, candidate.Generation), isLocalPlayer: false); if (!removed) return false; _dormant.Retain(snapshot); _diagnostic?.Invoke( $"live: dormant guid=0x{candidate.ServerGuid:X8} instSeq={candidate.Generation}"); return true; } }