Game/LeaderboardOrder.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Board = Sandbox.Services.Leaderboards.Board2;
namespace BlockParty;
/// <summary>Shared ordering for leaderboard values, including exact finish-time ties.</summary>
public static class LeaderboardOrder
{
public static int Compare( double valueA, DateTimeOffset timestampA, long steamIdA,
double valueB, DateTimeOffset timestampB, long steamIdB )
{
int result = valueB.CompareTo( valueA );
if ( result == 0 ) result = timestampA.CompareTo( timestampB );
// If both the run time and submission instant match, keep every screen deterministic.
if ( result == 0 ) result = steamIdA.CompareTo( steamIdB );
return result;
}
/// <summary>
/// Sort the fetched window locally. Only fetch beyond it when a tie involving the top three
/// reaches the end of the window, so unseen entries could change a medal placement.
/// Ties at ordinary page boundaries do not justify another backend request.
/// The factory must create an uncentered board with the same stat, scope and date filters.
/// </summary>
public static async Task<List<Board.Entry>> CompletePodiumTiesAsync( List<Board.Entry> entries,
long totalEntries, Func<Board> createPage, CancellationToken cancellation = default,
Func<bool> isCurrent = null )
{
if ( entries.Count == 0 ) return entries;
var byRank = entries.OrderBy( e => e.Rank ).ToList();
var last = byRank[^1];
// A centered window below rank 1 cannot establish the global podium. For a head window,
// any value below bronze already proves its podium tie is complete, with no extra probe.
bool needsMore = byRank[0].Rank == 1 && byRank.Count >= 3
&& byRank[2].Rank == 3 && last.Value == byRank[2].Value;
var result = new Dictionary<long, Board.Entry>();
foreach ( var entry in entries ) result[entry.SteamId] = entry;
long edge = last.Rank;
double value = last.Value;
while ( needsMore && edge < totalEntries )
{
cancellation.ThrowIfCancellationRequested();
if ( isCurrent?.Invoke() == false ) throw new OperationCanceledException();
var page = createPage();
page.MaxEntries = (int)Math.Min( 100, totalEntries - edge );
page.Offset = checked((int)edge);
await page.Refresh( cancellation );
cancellation.ThrowIfCancellationRequested();
if ( isCurrent?.Invoke() == false ) throw new OperationCanceledException();
var batch = page.Entries?.OrderBy( e => e.Rank ).ToList();
// Refresh may swallow upstream errors. Never pretend an incomplete tie is resolved.
if ( batch is null || batch.Count != page.MaxEntries
|| batch[0].Rank != page.Offset + 1
|| batch[^1].Rank != page.Offset + batch.Count )
throw new InvalidOperationException( "Could not complete leaderboard tie." );
if ( batch.Any( e => e.Value > value ) )
throw new InvalidOperationException( "Leaderboard changed while completing a tie." );
foreach ( var entry in batch.Where( e => e.Value == value ) )
{
if ( !result.TryAdd( entry.SteamId, entry ) )
throw new InvalidOperationException( "Leaderboard changed while completing a tie." );
}
if ( batch.Any( e => e.Value != value ) ) break;
edge = batch[^1].Rank;
}
return result.Values.OrderByDescending( e => e.Value ).ThenBy( e => e.Timestamp )
.ThenBy( e => e.SteamId ).ToList();
}
}