using AcDream.Content.Pak;
using AcDream.Core.Physics;
namespace AcDream.Content;
///
/// 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.
///
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
ReadGfxObjCollision(
uint sourceFileId,
CancellationToken cancellationToken = default)
{
PreparedCollisionReadResult overlay =
Require(_overlayCollision).ReadGfxObjCollision(
sourceFileId,
cancellationToken);
return overlay.Status == PreparedAssetReadStatus.Missing
? Require(_baseCollision).ReadGfxObjCollision(
sourceFileId,
cancellationToken)
: overlay;
}
public PreparedCollisionReadResult
ReadSetupCollision(
uint sourceFileId,
CancellationToken cancellationToken = default)
{
PreparedCollisionReadResult overlay =
Require(_overlayCollision).ReadSetupCollision(
sourceFileId,
cancellationToken);
return overlay.Status == PreparedAssetReadStatus.Missing
? Require(_baseCollision).ReadSetupCollision(
sourceFileId,
cancellationToken)
: overlay;
}
public PreparedCollisionReadResult
ReadCellStructureCollision(
uint sourceFileId,
CancellationToken cancellationToken = default)
{
PreparedCollisionReadResult overlay =
Require(_overlayCollision).ReadCellStructureCollision(
sourceFileId,
cancellationToken);
return overlay.Status == PreparedAssetReadStatus.Missing
? Require(_baseCollision).ReadCellStructureCollision(
sourceFileId,
cancellationToken)
: overlay;
}
public PreparedCollisionReadResult
ReadEnvCellTopology(
uint sourceFileId,
CancellationToken cancellationToken = default)
{
PreparedCollisionReadResult 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? 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? value)
where T : class =>
value ?? throw new ObjectDisposedException(
nameof(LayeredPreparedAssetSource));
private static void DisposeOne(
IDisposable? value,
ref List? failures)
{
if (value is null)
{
return;
}
try
{
value.Dispose();
}
catch (Exception exception)
{
(failures ??= []).Add(exception);
}
}
}