refactor(runtime): own canonical entity and object lifetime
Introduce one presentation-free RuntimeEntityObjectLifetime for the exact entity directory and ClientObjectTable. Make GameWindow, graphical projections, retained UI, interaction, session routing, create/delete integration, and reset borrow that owner while preserving synchronous retail ordering, dormant retention, and retry semantics. Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
parent
d9bf4c4960
commit
5ef8b5371d
40 changed files with 712 additions and 170 deletions
135
src/AcDream.Runtime/Entities/RuntimeEntityObjectLifetime.cs
Normal file
135
src/AcDream.Runtime/Entities/RuntimeEntityObjectLifetime.cs
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
using AcDream.Core.Items;
|
||||
using AcDream.Core.Net;
|
||||
using AcDream.Core.Net.Messages;
|
||||
|
||||
namespace AcDream.Runtime.Entities;
|
||||
|
||||
/// <summary>
|
||||
/// One exact DeleteObject acceptance issued by a
|
||||
/// <see cref="RuntimeEntityObjectLifetime"/>. The graphical host retires the
|
||||
/// active identity before returning this token to the Runtime owner for the
|
||||
/// retained-object mutation, preserving retail callback order.
|
||||
/// </summary>
|
||||
public sealed class RuntimeEntityDeleteAcceptance
|
||||
{
|
||||
internal RuntimeEntityDeleteAcceptance(
|
||||
RuntimeEntityObjectLifetime owner,
|
||||
DeleteObject.Parsed delete)
|
||||
{
|
||||
Owner = owner;
|
||||
Delete = delete;
|
||||
}
|
||||
|
||||
internal RuntimeEntityObjectLifetime Owner { get; }
|
||||
internal bool Completed { get; set; }
|
||||
public DeleteObject.Parsed Delete { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Presentation-free ownership root for one session's canonical entity and
|
||||
/// retained object lifetimes. Graphical and direct hosts borrow these exact
|
||||
/// instances; they never allocate a second directory or object table.
|
||||
/// </summary>
|
||||
public sealed class RuntimeEntityObjectLifetime
|
||||
{
|
||||
public RuntimeEntityObjectLifetime(
|
||||
uint firstLocalEntityId = RuntimeEntityDirectory.FirstLocalEntityId)
|
||||
{
|
||||
Entities = new RuntimeEntityDirectory(firstLocalEntityId);
|
||||
Objects = new ClientObjectTable();
|
||||
}
|
||||
|
||||
public RuntimeEntityDirectory Entities { get; }
|
||||
public ClientObjectTable Objects { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Applies the retained-object half of an accepted CreateObject while the
|
||||
/// exact canonical incarnation and its integration version remain current.
|
||||
/// Object-table callbacks are synchronous and may re-enter entity lifetime,
|
||||
/// so the acceptance predicate is checked before, during, and after apply.
|
||||
/// </summary>
|
||||
public bool ApplyAcceptedSpawn(
|
||||
RuntimeEntityRecord canonical,
|
||||
ulong expectedCreateIntegrationVersion,
|
||||
WorldSession.EntitySpawn spawn,
|
||||
bool replaceGeneration)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(canonical);
|
||||
if (canonical.ServerGuid != spawn.Guid
|
||||
|| canonical.Incarnation != spawn.InstanceSequence)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsCurrent() =>
|
||||
Entities.IsCurrent(canonical)
|
||||
&& canonical.CreateIntegrationVersion
|
||||
== expectedCreateIntegrationVersion;
|
||||
|
||||
return IsCurrent()
|
||||
&& ObjectTableWiring.ApplyEntitySpawn(
|
||||
Objects,
|
||||
spawn,
|
||||
replaceGeneration,
|
||||
IsCurrent)
|
||||
&& IsCurrent();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Accepts the exact DeleteObject generation without yet publishing the
|
||||
/// retained-object removal. This split lets the graphical host retire the
|
||||
/// active identity first, matching the established reentrant callback
|
||||
/// order, while Runtime remains the only freshness authority.
|
||||
/// </summary>
|
||||
public bool TryAcceptDelete(
|
||||
DeleteObject.Parsed delete,
|
||||
bool isLocalPlayer,
|
||||
out RuntimeEntityDeleteAcceptance acceptance)
|
||||
{
|
||||
if (!Entities.TryDelete(delete, isLocalPlayer))
|
||||
{
|
||||
acceptance = null!;
|
||||
return false;
|
||||
}
|
||||
|
||||
acceptance = new RuntimeEntityDeleteAcceptance(this, delete);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Completes a previously accepted delete exactly once. Marking the token
|
||||
/// complete before synchronous callbacks preserves the current retry rule:
|
||||
/// a throwing observer does not replay an already-committed removal.
|
||||
/// </summary>
|
||||
public void CompleteAcceptedDelete(
|
||||
RuntimeEntityDeleteAcceptance acceptance)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(acceptance);
|
||||
if (!ReferenceEquals(acceptance.Owner, this))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"A delete acceptance belongs to a different Runtime lifetime.");
|
||||
}
|
||||
if (acceptance.Completed)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"A delete acceptance has already been completed.");
|
||||
}
|
||||
|
||||
acceptance.Completed = true;
|
||||
ObjectTableWiring.ApplyEntityDelete(Objects, acceptance.Delete);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies a delete accepted by the dormant exact-incarnation owner after
|
||||
/// the active Runtime directory correctly reports no live record.
|
||||
/// </summary>
|
||||
public void ApplyAcceptedDormantDelete(DeleteObject.Parsed delete) =>
|
||||
ObjectTableWiring.ApplyEntityDelete(Objects, delete);
|
||||
|
||||
/// <summary>
|
||||
/// Clears retained object state at the Runtime-owned reset stage. App
|
||||
/// projection teardown remains a later acknowledged stage.
|
||||
/// </summary>
|
||||
public void ClearObjects() => Objects.Clear();
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue