feat(launcher): stabilize prepared content updates
This commit is contained in:
parent
f160f3fee1
commit
af9327a17b
42 changed files with 3706 additions and 147 deletions
239
src/AcDream.Content/LayeredPreparedAssetSource.cs
Normal file
239
src/AcDream.Content/LayeredPreparedAssetSource.cs
Normal file
|
|
@ -0,0 +1,239 @@
|
|||
using AcDream.Content.Pak;
|
||||
using AcDream.Core.Physics;
|
||||
|
||||
namespace AcDream.Content;
|
||||
|
||||
/// <summary>
|
||||
/// One cumulative overlay in front of one complete base package. Missing keys
|
||||
/// fall through; an overlay key that exists but is corrupt is authoritative and
|
||||
/// never hides its corruption behind older base bytes. Render and collision
|
||||
/// payloads use the exact same rule and the two package owners are disposed as
|
||||
/// one content set.
|
||||
/// </summary>
|
||||
public sealed class LayeredPreparedAssetSource :
|
||||
IPreparedAssetSource,
|
||||
IPreparedCollisionSource
|
||||
{
|
||||
private IPreparedAssetSource? _baseAssets;
|
||||
private IPreparedAssetSource? _overlayAssets;
|
||||
private IPreparedCollisionSource? _baseCollision;
|
||||
private IPreparedCollisionSource? _overlayCollision;
|
||||
|
||||
public LayeredPreparedAssetSource(
|
||||
IPreparedAssetSource baseSource,
|
||||
IPreparedAssetSource overlaySource)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(baseSource);
|
||||
ArgumentNullException.ThrowIfNull(overlaySource);
|
||||
if (ReferenceEquals(baseSource, overlaySource))
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"The base and overlay must have independent owners.",
|
||||
nameof(overlaySource));
|
||||
}
|
||||
|
||||
_baseCollision = baseSource as IPreparedCollisionSource
|
||||
?? throw new ArgumentException(
|
||||
"The base source must expose prepared collision payloads.",
|
||||
nameof(baseSource));
|
||||
_overlayCollision = overlaySource as IPreparedCollisionSource
|
||||
?? throw new ArgumentException(
|
||||
"The overlay source must expose prepared collision payloads.",
|
||||
nameof(overlaySource));
|
||||
_baseAssets = baseSource;
|
||||
_overlayAssets = overlaySource;
|
||||
}
|
||||
|
||||
public PreparedAssetSourceStats Stats
|
||||
{
|
||||
get
|
||||
{
|
||||
IPreparedAssetSource baseSource = Require(_baseAssets);
|
||||
IPreparedAssetSource overlay = Require(_overlayAssets);
|
||||
PreparedAssetSourceStats left = baseSource.Stats;
|
||||
PreparedAssetSourceStats right = overlay.Stats;
|
||||
return new(
|
||||
left.Probes + right.Probes,
|
||||
left.Reads + right.Reads,
|
||||
left.Loaded + right.Loaded,
|
||||
left.Missing + right.Missing,
|
||||
left.Corrupt + right.Corrupt);
|
||||
}
|
||||
}
|
||||
|
||||
public PreparedCollisionSourceStats CollisionStats
|
||||
{
|
||||
get
|
||||
{
|
||||
IPreparedCollisionSource baseSource = Require(_baseCollision);
|
||||
IPreparedCollisionSource overlay = Require(_overlayCollision);
|
||||
PreparedCollisionSourceStats left = baseSource.CollisionStats;
|
||||
PreparedCollisionSourceStats right = overlay.CollisionStats;
|
||||
return new(
|
||||
left.Probes + right.Probes,
|
||||
left.Reads + right.Reads,
|
||||
left.Loaded + right.Loaded,
|
||||
left.Missing + right.Missing,
|
||||
left.Corrupt + right.Corrupt);
|
||||
}
|
||||
}
|
||||
|
||||
public CacheStats DecodedTextureCacheStats
|
||||
{
|
||||
get
|
||||
{
|
||||
CacheStats left = Require(_baseAssets).DecodedTextureCacheStats;
|
||||
CacheStats right = Require(_overlayAssets).DecodedTextureCacheStats;
|
||||
return new(
|
||||
left.Hits + right.Hits,
|
||||
left.Misses + right.Misses,
|
||||
left.Evictions + right.Evictions);
|
||||
}
|
||||
}
|
||||
|
||||
public long MappedVirtualBytes =>
|
||||
checked(
|
||||
Require(_baseAssets).MappedVirtualBytes
|
||||
+ Require(_overlayAssets).MappedVirtualBytes);
|
||||
|
||||
public PreparedAssetPresence Probe(PakAssetType type, uint sourceFileId)
|
||||
{
|
||||
PreparedAssetPresence overlay =
|
||||
Require(_overlayAssets).Probe(type, sourceFileId);
|
||||
return overlay == PreparedAssetPresence.Missing
|
||||
? Require(_baseAssets).Probe(type, sourceFileId)
|
||||
: overlay;
|
||||
}
|
||||
|
||||
public PreparedAssetReadResult Read(
|
||||
in PreparedAssetRequest request,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
PreparedAssetReadResult overlay =
|
||||
Require(_overlayAssets).Read(request, cancellationToken);
|
||||
return overlay.Status == PreparedAssetReadStatus.Missing
|
||||
? Require(_baseAssets).Read(request, cancellationToken)
|
||||
: overlay;
|
||||
}
|
||||
|
||||
public PreparedAssetPresence ProbeCollision(
|
||||
PakAssetType type,
|
||||
uint sourceFileId)
|
||||
{
|
||||
PreparedAssetPresence overlay =
|
||||
Require(_overlayCollision).ProbeCollision(type, sourceFileId);
|
||||
return overlay == PreparedAssetPresence.Missing
|
||||
? Require(_baseCollision).ProbeCollision(type, sourceFileId)
|
||||
: overlay;
|
||||
}
|
||||
|
||||
public PreparedCollisionReadResult<FlatGfxObjCollisionAsset>
|
||||
ReadGfxObjCollision(
|
||||
uint sourceFileId,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
PreparedCollisionReadResult<FlatGfxObjCollisionAsset> overlay =
|
||||
Require(_overlayCollision).ReadGfxObjCollision(
|
||||
sourceFileId,
|
||||
cancellationToken);
|
||||
return overlay.Status == PreparedAssetReadStatus.Missing
|
||||
? Require(_baseCollision).ReadGfxObjCollision(
|
||||
sourceFileId,
|
||||
cancellationToken)
|
||||
: overlay;
|
||||
}
|
||||
|
||||
public PreparedCollisionReadResult<FlatSetupCollision>
|
||||
ReadSetupCollision(
|
||||
uint sourceFileId,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
PreparedCollisionReadResult<FlatSetupCollision> overlay =
|
||||
Require(_overlayCollision).ReadSetupCollision(
|
||||
sourceFileId,
|
||||
cancellationToken);
|
||||
return overlay.Status == PreparedAssetReadStatus.Missing
|
||||
? Require(_baseCollision).ReadSetupCollision(
|
||||
sourceFileId,
|
||||
cancellationToken)
|
||||
: overlay;
|
||||
}
|
||||
|
||||
public PreparedCollisionReadResult<FlatCellStructureCollisionAsset>
|
||||
ReadCellStructureCollision(
|
||||
uint sourceFileId,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
PreparedCollisionReadResult<FlatCellStructureCollisionAsset> overlay =
|
||||
Require(_overlayCollision).ReadCellStructureCollision(
|
||||
sourceFileId,
|
||||
cancellationToken);
|
||||
return overlay.Status == PreparedAssetReadStatus.Missing
|
||||
? Require(_baseCollision).ReadCellStructureCollision(
|
||||
sourceFileId,
|
||||
cancellationToken)
|
||||
: overlay;
|
||||
}
|
||||
|
||||
public PreparedCollisionReadResult<FlatEnvCellTopology>
|
||||
ReadEnvCellTopology(
|
||||
uint sourceFileId,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
PreparedCollisionReadResult<FlatEnvCellTopology> overlay =
|
||||
Require(_overlayCollision).ReadEnvCellTopology(
|
||||
sourceFileId,
|
||||
cancellationToken);
|
||||
return overlay.Status == PreparedAssetReadStatus.Missing
|
||||
? Require(_baseCollision).ReadEnvCellTopology(
|
||||
sourceFileId,
|
||||
cancellationToken)
|
||||
: overlay;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
IPreparedAssetSource? overlay = Interlocked.Exchange(
|
||||
ref _overlayAssets,
|
||||
null);
|
||||
IPreparedAssetSource? baseSource = Interlocked.Exchange(
|
||||
ref _baseAssets,
|
||||
null);
|
||||
_overlayCollision = null;
|
||||
_baseCollision = null;
|
||||
|
||||
List<Exception>? failures = null;
|
||||
DisposeOne(overlay, ref failures);
|
||||
DisposeOne(baseSource, ref failures);
|
||||
if (failures is { Count: > 0 })
|
||||
{
|
||||
throw new AggregateException(
|
||||
"One or more prepared-content layers failed to dispose.",
|
||||
failures);
|
||||
}
|
||||
}
|
||||
|
||||
private static T Require<T>(T? value)
|
||||
where T : class =>
|
||||
value ?? throw new ObjectDisposedException(
|
||||
nameof(LayeredPreparedAssetSource));
|
||||
|
||||
private static void DisposeOne(
|
||||
IDisposable? value,
|
||||
ref List<Exception>? failures)
|
||||
{
|
||||
if (value is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
value.Dispose();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
(failures ??= []).Add(exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue