logo SERENITY
Game Systems Foundation
Search Results for

    Show / Hide Table of Contents

    Class BlobAchievementScreenshotStore

    Stores the frame captured at unlock time as a blob, under Achievements/{player}/{achievement}.png, and hands the unlock record that key.

    Inheritance
    object
    BlobAchievementScreenshotStore
    Implements
    IAchievementScreenshotStore
    Inherited Members
    object.ToString()
    object.Equals(object)
    object.Equals(object, object)
    object.ReferenceEquals(object, object)
    object.GetHashCode()
    object.GetType()
    Namespace: Serenity.Achievements.Infrastructure.Stores
    Assembly: Serenity.Achievements.Infrastructure.dll
    Syntax
    public sealed class BlobAchievementScreenshotStore : IAchievementScreenshotStore
    Remarks

    Engine-free: everything Unity-specific stops at the capturer that produced the bytes.

    No key reaches the blob store unvalidated. Keys arrive from unlock records, which the project's store may have loaded from a file a player can edit, so every one is parsed by AchievementScreenshotKey and the resolved path is re-checked against the anchor before any read, write or delete. A key that fails either check is refused and reported once — never followed.

    IBlobStore is addressed by key and cannot enumerate one, so "delete everything this player has" is answered from an index blob this store maintains beside the screenshots — one relative key per line. Rebuilding that list by asking the store about every declared achievement would miss the screenshots of achievements a later build removed, which are exactly the ones a reset has to clear. The index is read-modify-written under an AsyncMutex: two unlocks in the same frame are two concurrent saves, and an unserialized read-modify-write loses one of them, orphaning a PNG no reset can find.

    The index entry is written before the PNG, and removed best-effort if the PNG write then fails. The two orderings fail differently and only one is safe: an index naming a blob that is not there costs a delete that finds nothing, while a blob no index names is an orphan on the player's disk that no reset will ever remove.

    Nothing here throws at the caller except cancellation the caller asked for: a reset that failed to delete a file must not abort the reset, and an unlock must not fail because its screenshot could not be written. A failed write reports no key, so the record simply carries no screenshot.

    Constructors

    BlobAchievementScreenshotStore(IBlobStore, string, ILogService)

    Initializes a new instance over an already-resolved blob store.

    Declaration
    public BlobAchievementScreenshotStore(IBlobStore blobStore, string rootDirectory, ILogService logService = null)
    Parameters
    Type Name Description
    IBlobStore blobStore

    The blob store the bytes are written to and read back from.

    string rootDirectory

    The directory the backend anchors its keys under. May be blank when the backend keys are already relative.

    ILogService logService

    Service used to report a failed or refused operation. May be null.

    Exceptions
    Type Condition
    ArgumentNullException

    Thrown when blobStore is null.

    BlobAchievementScreenshotStore(Func<IBlobStore>, string, ILogService)

    Initializes a new instance over a blob store that may not be installed yet.

    Declaration
    public BlobAchievementScreenshotStore(Func<IBlobStore> resolveBlobStore, string rootDirectory, ILogService logService = null)
    Parameters
    Type Name Description
    Func<IBlobStore> resolveBlobStore

    Resolves the blob store, retried until it answers.

    string rootDirectory

    The directory the backend anchors its keys under.

    ILogService logService

    Service used to report a failed or refused operation. May be null.

    Remarks

    The resolver is re-asked on every operation rather than captured once, so a project whose pipeline profile installs achievements before persistence still stores screenshots from the moment the backend arrives — a captured null would switch them off for the whole session.

    Exceptions
    Type Condition
    ArgumentNullException

    Thrown when resolveBlobStore is null.

    Methods

    DeleteAllAsync(PlayerId, CancellationToken)

    Declaration
    public Task DeleteAllAsync(PlayerId player, CancellationToken cancellationToken)
    Parameters
    Type Name Description
    PlayerId player
    CancellationToken cancellationToken
    Returns
    Type Description
    Task

    SaveAsync(PlayerId, AchievementId, byte[], CancellationToken)

    Declaration
    public Task<string> SaveAsync(PlayerId player, AchievementId achievement, byte[] pngBytes, CancellationToken cancellationToken)
    Parameters
    Type Name Description
    PlayerId player
    AchievementId achievement
    byte[] pngBytes
    CancellationToken cancellationToken
    Returns
    Type Description
    Task<string>

    TryReadAsync(string, CancellationToken)

    Reads back the bytes stored under a key, or null when nothing could be read.

    Declaration
    public Task<byte[]> TryReadAsync(string storageKey, CancellationToken cancellationToken)
    Parameters
    Type Name Description
    string storageKey

    The key an unlock record carries.

    CancellationToken cancellationToken

    Token used to cancel the operation.

    Returns
    Type Description
    Task<byte[]>

    Implements

    IAchievementScreenshotStore
    In this article
    © 2026 Serenity. All Rights Reserved