feat(streaming): establish cost budget ledger
Add the validated frame-work profile, deterministic completion charges, and shadow admission meter before enforcing the scheduler in Slice E2. Lifecycle artifacts now expose elapsed work, would-yield limits, backlog bytes/age, and pending owner ledgers without changing accepted execution. Tests: dotnet build AcDream.slnx -c Release --no-restore; dotnet test AcDream.slnx -c Release --no-build --no-restore (8124 passed, 5 skipped)
This commit is contained in:
parent
2945896a6f
commit
ac45cb1bd7
14 changed files with 1280 additions and 71 deletions
187
src/AcDream.App/Streaming/LandblockStreamResultCost.cs
Normal file
187
src/AcDream.App/Streaming/LandblockStreamResultCost.cs
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
using AcDream.App.Rendering.Wb;
|
||||
using AcDream.Core.Terrain;
|
||||
using AcDream.Core.World;
|
||||
|
||||
namespace AcDream.App.Streaming;
|
||||
|
||||
/// <summary>
|
||||
/// Deterministic scheduling charge derived from one immutable worker result.
|
||||
/// It is not a claim about CLR object-header or allocator-bucket size: those
|
||||
/// implementation details are neither stable nor observable without walking
|
||||
/// the managed heap. Exact array payloads and logical retained entries are
|
||||
/// charged consistently so admission has a deterministic unit.
|
||||
/// </summary>
|
||||
public readonly record struct LandblockStreamCostEstimate(
|
||||
StreamingWorkCost Work,
|
||||
long TerrainPayloadBytes,
|
||||
int Entities,
|
||||
int MeshReferences,
|
||||
int VisibilityCells,
|
||||
int EnvCellShells,
|
||||
int PortalConnections,
|
||||
int PortalPolygonVertices,
|
||||
int PhysicsEnvCells,
|
||||
int PhysicsEnvironments,
|
||||
int PhysicsSetups,
|
||||
int PhysicsGfxObjects);
|
||||
|
||||
public static class LandblockStreamResultCost
|
||||
{
|
||||
private const int ReferenceChargeBytes = 8;
|
||||
private const int DictionaryEntryChargeBytes = 16;
|
||||
|
||||
public static LandblockStreamCostEstimate Estimate(
|
||||
LandblockStreamResult result)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(result);
|
||||
return result switch
|
||||
{
|
||||
LandblockStreamResult.Loaded loaded =>
|
||||
Estimate(loaded.Build, loaded.MeshData),
|
||||
LandblockStreamResult.Promoted promoted =>
|
||||
Estimate(promoted.Build, promoted.MeshData),
|
||||
_ => new LandblockStreamCostEstimate(
|
||||
new StreamingWorkCost(CompletionAdmissions: 1),
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0),
|
||||
};
|
||||
}
|
||||
|
||||
public static LandblockStreamCostEstimate Estimate(
|
||||
LandblockBuild build,
|
||||
LandblockMeshData meshData)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(build);
|
||||
ArgumentNullException.ThrowIfNull(meshData);
|
||||
|
||||
long terrainBytes = Add(
|
||||
Multiply(meshData.Vertices.LongLength, Unsafe.SizeOf<TerrainVertex>()),
|
||||
Multiply(meshData.Indices.LongLength, sizeof(uint)));
|
||||
long retainedBytes = terrainBytes;
|
||||
|
||||
IReadOnlyList<WorldEntity> entities = build.Landblock.Entities;
|
||||
int meshReferences = 0;
|
||||
retainedBytes = Add(
|
||||
retainedBytes,
|
||||
Multiply(entities.Count, ReferenceChargeBytes));
|
||||
for (int i = 0; i < entities.Count; i++)
|
||||
{
|
||||
int count = entities[i].MeshRefs.Count;
|
||||
meshReferences = SaturatingAdd(meshReferences, count);
|
||||
retainedBytes = Add(
|
||||
retainedBytes,
|
||||
Multiply(count, Unsafe.SizeOf<MeshRef>()));
|
||||
}
|
||||
|
||||
int visibilityCells = 0;
|
||||
int shells = 0;
|
||||
int portalConnections = 0;
|
||||
int portalPolygonVertices = 0;
|
||||
if (build.EnvCells is { } envCells)
|
||||
{
|
||||
visibilityCells = envCells.VisibilityCells.Length;
|
||||
shells = envCells.Shells.Length;
|
||||
retainedBytes = Add(
|
||||
retainedBytes,
|
||||
Multiply(
|
||||
(long)visibilityCells + shells,
|
||||
ReferenceChargeBytes));
|
||||
|
||||
foreach (var cell in envCells.VisibilityCells)
|
||||
{
|
||||
portalConnections = SaturatingAdd(
|
||||
portalConnections,
|
||||
cell.Portals.Count);
|
||||
retainedBytes = Add(
|
||||
retainedBytes,
|
||||
Multiply(
|
||||
cell.Portals.Count,
|
||||
Unsafe.SizeOf<Rendering.CellPortalInfo>()));
|
||||
retainedBytes = Add(
|
||||
retainedBytes,
|
||||
Multiply(
|
||||
cell.ClipPlanes.Count,
|
||||
Unsafe.SizeOf<Rendering.PortalClipPlane>()));
|
||||
retainedBytes = Add(
|
||||
retainedBytes,
|
||||
Multiply(
|
||||
cell.VisibleCells.Count,
|
||||
sizeof(uint)));
|
||||
|
||||
foreach (System.Numerics.Vector3[] polygon in cell.PortalPolygons)
|
||||
{
|
||||
portalPolygonVertices = SaturatingAdd(
|
||||
portalPolygonVertices,
|
||||
polygon.Length);
|
||||
retainedBytes = Add(
|
||||
retainedBytes,
|
||||
Multiply(
|
||||
polygon.LongLength,
|
||||
Unsafe.SizeOf<System.Numerics.Vector3>()));
|
||||
}
|
||||
}
|
||||
|
||||
foreach (EnvCellShellPlacement shell in envCells.Shells)
|
||||
{
|
||||
retainedBytes = Add(
|
||||
retainedBytes,
|
||||
Multiply(shell.Surfaces.Length, sizeof(ushort)));
|
||||
}
|
||||
}
|
||||
|
||||
PhysicsDatBundle physics =
|
||||
build.Landblock.PhysicsDats ?? PhysicsDatBundle.Empty;
|
||||
int physicsEnvCells = physics.EnvCells.Count;
|
||||
int physicsEnvironments = physics.Environments.Count;
|
||||
int physicsSetups = physics.Setups.Count;
|
||||
int physicsGfxObjects = physics.GfxObjs.Count;
|
||||
long physicsEntries = (long)physicsEnvCells
|
||||
+ physicsEnvironments
|
||||
+ physicsSetups
|
||||
+ physicsGfxObjects;
|
||||
retainedBytes = Add(
|
||||
retainedBytes,
|
||||
Multiply(physicsEntries, DictionaryEntryChargeBytes));
|
||||
|
||||
return new LandblockStreamCostEstimate(
|
||||
new StreamingWorkCost(
|
||||
CompletionAdmissions: 1,
|
||||
AdoptedCpuBytes: retainedBytes,
|
||||
EntityOperations: entities.Count,
|
||||
GpuUploadBytes: terrainBytes),
|
||||
terrainBytes,
|
||||
entities.Count,
|
||||
meshReferences,
|
||||
visibilityCells,
|
||||
shells,
|
||||
portalConnections,
|
||||
portalPolygonVertices,
|
||||
physicsEnvCells,
|
||||
physicsEnvironments,
|
||||
physicsSetups,
|
||||
physicsGfxObjects);
|
||||
}
|
||||
|
||||
private static long Multiply(long count, int elementBytes) =>
|
||||
count <= 0
|
||||
? 0
|
||||
: count > long.MaxValue / elementBytes
|
||||
? long.MaxValue
|
||||
: count * elementBytes;
|
||||
|
||||
private static long Add(long left, long right) =>
|
||||
left > long.MaxValue - right ? long.MaxValue : left + right;
|
||||
|
||||
private static int SaturatingAdd(int left, int right) =>
|
||||
left > int.MaxValue - right ? int.MaxValue : left + right;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue