Revert "fix(world): AP-48 client-side visibility cull (FPS sink across portal-hops)"
This reverts commit e5b2d15b63.
This commit is contained in:
parent
e5b2d15b63
commit
8fbde99441
4 changed files with 1 additions and 268 deletions
|
|
@ -840,13 +840,6 @@ public sealed class GameWindow : IDisposable
|
|||
/// keys the render list; this parallel dictionary keys by server guid.
|
||||
/// </summary>
|
||||
private readonly Dictionary<uint, AcDream.Core.World.WorldEntity> _entitiesByServerGuid = new();
|
||||
|
||||
// AP-48: client-side visibility cull (holtburger liveness.rs port) — prunes world
|
||||
// entities the player left far behind so the tables don't accumulate every town's
|
||||
// objects across portal-hops (which sank FPS). 1 Hz; the player + held/equipped items
|
||||
// are excluded (see TickEntityCull).
|
||||
private readonly AcDream.App.World.EntityVisibilityCuller _entityCuller = new();
|
||||
private double _cullAccum;
|
||||
/// <summary>
|
||||
/// Latest <see cref="AcDream.Core.Net.WorldSession.EntitySpawn"/> for each
|
||||
/// guid. Captured at the end of <see cref="OnLiveEntitySpawnedLocked"/> so
|
||||
|
|
@ -3835,61 +3828,8 @@ public sealed class GameWindow : IDisposable
|
|||
}
|
||||
}
|
||||
|
||||
// AP-48 cull wiring (logic lives in EntityVisibilityCuller). Snapshots the world
|
||||
// entities + player, asks the culler who has been >384m AND outside the 3×3 landblock
|
||||
// neighborhood for 25s, and tears each down through the existing pruner. Throttled to
|
||||
// 1 Hz (holtburger runs it per world tick; per-frame is wasteful). Skipped during a
|
||||
// teleport hold (the player WorldEntity.Position is frozen). Held/equipped/contained
|
||||
// items never enter _entitiesByServerGuid (OnLiveEntitySpawnedLocked early-returns on a
|
||||
// null world position), so the cull is structurally incapable of touching them; the
|
||||
// ClientObjectTable weenie row is dropped only for genuinely world-placed objects.
|
||||
private void TickEntityCull(double dt)
|
||||
{
|
||||
_cullAccum += dt;
|
||||
if (_cullAccum < 1.0) return;
|
||||
_cullAccum = 0.0;
|
||||
|
||||
if (_playerController is { State: AcDream.App.Input.PlayerState.PortalSpace }) return;
|
||||
if (!_entitiesByServerGuid.TryGetValue(_playerServerGuid, out var pe)) return;
|
||||
|
||||
uint plb = pe.ParentCellId ?? 0u;
|
||||
var player = new AcDream.App.World.EntityCullInfo(
|
||||
_playerServerGuid, pe.Position,
|
||||
(int)((plb >> 24) & 0xFFu), (int)((plb >> 16) & 0xFFu), plb != 0u);
|
||||
|
||||
// Snapshot first (the eviction below mutates _entitiesByServerGuid).
|
||||
var snapshot = new System.Collections.Generic.List<AcDream.App.World.EntityCullInfo>(
|
||||
_entitiesByServerGuid.Count);
|
||||
foreach (var kv in _entitiesByServerGuid)
|
||||
{
|
||||
uint elb = kv.Value.ParentCellId ?? 0u;
|
||||
snapshot.Add(new AcDream.App.World.EntityCullInfo(
|
||||
kv.Key, kv.Value.Position,
|
||||
(int)((elb >> 24) & 0xFFu), (int)((elb >> 16) & 0xFFu), elb != 0u));
|
||||
}
|
||||
|
||||
double now = (System.DateTime.UtcNow - System.DateTime.UnixEpoch).TotalSeconds;
|
||||
var evicted = _entityCuller.Tick(snapshot, player, now);
|
||||
foreach (var guid in evicted)
|
||||
{
|
||||
var row = Objects.Get(guid);
|
||||
RemoveLiveEntityByServerGuid(guid);
|
||||
// has_nonworld_retention parity: keep the weenie row if it's held/equipped (a far
|
||||
// ground item open in a container view); drop it only for world-placed objects.
|
||||
if (row is null || (row.ContainerId == 0u && row.WielderId == 0u))
|
||||
Objects.Remove(guid);
|
||||
}
|
||||
}
|
||||
|
||||
private bool RemoveLiveEntityByServerGuid(uint serverGuid)
|
||||
{
|
||||
// AP-48: clear any armed cull deadline for this guid BEFORE the early-return, so a
|
||||
// guid that was culled (and is no longer in _entitiesByServerGuid) but is then
|
||||
// re-created by the server starts fresh — never inherits a stale, already-expired
|
||||
// deadline that would evict it on the next cull tick. Mirrors holtburger's
|
||||
// clear-on-upsert/delete.
|
||||
_entityCuller.Forget(serverGuid);
|
||||
|
||||
if (!_entitiesByServerGuid.TryGetValue(serverGuid, out var existingEntity))
|
||||
return false;
|
||||
|
||||
|
|
@ -7644,10 +7584,6 @@ public sealed class GameWindow : IDisposable
|
|||
// Step 2: routed through the controller; functionally identical.
|
||||
_liveSessionController?.Tick();
|
||||
|
||||
// AP-48: 1 Hz visibility cull. Runs after the live-session drain so the player's
|
||||
// WorldEntity.Position/ParentCellId reflect this frame's UpdatePosition.
|
||||
TickEntityCull(dt);
|
||||
|
||||
// Retail teleport transit. Runs AFTER streaming (which priority-applies the
|
||||
// destination landblock this frame) and the live-session drain, so a destination
|
||||
// that became resident this frame materializes the same frame. The TAS holds at
|
||||
|
|
|
|||
|
|
@ -1,94 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
|
||||
namespace AcDream.App.World;
|
||||
|
||||
/// <summary>Per-entity world-position + landblock snapshot fed to the culler.</summary>
|
||||
public readonly record struct EntityCullInfo(uint Guid, Vector3 Pos, int LbX, int LbY, bool HasLb);
|
||||
|
||||
/// <summary>
|
||||
/// AP-48 fix: a client-side visibility cull that prunes world entities the player has left
|
||||
/// far behind, so the entity tables don't accumulate every object from every town visited
|
||||
/// (which sank FPS the more the player portal-hopped). Faithful port of holtburger's
|
||||
/// <c>liveness.rs</c> (<c>crates/holtburger-world/src/state/liveness.rs</c>): an entity is
|
||||
/// "conservatively visible" if it's in the 3×3 landblock neighborhood OR within 384 m of the
|
||||
/// player; it is evicted only after being continuously OUT of visibility for 25 s
|
||||
/// (arm-on-leave / clear-on-return). The 25 s is time-since-it-left-visibility, NOT
|
||||
/// time-since-last-update.
|
||||
///
|
||||
/// <para>This class is pure: it owns only the per-guid prune deadlines and decides WHO to
|
||||
/// evict; the caller (GameWindow) supplies the entity snapshots and performs the actual
|
||||
/// teardown through its existing pruner. The local player and any held/equipped/contained
|
||||
/// item are excluded by the caller (the player by guid; inventory items never have a world
|
||||
/// position so they never appear in the snapshot).</para>
|
||||
/// </summary>
|
||||
public sealed class EntityVisibilityCuller
|
||||
{
|
||||
/// <summary>holtburger <c>CONSERVATIVE_VISIBILITY_DISTANCE_M</c> (liveness.rs:12).</summary>
|
||||
public const double VisibilityDistanceM = 384.0;
|
||||
|
||||
/// <summary>holtburger <c>ACE_DESTRUCTION_TIMEOUT_SECS</c> (liveness.rs:11).</summary>
|
||||
public const double DestructionTimeoutSec = 25.0;
|
||||
|
||||
private readonly Dictionary<uint, double> _deadline = new();
|
||||
|
||||
/// <summary>Number of entities currently armed for eviction (diagnostic).</summary>
|
||||
public int ArmedCount => _deadline.Count;
|
||||
|
||||
/// <summary>
|
||||
/// "Conservatively visible": in the 3×3 landblock neighborhood (own + adjacent landblocks)
|
||||
/// OR within <see cref="VisibilityDistanceM"/> of the player. The landblock test uses the
|
||||
/// absolute landblock id (frame-independent), so a streaming recenter on teleport doesn't
|
||||
/// false-positive a near entity whose offset position is momentarily stale.
|
||||
/// </summary>
|
||||
public static bool IsVisible(
|
||||
Vector3 entityPos, int eLbX, int eLbY, bool entityHasLb,
|
||||
Vector3 playerPos, int pLbX, int pLbY)
|
||||
{
|
||||
bool nearLb = entityHasLb && Math.Abs(eLbX - pLbX) <= 1 && Math.Abs(eLbY - pLbY) <= 1;
|
||||
return nearLb
|
||||
|| Vector3.DistanceSquared(entityPos, playerPos)
|
||||
<= VisibilityDistanceM * VisibilityDistanceM;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pure per-entity state transition. Given current visibility, the entity's current
|
||||
/// prune deadline (null = none armed) and the current time, returns the new deadline
|
||||
/// (null = cleared) and whether to evict now. Visible → clear + keep; not-visible +
|
||||
/// unarmed → arm at now+25s; not-visible + armed-and-expired → evict; not-visible +
|
||||
/// armed-not-expired → keep.
|
||||
/// </summary>
|
||||
public static (double? newDeadline, bool evict) Step(bool visible, double? deadline, double now)
|
||||
{
|
||||
if (visible) return (null, false);
|
||||
if (deadline is null) return (now + DestructionTimeoutSec, false);
|
||||
if (deadline.Value <= now) return (null, true);
|
||||
return (deadline, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Advance the cull over the current world entities and return the guids to evict. The
|
||||
/// caller must skip the local player BEFORE calling (or rely on the per-guid
|
||||
/// <paramref name="playerGuid"/> filter here) and perform the teardown for each returned
|
||||
/// guid. Mutates only the internal deadline map; does not touch the entities.
|
||||
/// </summary>
|
||||
public IReadOnlyList<uint> Tick(IEnumerable<EntityCullInfo> entities, EntityCullInfo player, double now)
|
||||
{
|
||||
List<uint>? evict = null;
|
||||
foreach (var e in entities)
|
||||
{
|
||||
if (e.Guid == player.Guid) continue; // never cull the local player
|
||||
bool visible = IsVisible(e.Pos, e.LbX, e.LbY, e.HasLb, player.Pos, player.LbX, player.LbY);
|
||||
double? current = _deadline.TryGetValue(e.Guid, out var d) ? d : null;
|
||||
var (newDeadline, doEvict) = Step(visible, current, now);
|
||||
if (newDeadline is { } nd) _deadline[e.Guid] = nd; else _deadline.Remove(e.Guid);
|
||||
if (doEvict) (evict ??= new List<uint>()).Add(e.Guid);
|
||||
}
|
||||
return (IReadOnlyList<uint>?)evict ?? Array.Empty<uint>();
|
||||
}
|
||||
|
||||
/// <summary>Drop any armed deadline for a guid (e.g. when it's explicitly deleted or
|
||||
/// re-created by the server, matching holtburger's clear-on-upsert).</summary>
|
||||
public void Forget(uint guid) => _deadline.Remove(guid);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue