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.
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;
}
}