-
Notifications
You must be signed in to change notification settings - Fork 17
Expand file tree
/
Copy pathSceneByteProgressTracker.cs
More file actions
125 lines (109 loc) · 5.25 KB
/
Copy pathSceneByteProgressTracker.cs
File metadata and controls
125 lines (109 loc) · 5.25 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
using Arch.Core;
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Pool;
namespace ECS.SceneLifeCycle.Systems
{
/// <summary>
/// Byte-weighted progress accumulator for a scene's GLTF assets.
/// Tracks per-entity ContentLength and weights assets whose size was never observed
/// as <see cref="UNKNOWN_ASSET_BYTES"/> so they still contribute a fixed share to total progress.
/// </summary>
internal class SceneByteProgressTracker : IDisposable
{
// Flat weight for assets with no observed ContentLength (cache hits, HEAD failures).
// Kept at 1 byte so unknowns don't skew the bar away from real-download progress: cache hits
// are effectively free, just held by finalize budget, and shouldn't drag the percentage down.
private const long UNKNOWN_ASSET_BYTES = 1;
private readonly Dictionary<Entity, long> sizes;
private long completedBytes;
private long totalBytesExpected;
private int entitiesWithKnownSize;
// Per-frame accumulator: weighted bytes for entities still loading.
// Reset by ComputeAndClamp at frame end.
private long inProgressWeightedBytes;
// Byte-weighted math can dip when a small entity finishes while a new large unknown entity registers.
// Clamping locally avoids visual regression without affecting other AsyncLoadProcessReport callers (e.g. teleport retry).
private float maxReportedProgress;
public SceneByteProgressTracker()
{
sizes = DictionaryPool<Entity, long>.Get();
}
public void Dispose()
{
DictionaryPool<Entity, long>.Release(sizes);
}
/// <summary>
/// First sighting with known length: accumulate ContentLength into totalBytesExpected exactly once
/// and remember it so the finish path doesn't need to re-read the loading state.
/// Idempotent for already-tracked entities or unknown sizes.
/// </summary>
public void RegisterIfNew(Entity entity, long contentLength)
{
if (contentLength > 0 && sizes.TryAdd(entity, contentLength))
{
totalBytesExpected += contentLength;
entitiesWithKnownSize++;
}
}
/// <summary>
/// Clean finish: credit the entity's stored ContentLength from registration,
/// or <see cref="UNKNOWN_ASSET_BYTES"/> if its size was never observed.
/// </summary>
public void CreditFinish(Entity entity)
{
if (sizes.Remove(entity, out long contentLength))
completedBytes += contentLength;
else
completedBytes += UNKNOWN_ASSET_BYTES;
}
/// <summary>
/// Sudden death (no clean finish observed): credit the entity's registered ContentLength so
/// totalBytesExpected stays balanced. No-op if the entity was never registered.
/// </summary>
public void CreditDeath(Entity entity)
{
if (sizes.Remove(entity, out long contentLength))
completedBytes += contentLength;
}
/// <summary>
/// Accumulate weighted in-progress bytes for an entity that is still loading this frame.
/// </summary>
public void AccumulateInProgress(float entityProgress, long contentLength)
{
if (contentLength > 0)
inProgressWeightedBytes += (long)(entityProgress * contentLength);
}
/// <summary>
/// Returns this frame's progress value, guaranteed never to go down between frames.
/// Call once per frame: it also clears the per-frame in-progress accumulator.
/// </summary>
public float ComputeAndClamp(int totalAssetsToResolve, float deltaTime)
{
// Frame-rate independent smoothing: τ ≈ 111ms matches 0.15-per-tick at 60fps.
const float SMOOTHING_TIME_CONSTANT = 0.111f;
float target = Compute(totalAssetsToResolve);
inProgressWeightedBytes = 0;
if (target > maxReportedProgress)
{
float alpha = Mathf.Clamp01(deltaTime / SMOOTHING_TIME_CONSTANT);
maxReportedProgress = Mathf.Lerp(maxReportedProgress, target, alpha);
}
return maxReportedProgress;
}
// Cap below 1.0 because AsyncLoadProcessReport.SetProgress(>=1f) auto-resolves its completion source,
// which closes the loading screen. In-progress credits can saturate effectiveTotal during the finalize-wait
// window (entities are downloaded but still in LoadingState.Loading); only the explicit conclude path
// in GatherGltfAssetsSystem should report 1.0.
private const float MAX_IN_PROGRESS = 0.99f;
private float Compute(int totalAssetsToResolve)
{
int unknownCount = Math.Max(0, totalAssetsToResolve - entitiesWithKnownSize);
long effectiveTotal = totalBytesExpected + (UNKNOWN_ASSET_BYTES * unknownCount);
return effectiveTotal > 0
? Mathf.Min(MAX_IN_PROGRESS, (float)(completedBytes + inProgressWeightedBytes) / effectiveTotal)
: 0f;
}
}
}