perf(streaming): cursor publication across frame budgets
This commit is contained in:
parent
bb16f74fd4
commit
98f1ac8934
32 changed files with 2215 additions and 823 deletions
|
|
@ -13,26 +13,46 @@ namespace AcDream.App.Streaming;
|
|||
/// </summary>
|
||||
public sealed class LandblockStaticPresentationPublication
|
||||
{
|
||||
private readonly IReadOnlyDictionary<uint, WorldEntity> _entities;
|
||||
private readonly IReadOnlyDictionary<uint, WorldEntitySnapshot> _snapshots;
|
||||
private readonly Dictionary<uint, WorldEntity> _entities;
|
||||
private readonly Dictionary<uint, WorldEntitySnapshot> _snapshots;
|
||||
private readonly uint[] _orderedPreviouslyActiveIds;
|
||||
private readonly Dictionary<uint, WorldEntitySnapshot> _replacementActive;
|
||||
|
||||
internal LandblockStaticPresentationPublication(
|
||||
object owner,
|
||||
LandblockPhysicsPublication physicsPublication,
|
||||
Dictionary<uint, WorldEntity> entities,
|
||||
Dictionary<uint, WorldEntitySnapshot> snapshots,
|
||||
HashSet<uint> previouslyActiveIds)
|
||||
IReadOnlyDictionary<uint, WorldEntitySnapshot> priorActive,
|
||||
HashSet<uint> previouslyActiveIds,
|
||||
uint[] orderedPreviouslyActiveIds)
|
||||
{
|
||||
Owner = owner;
|
||||
PhysicsPublication = physicsPublication;
|
||||
_entities = entities;
|
||||
_snapshots = snapshots;
|
||||
PriorActive = priorActive;
|
||||
PreviouslyActiveIds = previouslyActiveIds;
|
||||
_orderedPreviouslyActiveIds = orderedPreviouslyActiveIds;
|
||||
_replacementActive = new Dictionary<uint, WorldEntitySnapshot>(
|
||||
physicsPublication.Build.Landblock.Entities.Count);
|
||||
}
|
||||
|
||||
internal object Owner { get; }
|
||||
internal LandblockPhysicsPublication PhysicsPublication { get; }
|
||||
internal IReadOnlyDictionary<uint, WorldEntitySnapshot> PriorActive { get; }
|
||||
internal Dictionary<uint, WorldEntity> MutableEntities => _entities;
|
||||
internal Dictionary<uint, WorldEntitySnapshot> MutableSnapshots => _snapshots;
|
||||
internal HashSet<uint> PreviouslyActiveIds { get; }
|
||||
internal IReadOnlyList<uint> OrderedPreviouslyActiveIds =>
|
||||
_orderedPreviouslyActiveIds;
|
||||
internal IReadOnlyList<KeyValuePair<uint, WorldEntitySnapshot>>
|
||||
OrderedSnapshots { get; set; } =
|
||||
Array.Empty<KeyValuePair<uint, WorldEntitySnapshot>>();
|
||||
internal Dictionary<uint, WorldEntitySnapshot> ReplacementActive =>
|
||||
_replacementActive;
|
||||
internal int PreparationCursor { get; set; }
|
||||
internal bool PreparationCommitted { get; set; }
|
||||
internal int PriorCleanupCursor { get; set; }
|
||||
internal int PluginCursor { get; set; }
|
||||
internal bool BeginCommitted { get; set; }
|
||||
|
|
@ -116,6 +136,22 @@ public sealed class LandblockStaticPresentationPublisher
|
|||
|
||||
public LandblockStaticPresentationPublication PreparePublication(
|
||||
LandblockPhysicsPublication physicsPublication)
|
||||
{
|
||||
LandblockStaticPresentationPublication publication =
|
||||
CreatePublication(physicsPublication);
|
||||
while (!AdvancePreparationOne(publication))
|
||||
{
|
||||
}
|
||||
return publication;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Captures prior ownership without validating the replacement entity
|
||||
/// list. The concrete pipeline advances replacement validation one entity
|
||||
/// at a time before any render/physics owner mutates external state.
|
||||
/// </summary>
|
||||
internal LandblockStaticPresentationPublication CreatePublication(
|
||||
LandblockPhysicsPublication physicsPublication)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(physicsPublication);
|
||||
|
||||
|
|
@ -125,11 +161,43 @@ public sealed class LandblockStaticPresentationPublisher
|
|||
out Dictionary<uint, WorldEntitySnapshot>? active);
|
||||
var entities = new Dictionary<uint, WorldEntity>();
|
||||
var snapshots = new Dictionary<uint, WorldEntitySnapshot>();
|
||||
foreach (WorldEntity entity in physicsPublication.Build.Landblock.Entities)
|
||||
HashSet<uint> previous = active is not null
|
||||
? active.Keys.ToHashSet()
|
||||
: new HashSet<uint>();
|
||||
uint[] orderedPrevious = previous.Order().ToArray();
|
||||
return new LandblockStaticPresentationPublication(
|
||||
_receiptOwner,
|
||||
physicsPublication,
|
||||
entities,
|
||||
snapshots,
|
||||
active ?? new Dictionary<uint, WorldEntitySnapshot>(),
|
||||
previous,
|
||||
orderedPrevious);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates and snapshots one replacement entity. The final stable plugin
|
||||
/// order is materialized only after the cursor reaches the immutable tail.
|
||||
/// </summary>
|
||||
internal bool AdvancePreparationOne(
|
||||
LandblockStaticPresentationPublication publication)
|
||||
{
|
||||
ValidateReceipt(publication);
|
||||
if (publication.PreparationCommitted)
|
||||
return true;
|
||||
|
||||
IReadOnlyList<WorldEntity> source =
|
||||
publication.PhysicsPublication.Build.Landblock.Entities;
|
||||
uint canonical = Canonicalize(publication.LandblockId);
|
||||
if (publication.PreparationCursor < source.Count)
|
||||
{
|
||||
WorldEntity entity = source[publication.PreparationCursor];
|
||||
if (entity.ServerGuid != 0)
|
||||
continue;
|
||||
if (!entities.TryAdd(entity.Id, entity))
|
||||
{
|
||||
publication.PreparationCursor++;
|
||||
return false;
|
||||
}
|
||||
if (publication.Entities.ContainsKey(entity.Id))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Landblock 0x{canonical:X8} contains duplicate DAT-static ID " +
|
||||
|
|
@ -142,8 +210,9 @@ public sealed class LandblockStaticPresentationPublisher
|
|||
$"DAT-static ID 0x{entity.Id:X8} is already owned by " +
|
||||
$"landblock 0x{owner:X8}.");
|
||||
}
|
||||
if (active is not null
|
||||
&& active.TryGetValue(entity.Id, out WorldEntitySnapshot retained)
|
||||
if (publication.PriorActive.TryGetValue(
|
||||
entity.Id,
|
||||
out WorldEntitySnapshot retained)
|
||||
&& retained.SourceId != entity.SourceGfxObjOrSetupId)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
|
|
@ -152,18 +221,17 @@ public sealed class LandblockStaticPresentationPublisher
|
|||
$"0x{entity.SourceGfxObjOrSetupId:X8}.");
|
||||
}
|
||||
|
||||
snapshots.Add(entity.Id, Snapshot(entity));
|
||||
publication.MutableEntities.Add(entity.Id, entity);
|
||||
publication.MutableSnapshots.Add(entity.Id, Snapshot(entity));
|
||||
publication.PreparationCursor++;
|
||||
return false;
|
||||
}
|
||||
|
||||
HashSet<uint> previous = active is not null
|
||||
? active.Keys.ToHashSet()
|
||||
: new HashSet<uint>();
|
||||
return new LandblockStaticPresentationPublication(
|
||||
_receiptOwner,
|
||||
physicsPublication,
|
||||
entities,
|
||||
snapshots,
|
||||
previous);
|
||||
publication.OrderedSnapshots = publication.Snapshots
|
||||
.OrderBy(static pair => pair.Key)
|
||||
.ToArray();
|
||||
publication.PreparationCommitted = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -174,13 +242,31 @@ public sealed class LandblockStaticPresentationPublisher
|
|||
public void BeginPublication(LandblockStaticPresentationPublication publication)
|
||||
{
|
||||
ValidateReceipt(publication);
|
||||
if (publication.BeginCommitted)
|
||||
return;
|
||||
|
||||
uint[] priorIds = publication.PreviouslyActiveIds.Order().ToArray();
|
||||
while (publication.PriorCleanupCursor < priorIds.Length)
|
||||
if (!publication.PreparationCommitted)
|
||||
throw new InvalidOperationException(
|
||||
"Static presentation cannot begin before preparation commits.");
|
||||
while (!AdvanceBeginOne(publication))
|
||||
{
|
||||
uint id = priorIds[publication.PriorCleanupCursor];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Advances exactly one retained prior-owner cleanup. The cursor advances
|
||||
/// only after the complete owner operation succeeds, so a failure retries
|
||||
/// the same ID without rebuilding or reordering the work list.
|
||||
/// </summary>
|
||||
internal bool AdvanceBeginOne(
|
||||
LandblockStaticPresentationPublication publication)
|
||||
{
|
||||
ValidateReceipt(publication);
|
||||
if (publication.BeginCommitted)
|
||||
return true;
|
||||
|
||||
if (publication.PriorCleanupCursor
|
||||
< publication.OrderedPreviouslyActiveIds.Count)
|
||||
{
|
||||
uint id = publication.OrderedPreviouslyActiveIds[
|
||||
publication.PriorCleanupCursor];
|
||||
bool retained = publication.Entities.ContainsKey(id);
|
||||
_lighting.UnregisterOwner(id, forgetState: !retained);
|
||||
if (!retained)
|
||||
|
|
@ -192,10 +278,12 @@ public sealed class LandblockStaticPresentationPublisher
|
|||
_pluginRemovalCount++;
|
||||
}
|
||||
publication.PriorCleanupCursor++;
|
||||
return false;
|
||||
}
|
||||
|
||||
publication.BeginCommitted = true;
|
||||
_beginCount++;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -258,15 +346,32 @@ public sealed class LandblockStaticPresentationPublisher
|
|||
throw new InvalidOperationException(
|
||||
"Static presentation requires completed physics publication.");
|
||||
}
|
||||
if (publication.CompletionCommitted)
|
||||
return;
|
||||
|
||||
KeyValuePair<uint, WorldEntitySnapshot>[] snapshots = publication.Snapshots
|
||||
.OrderBy(pair => pair.Key)
|
||||
.ToArray();
|
||||
while (publication.PluginCursor < snapshots.Length)
|
||||
while (!AdvanceCompleteOne(publication))
|
||||
{
|
||||
(uint id, WorldEntitySnapshot snapshot) = snapshots[publication.PluginCursor];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Advances exactly one plugin projection, then atomically publishes the
|
||||
/// retained replacement dictionary after the cursor reaches its tail.
|
||||
/// </summary>
|
||||
internal bool AdvanceCompleteOne(
|
||||
LandblockStaticPresentationPublication publication)
|
||||
{
|
||||
ValidateReceipt(publication);
|
||||
if (!publication.BeginCommitted
|
||||
|| !publication.PhysicsPublication.CompletionCommitted)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Static presentation requires completed physics publication.");
|
||||
}
|
||||
if (publication.CompletionCommitted)
|
||||
return true;
|
||||
|
||||
if (publication.PluginCursor < publication.OrderedSnapshots.Count)
|
||||
{
|
||||
(uint id, WorldEntitySnapshot snapshot) =
|
||||
publication.OrderedSnapshots[publication.PluginCursor];
|
||||
_worldState.Add(snapshot);
|
||||
if (publication.PreviouslyActiveIds.Contains(id))
|
||||
{
|
||||
|
|
@ -279,21 +384,19 @@ public sealed class LandblockStaticPresentationPublisher
|
|||
_pluginSpawnCount++;
|
||||
}
|
||||
_landblockByEntityId[id] = Canonicalize(publication.LandblockId);
|
||||
publication.ReplacementActive[id] = snapshot;
|
||||
publication.PluginCursor++;
|
||||
return false;
|
||||
}
|
||||
|
||||
uint canonical = Canonicalize(publication.LandblockId);
|
||||
if (publication.Snapshots.Count == 0)
|
||||
{
|
||||
if (publication.ReplacementActive.Count == 0)
|
||||
_activeByLandblock.Remove(canonical);
|
||||
}
|
||||
else
|
||||
{
|
||||
_activeByLandblock[canonical] = publication.Snapshots
|
||||
.ToDictionary(pair => pair.Key, pair => pair.Value);
|
||||
}
|
||||
_activeByLandblock[canonical] = publication.ReplacementActive;
|
||||
publication.CompletionCommitted = true;
|
||||
_completeCount++;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void RemoveLighting(WorldEntity entity)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue