logo SERENITY
Game Systems Foundation
Search Results for

    Show / Hide Table of Contents

    Class AchievementService

    Default IAchievementService implementation. Holds unlocks and progress as an in-memory cache hydrated by LoadAsync(CancellationToken), and delegates the parts that are not about state changes to named collaborators: AchievementRegistry for what an achievement is, AchievementStoreAccess for persistence, AchievementPlatformSyncPass for the platform account, AchievementPointsMirror for the Score projection, and AchievementScreenshotAttachment for the detached capture.

    Inheritance
    object
    AchievementService
    Implements
    IAchievementService
    IService
    IFoundationSettings
    IDisposable
    Inherited Members
    object.ToString()
    object.Equals(object)
    object.Equals(object, object)
    object.ReferenceEquals(object, object)
    object.GetHashCode()
    object.GetType()
    object.MemberwiseClone()
    Namespace: Serenity.Achievements.Application.Services
    Assembly: Serenity.Achievements.Application.dll
    Syntax
    public class AchievementService : IAchievementService, IService, IFoundationSettings, IDisposable
    Remarks

    Every mutation runs under one service-wide AsyncMutex. Without it, a check-then-act sequence that spans an await lets two concurrent unlocks of the same achievement both pass the "already unlocked?" test, and two concurrent increments both read the same numerator and lose one of the deltas. Nothing here throws for the caller: an unknown achievement warns once and no-ops, a store failure keeps the in-memory state and degrades persistence, and a platform failure leaves the record queued for the next sync pass.

    Constructors

    AchievementService(AchievementRegistry, AchievementStoreAccess, AchievementPlatformSyncPass, AchievementScreenshotAttachment, AchievementPointsMirror, AchievementSessionGeneration, IAchievementPlayerIdentityProvider, IAchievementSettings, IEventDispatcherService, ILogService)

    Initializes a new instance of the AchievementService class.

    Declaration
    public AchievementService(AchievementRegistry registry, AchievementStoreAccess store, AchievementPlatformSyncPass platformSync, AchievementScreenshotAttachment screenshotAttachment, AchievementPointsMirror pointsMirror, AchievementSessionGeneration sessionGeneration, IAchievementPlayerIdentityProvider identity, IAchievementSettings settings, IEventDispatcherService eventDispatcherService, ILogService logService)
    Parameters
    Type Name Description
    AchievementRegistry registry

    The achievements this project declares.

    AchievementStoreAccess store

    Never-throwing access to the backend store.

    AchievementPlatformSyncPass platformSync

    Owns every call to the platform account.

    AchievementScreenshotAttachment screenshotAttachment

    Owns the detached unlock-frame capture.

    AchievementPointsMirror pointsMirror

    Projects unlocked points into a Score metric.

    AchievementSessionGeneration sessionGeneration

    The reset counter detached work is gated on. Must be the same instance screenshotAttachment was built with, or a reset will not invalidate an in-flight capture.

    IAchievementPlayerIdentityProvider identity

    Provider that answers whose achievements these are.

    IAchievementSettings settings

    Achievement configuration read model.

    IEventDispatcherService eventDispatcherService

    Dispatcher used to broadcast AchievementUnlockedSignal, AchievementProgressChangedSignal and AchievementScreenshotCapturedSignal. May be null; the service works silently without one.

    ILogService logService

    Logging service. May be null.

    Exceptions
    Type Condition
    ArgumentNullException

    Thrown when a required collaborator is null.

    Properties

    Guid

    Declaration
    public string Guid { get; set; }
    Property Value
    Type Description
    string

    Id

    Declaration
    public string Id { get; set; }
    Property Value
    Type Description
    string

    IsLoaded

    Whether LoadAsync(CancellationToken) has successfully hydrated the cache. False means every read answers "locked" because nothing has been read from the store yet, not because the player has earned nothing.

    Declaration
    public virtual bool IsLoaded { get; }
    Property Value
    Type Description
    bool

    LogService

    Declaration
    public ILogService LogService { get; set; }
    Property Value
    Type Description
    ILogService

    Methods

    Dispose()

    Cancels every capture still in flight and swaps in a fresh token source, the same way Serenity.Achievements.Application.Services.AchievementService.CancelDetachedWork does for a reset, so teardown stops work instead of leaving it running against a service nothing reads any more. Every mutating call made after this is a no-op. Safe to call more than once.

    Declaration
    public virtual void Dispose()
    Remarks

    A mutation that already read _isDisposed as false and is now waiting on the mutation gate is not aborted — it finishes what it started — but it re-checks the flag once it holds the gate, so it starts no NEW detached work (a screenshot capture, another platform push) on a service that is already torn down. _isDisposed is volatile so that re-check, made from whatever thread resumed the awaiter, actually observes a concurrent Dispose().

    GetAll()

    Every registered achievement definition, in display order.

    Declaration
    public virtual IReadOnlyList<AchievementDefinition> GetAll()
    Returns
    Type Description
    IReadOnlyList<AchievementDefinition>

    GetMaximumPoints()

    The points every registered achievement adds up to.

    Declaration
    public virtual int GetMaximumPoints()
    Returns
    Type Description
    int

    GetTotalPoints()

    The points the current player has actually earned.

    Declaration
    public virtual int GetTotalPoints()
    Returns
    Type Description
    int

    GetUnlocked()

    The registered definitions the current player has unlocked, in display order.

    Declaration
    public virtual IReadOnlyList<AchievementDefinition> GetUnlocked()
    Returns
    Type Description
    IReadOnlyList<AchievementDefinition>

    GetVisible()

    The registered definitions the UI may show, in display order: everything that is not hidden, plus every hidden achievement the player has already unlocked.

    Declaration
    public virtual IReadOnlyList<AchievementDefinition> GetVisible()
    Returns
    Type Description
    IReadOnlyList<AchievementDefinition>

    IncrementProgressAsync(AchievementId, long, CancellationToken)

    Adds a delta to the progress numerator of a progressive achievement.

    Declaration
    public virtual Task IncrementProgressAsync(AchievementId achievement, long delta, CancellationToken cancellationToken = default)
    Parameters
    Type Name Description
    AchievementId achievement

    The achievement to advance.

    long delta

    The amount to add to the numerator.

    CancellationToken cancellationToken

    Token used to cancel the operation.

    Returns
    Type Description
    Task

    IsUnlocked(AchievementId)

    Whether an achievement is unlocked for the current player.

    Declaration
    public virtual bool IsUnlocked(AchievementId achievement)
    Parameters
    Type Name Description
    AchievementId achievement

    The achievement to test.

    Returns
    Type Description
    bool

    LoadAsync(CancellationToken)

    Hydrates the in-memory cache from the store, reconciles it against the platform account, and retries any unlock that has not reached the platform yet.

    Declaration
    public virtual Task LoadAsync(CancellationToken cancellationToken = default)
    Parameters
    Type Name Description
    CancellationToken cancellationToken

    Token used to cancel the operation.

    Returns
    Type Description
    Task

    ResetAllAsync(CancellationToken)

    Clears every unlock, progress row and screenshot for the current player. The platform account is only cleared when ClearPlatformOnReset is on. Intended for debugging and QA, not for shipped gameplay.

    Declaration
    public virtual Task ResetAllAsync(CancellationToken cancellationToken = default)
    Parameters
    Type Name Description
    CancellationToken cancellationToken

    Token used to cancel the operation.

    Returns
    Type Description
    Task

    RetryPlatformSyncAsync(CancellationToken)

    Re-attempts everything LoadAsync(CancellationToken) does with the platform account: brings the session up if it is not up yet, reconciles both directions when the account can be read, and drains the offline queue.

    Declaration
    public virtual Task RetryPlatformSyncAsync(CancellationToken cancellationToken = default)
    Parameters
    Type Name Description
    CancellationToken cancellationToken

    Token used to cancel the operation.

    Returns
    Type Description
    Task
    Remarks

    A storefront client that was closed at boot typically comes up minutes later, and nothing else re-tries: without this the unlocks earned in between sit at platform_synced = false until the next launch. The Unity runner added in a later phase calls this on a backoff timer; game code rarely needs to. Cheap and safe to call when the platform is still unavailable — it returns after the failed initialize.

    SaveConditionStateAsync(AchievementId, string, CancellationToken)

    Stores the opaque state an unlock condition needs to resume counting after a relaunch, against the achievement's progress row.

    Declaration
    public virtual Task SaveConditionStateAsync(AchievementId achievement, string conditionState, CancellationToken cancellationToken = default)
    Parameters
    Type Name Description
    AchievementId achievement

    The achievement the state belongs to.

    string conditionState

    The state produced by the condition tree. May be null.

    CancellationToken cancellationToken

    Token used to cancel the operation.

    Returns
    Type Description
    Task
    Remarks

    Separate from SetProgressAsync(AchievementId, long, CancellationToken) because the two answer different questions: the numerator is what the player is shown, the condition state is how the evaluator rebuilds its counters, and a boolean achievement has the second without ever having the first. Unknown and already-unlocked achievements are no-ops.

    SetProgressAsync(AchievementId, long, CancellationToken)

    Sets the progress numerator of a progressive achievement, clamped to its target.

    Declaration
    public virtual Task SetProgressAsync(AchievementId achievement, long current, CancellationToken cancellationToken = default)
    Parameters
    Type Name Description
    AchievementId achievement

    The achievement to advance.

    long current

    The new numerator.

    CancellationToken cancellationToken

    Token used to cancel the operation.

    Returns
    Type Description
    Task

    TryGetProgress(AchievementId, out AchievementProgress)

    Attempts to read the tracked progress of an achievement.

    Declaration
    public virtual bool TryGetProgress(AchievementId achievement, out AchievementProgress progress)
    Parameters
    Type Name Description
    AchievementId achievement

    The achievement to look up.

    AchievementProgress progress

    The tracked progress, when one exists.

    Returns
    Type Description
    bool

    True when progress is tracked for the achievement; otherwise false.

    TryGetUnlockRecord(AchievementId, out AchievementUnlockRecord)

    Attempts to read the unlock record of an achievement.

    Declaration
    public virtual bool TryGetUnlockRecord(AchievementId achievement, out AchievementUnlockRecord record)
    Parameters
    Type Name Description
    AchievementId achievement

    The achievement to look up.

    AchievementUnlockRecord record

    The unlock record, when the achievement is unlocked.

    Returns
    Type Description
    bool

    True when the achievement is unlocked; otherwise false.

    UnlockAsync(AchievementId, CancellationToken)

    Unlocks an achievement. Already-unlocked and unknown achievements are no-ops.

    Declaration
    public virtual Task UnlockAsync(AchievementId achievement, CancellationToken cancellationToken = default)
    Parameters
    Type Name Description
    AchievementId achievement

    The achievement to unlock.

    CancellationToken cancellationToken

    Token used to cancel the operation.

    Returns
    Type Description
    Task

    Events

    CacheHydrated

    Raised once LoadAsync(CancellationToken) has hydrated the local cache, BEFORE the platform account is consulted.

    Declaration
    public event Action CacheHydrated
    Event Type
    Type Description
    Action
    Remarks

    Loaded only fires after the whole load, and the whole load includes the storefront handshake — which, for a gateway whose session arrives through a callback, can take seconds. Anything that needs the stored state and nothing from the platform (the evaluator's condition tallies) listens here instead, so it is not held back by a storefront that is slow to answer.

    Loaded

    Raised after LoadAsync(CancellationToken) has hydrated the cache, so a screen already on display can show what the player actually owns.

    Declaration
    public event Action Loaded
    Event Type
    Type Description
    Action
    Remarks

    The achievements module loads asynchronously while the game's first menu is already up, so a listing opened early would otherwise show every achievement as locked and never correct itself — reads are synchronous over a cache that simply was not populated yet. Only a successful load raises this; a store that could not be reached raises nothing, because nothing changed. Raised outside the mutation gate, so a handler is free to call back into the service, and every handler failure is contained.

    StateReset

    Raised after ResetAllAsync(CancellationToken) has cleared everything, so the parts of the system that hold derived state can drop theirs too.

    Declaration
    public event Action StateReset
    Event Type
    Type Description
    Action
    Remarks

    The evaluator is the reason this exists: it drops an achievement's condition tree the moment the achievement unlocks, and its trees carry tallies of their own. Without a notification, a debug reset would clear the store and leave every already-unlocked achievement permanently unreachable for the rest of the session, with stale counters in the trees that remained. Raised outside the service's mutation gate, so a handler is free to call back into the service.

    Implements

    IAchievementService
    IService
    IFoundationSettings
    IDisposable
    In this article
    © 2026 Serenity. All Rights Reserved