public static async Task CheckForTimedUnlocksAsync( PuzzleServerContext context, Event eventObj, Team team) { DateTime expiry; lock (TimedUnlockExpiryCache) { // throttle this by an expiry interval before we do anything even remotely expensive if (TimedUnlockExpiryCache.TryGetValue(team.ID, out expiry) && expiry >= DateTime.UtcNow) { return; } } DateTime now = DateTime.UtcNow; // do the unlocks in a loop. // The loop will catch cascading unlocks, e.g. if someone does not hit the site between 11:59 and 12:31, catch up to the 12:30 unlocks immediately. while (true) { var puzzlesToSolveByTime = await PuzzleStateHelper.GetSparseQuery(context, eventObj, null, team) .Where(state => state.SolvedTime == null && state.UnlockedTime != null && state.Puzzle.MinutesToAutomaticallySolve != null && state.UnlockedTime.Value + TimeSpan.FromMinutes(state.Puzzle.MinutesToAutomaticallySolve.Value) <= now) .Select((state) => new { Puzzle = state.Puzzle, UnlockedTime = state.UnlockedTime.Value }) .ToListAsync(); foreach (var state in puzzlesToSolveByTime) { // mark solve time as when the puzzle was supposed to complete, so cascading unlocks work properly. await PuzzleStateHelper.SetSolveStateAsync(context, eventObj, state.Puzzle, team, state.UnlockedTime + TimeSpan.FromMinutes(state.Puzzle.MinutesToAutomaticallySolve.Value)); } // get out of the loop if we did nothing if (puzzlesToSolveByTime.Count == 0) { break; } } lock (TimedUnlockExpiryCache) { // effectively, expiry = Math.Max(DateTime.UtcNow, LastGlobalExpiry) + ClosestExpirySpacing - if you could use Math.Max on DateTime expiry = DateTime.UtcNow; if (expiry < LastGlobalExpiry) { expiry = LastGlobalExpiry; } expiry += ClosestExpirySpacing; TimedUnlockExpiryCache[team.ID] = expiry; LastGlobalExpiry = expiry; } }
public static async Task CheckForTimedUnlocksAsync( PuzzleServerContext context, Event eventObj, Team team) { DateTime expiry; lock (TimedUnlockExpiryCache) { // throttle this by an expiry interval before we do anything even remotely expensive if (TimedUnlockExpiryCache.TryGetValue(team.ID, out expiry) && expiry >= DateTime.UtcNow) { return; } } DateTime now = DateTime.UtcNow; var puzzlesToSolveByTime = await PuzzleStateHelper.GetSparseQuery(context, eventObj, null, team) .Where(state => state.SolvedTime == null && state.UnlockedTime != null && state.Puzzle.MinutesToAutomaticallySolve != null && state.UnlockedTime.Value + TimeSpan.FromMinutes(state.Puzzle.MinutesToAutomaticallySolve.Value) <= now) .Select(state => state.Puzzle) .ToListAsync(); foreach (Puzzle puzzle in puzzlesToSolveByTime) { await PuzzleStateHelper.SetSolveStateAsync(context, eventObj, puzzle, team, DateTime.UtcNow); } lock (TimedUnlockExpiryCache) { // effectively, expiry = Math.Max(DateTime.UtcNow, LastGlobalExpiry) + ClosestExpirySpacing - if you could use Math.Max on DateTime expiry = DateTime.UtcNow; if (expiry < LastGlobalExpiry) { expiry = LastGlobalExpiry; } expiry += ClosestExpirySpacing; TimedUnlockExpiryCache[team.ID] = expiry; LastGlobalExpiry = expiry; } }