PlatformAchievementSync.cs

Helper class for retrying platform achievement unlock calls. Tracks per-achievement retry timestamps, skips already-unlocked or ineligible entries, and reports exceptions through a callback.

Rough Code
using System;
using System.Collections.Generic;

namespace Socer;

public sealed record PlatformAchievementState(string Id,bool IsUnlocked);

// A void Unlock call is a request, never confirmation. Only platform state can
// confirm delivery. No persistent 'sent' flag may discard an offline unlock.
public sealed class PlatformAchievementSync
{
    readonly Dictionary<string,double> nextAttempt=new(StringComparer.Ordinal);
    public const double RetrySeconds=30;

    public int Synchronize(double now,bool canSubmit,IReadOnlyCollection<string> earned,
        IEnumerable<PlatformAchievementState> registered,Action<string> unlock,Action<Exception> onError=null)
    {
        if(!canSubmit || earned==null || registered==null || unlock==null)return 0;
        var eligible=new HashSet<string>(earned,StringComparer.Ordinal);
        var visited=new HashSet<string>(StringComparer.Ordinal);
        int requested=0;
        foreach(var achievement in registered)
        {
            if(achievement==null || string.IsNullOrEmpty(achievement.Id) || !visited.Add(achievement.Id))continue;
            if(achievement.IsUnlocked){nextAttempt.Remove(achievement.Id);continue;}
            if(!eligible.Contains(achievement.Id) || nextAttempt.TryGetValue(achievement.Id,out double after) && now<after)continue;
            nextAttempt[achievement.Id]=now+RetrySeconds;
            try{unlock(achievement.Id);requested++;}
            catch(Exception error){onError?.Invoke(error);}
        }
        return requested;
    }
}