fix(portal): synchronize destination presentation state

This commit is contained in:
Erik 2026-07-16 21:17:13 +02:00
parent 4b1bceefbb
commit e95f55f25b
42 changed files with 2815 additions and 288 deletions

View file

@ -0,0 +1,117 @@
using System.Collections.Concurrent;
using AcDream.Content;
namespace AcDream.App.Rendering.Wb;
/// <summary>
/// Deduplicated CPU-to-GPU staging queue. One object id may be queued or
/// in-flight at a time; a retry retains ownership until upload succeeds or
/// exhausts its retry budget.
/// </summary>
internal sealed class MeshUploadStagingQueue
{
private readonly ConcurrentQueue<ObjectMeshData> _queue = new();
private readonly ConcurrentDictionary<ulong, byte> _ownedIds = new();
public bool Stage(ObjectMeshData data)
{
if (!_ownedIds.TryAdd(data.ObjectId, 0))
return false;
data.UploadAttempts = 0;
_queue.Enqueue(data);
return true;
}
public bool TryDequeue(out ObjectMeshData? data) => _queue.TryDequeue(out data);
public void Requeue(ObjectMeshData data) => _queue.Enqueue(data);
public void Complete(ulong objectId) => _ownedIds.TryRemove(objectId, out _);
}
/// <summary>
/// Logical render-data ownership, deliberately independent from GPU upload
/// existence. Completing or repeating an upload never manufactures an owner;
/// only a live entity/landblock pin changes this count.
/// </summary>
internal sealed class MeshOwnershipCounter
{
private readonly ConcurrentDictionary<ulong, int> _counts = new();
public int Acquire(ulong id) => _counts.AddOrUpdate(id, 1, (_, count) => count + 1);
public int Release(ulong id) => _counts.AddOrUpdate(id, 0, (_, count) => Math.Max(0, count - 1));
public int Count(ulong id) => _counts.TryGetValue(id, out int count) ? count : 0;
public bool IsOwned(ulong id) => Count(id) > 0;
/// <summary>
/// Reports whether freshly uploaded data has a live owner. This is a query,
/// not an acquire: upload existence and logical ownership are independent.
/// </summary>
public bool MarkUploadComplete(ulong id) => IsOwned(id);
public bool Remove(ulong id) => _counts.TryRemove(id, out _);
}
/// <summary>
/// Bounded prepared-mesh cache. A cache hit is not merely a CPU return value:
/// when GPU render data is absent it re-stages that mesh for upload.
/// </summary>
internal sealed class CpuMeshUploadCache
{
private readonly Dictionary<ulong, ObjectMeshData> _data = new();
private readonly LinkedList<ulong> _lru = new();
private readonly int _capacity;
public CpuMeshUploadCache(int capacity)
{
if (capacity <= 0)
throw new ArgumentOutOfRangeException(nameof(capacity));
_capacity = capacity;
}
public bool TryGetAndStage(
ulong id,
MeshUploadStagingQueue staging,
out ObjectMeshData? data)
{
lock (_data)
{
if (!_data.TryGetValue(id, out data))
return false;
_lru.Remove(id);
_lru.AddLast(id);
staging.Stage(data);
return true;
}
}
public void Store(ObjectMeshData data)
{
lock (_data)
{
if (!_data.ContainsKey(data.ObjectId) && _data.Count >= _capacity)
{
ulong oldest = _lru.First!.Value;
_lru.RemoveFirst();
_data.Remove(oldest);
}
_data[data.ObjectId] = data;
_lru.Remove(data.ObjectId);
_lru.AddLast(data.ObjectId);
}
}
public void Clear()
{
lock (_data)
{
_data.Clear();
_lru.Clear();
}
}
}