using AcDream.Content.Pak; using AcDream.Core.Physics; namespace AcDream.Content; public readonly record struct PreparedCollisionSourceStats( long Probes, long Reads, long Loaded, long Missing, long Corrupt); public readonly record struct PreparedCollisionReadResult( PreparedAssetReadStatus Status, T? Data) where T : class { public static PreparedCollisionReadResult Missing => new(PreparedAssetReadStatus.Missing, null); public static PreparedCollisionReadResult Corrupt => new(PreparedAssetReadStatus.Corrupt, null); public static PreparedCollisionReadResult Loaded(T data) => new(PreparedAssetReadStatus.Loaded, data); } /// /// Typed immutable collision payload source. It is deliberately separate from /// the render-only contract even when one /// concrete package owner implements both interfaces over the same mmap. /// public interface IPreparedCollisionSource : IDisposable { PreparedAssetPresence ProbeCollision( PakAssetType type, uint sourceFileId); PreparedCollisionReadResult ReadGfxObjCollision( uint sourceFileId, CancellationToken cancellationToken = default); PreparedCollisionReadResult ReadSetupCollision( uint sourceFileId, CancellationToken cancellationToken = default); PreparedCollisionReadResult ReadCellStructureCollision( uint sourceFileId, CancellationToken cancellationToken = default); PreparedCollisionReadResult ReadEnvCellTopology( uint sourceFileId, CancellationToken cancellationToken = default); PreparedCollisionSourceStats CollisionStats { get; } } internal static class PreparedCollisionTypeContract { public static void Validate(PakAssetType type) { if (type is not ( PakAssetType.GfxObjCollision or PakAssetType.SetupCollision or PakAssetType.CellStructureCollision or PakAssetType.EnvCellTopology)) { throw new ArgumentOutOfRangeException( nameof(type), type, "unknown prepared collision asset type"); } } } public sealed partial class PakPreparedAssetSource { private long _collisionProbes; private long _collisionReads; private long _collisionLoaded; private long _collisionMissing; private long _collisionCorrupt; public PreparedCollisionSourceStats CollisionStats => new( Volatile.Read(ref _collisionProbes), Volatile.Read(ref _collisionReads), Volatile.Read(ref _collisionLoaded), Volatile.Read(ref _collisionMissing), Volatile.Read(ref _collisionCorrupt)); public PreparedAssetPresence ProbeCollision( PakAssetType type, uint sourceFileId) { PreparedCollisionTypeContract.Validate(type); Interlocked.Increment(ref _collisionProbes); ulong key = PakKey.Compose(type, sourceFileId); return _reader.ProbeEntry(key) switch { PakEntryState.Available => PreparedAssetPresence.Available, PakEntryState.Corrupt => PreparedAssetPresence.Corrupt, _ => PreparedAssetPresence.Missing, }; } public PreparedCollisionReadResult ReadGfxObjCollision( uint sourceFileId, CancellationToken cancellationToken = default) => ReadCollision( PakAssetType.GfxObjCollision, sourceFileId, static (bytes, token) => FlatCollisionAssetSerializer.DeserializeGfxObj(bytes, token), cancellationToken); public PreparedCollisionReadResult ReadSetupCollision( uint sourceFileId, CancellationToken cancellationToken = default) => ReadCollision( PakAssetType.SetupCollision, sourceFileId, static (bytes, token) => FlatCollisionAssetSerializer.DeserializeSetup(bytes, token), cancellationToken); public PreparedCollisionReadResult ReadCellStructureCollision( uint sourceFileId, CancellationToken cancellationToken = default) => ReadCollision( PakAssetType.CellStructureCollision, sourceFileId, static (bytes, token) => FlatCollisionAssetSerializer.DeserializeCellStructure( bytes, token), cancellationToken); public PreparedCollisionReadResult ReadEnvCellTopology( uint sourceFileId, CancellationToken cancellationToken = default) => ReadCollision( PakAssetType.EnvCellTopology, sourceFileId, static (bytes, token) => FlatCollisionAssetSerializer.DeserializeEnvCellTopology( bytes, token), cancellationToken); private PreparedCollisionReadResult ReadCollision( PakAssetType type, uint sourceFileId, Func deserialize, CancellationToken cancellationToken) where T : class { PreparedCollisionTypeContract.Validate(type); cancellationToken.ThrowIfCancellationRequested(); Interlocked.Increment(ref _collisionReads); ulong key = PakKey.Compose(type, sourceFileId); PakObjectReadStatus status = _reader.ReadBlobBytes(key, out byte[]? bytes); cancellationToken.ThrowIfCancellationRequested(); if (status == PakObjectReadStatus.Missing) { Interlocked.Increment(ref _collisionMissing); return PreparedCollisionReadResult.Missing; } if (status == PakObjectReadStatus.Corrupt || bytes is null) { Interlocked.Increment(ref _collisionCorrupt); return PreparedCollisionReadResult.Corrupt; } try { T data = deserialize(bytes, cancellationToken); cancellationToken.ThrowIfCancellationRequested(); Interlocked.Increment(ref _collisionLoaded); return PreparedCollisionReadResult.Loaded(data); } catch (OperationCanceledException) { throw; } catch (Exception exception) { _reader.MarkPayloadCorrupt( key, $"collision deserialization failed despite matching CRC: " + $"{exception.GetType().Name}: {exception.Message}"); Interlocked.Increment(ref _collisionCorrupt); return PreparedCollisionReadResult.Corrupt; } } }