Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions Timer.Shared/Models/Zone/EZoneType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,11 @@ namespace Source2Surf.Timer.Shared.Models.Zone;

public enum EZoneType : sbyte
{
Invalid = -1,
Start = 0,
End = 1,
Stage = 2,
Checkpoint = 3,
StopTimer = 4,
Invalid = -1,
Start,
End,
Stage,
Checkpoint,
StopTimer,
Max,
}
50 changes: 24 additions & 26 deletions Timer/Modules/RecordModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ internal partial class RecordModule : IModule, IGameListener, IRecordModule, ITi
private readonly ListenerHub<IRecordModuleListener> _listenerHub;

// Per-slot session start time (engine time when player joined this map)
private readonly double[] _sessionStartTime = new double[64];
private readonly double[] _sessionStartTime = new double[PlayerSlot.MaxPlayerCount];

// Late-resolved to avoid circular DI (ReplayRecorderModule depends on IRecordModule)
private IReplayRecorderModule _replayRecorder = null!;
Expand Down Expand Up @@ -191,24 +191,24 @@ public void OnServerActivate()
).ConfigureAwait(false);

// Load WR checkpoints for each (style, track) combination
var wrCheckpointMap = new Dictionary<(int style, int track), IReadOnlyList<RunCheckpoint>>();
var wrPerTrack = records
.GroupBy(r => (r.Style, r.Track))
.Select(g => g.OrderBy(r => r.Time).ThenBy(r => r.Id).First());

var wrByTrack = records.GroupBy(r => (r.Style, r.Track));

foreach (var group in wrByTrack)
var wrCheckpointTasks = wrPerTrack.Select(async wr =>
{
var wr = group.OrderBy(r => r.Time).ThenBy(r => r.Id).FirstOrDefault();
var checkpoints = await RetryHelper.RetryAsync(() => _request.GetRecordCheckpoints(wr.Id),
RetryHelper.IsTransient,
_logger,
"GetRecordCheckpoints").ConfigureAwait(false);

if (wr is not null)
{
var checkpoints = await RetryHelper.RetryAsync(
() => _request.GetRecordCheckpoints(wr.Id),
RetryHelper.IsTransient, _logger, "GetRecordCheckpoints"
).ConfigureAwait(false);
return (key: (wr.Style, wr.Track), checkpoints);
});

wrCheckpointMap[(wr.Style, wr.Track)] = checkpoints;
}
}
var wrCheckpointResults = await Task.WhenAll(wrCheckpointTasks).ConfigureAwait(false);

var wrCheckpointMap = wrCheckpointResults
.ToDictionary(r => r.key, r => r.checkpoints);

await _bridge.ModSharp.InvokeFrameActionAsync(() =>
{
Expand Down Expand Up @@ -243,18 +243,16 @@ await _bridge.ModSharp.InvokeFrameActionAsync(() =>
public void OnGameShutdown()
{
// Flush playtime for all connected players before map change
for (var i = 0; i < 64; i++)
for (PlayerSlot i = 0; i < PlayerSlot.MaxPlayerCount; i++)
{
if (_sessionStartTime[i] <= 0)
{
continue;
}

var playerSlot = new PlayerSlot(i);

if (_bridge.ClientManager.GetGameClient(playerSlot) is { IsFakeClient: false } client)
if (_bridge.ClientManager.GetGameClient(i) is { IsFakeClient: false } client)
{
FlushPlayerMapStats(playerSlot, client.SteamId);
FlushPlayerMapStats(i, client.SteamId);
}
else
{
Expand Down Expand Up @@ -322,7 +320,7 @@ public void OnClientPutInServer(PlayerSlot slot)
}

_playerCache.Clear(slot);
_sessionStartTime[(int)slot] = _bridge.ModSharp.EngineTime();
_sessionStartTime[slot] = _bridge.ModSharp.EngineTime();
}

public void OnClientDisconnected(PlayerSlot slot)
Expand All @@ -333,7 +331,7 @@ public void OnClientDisconnected(PlayerSlot slot)
}

FlushPlayerMapStats(slot, client.SteamId);
_sessionStartTime[(int)slot] = 0; // player left, clear session
_sessionStartTime[slot] = 0; // player left, clear session

_playerCache.Clear(slot);
}
Expand Down Expand Up @@ -411,14 +409,14 @@ public int GetTotalRecordCount(int style, int track) =>

public float GetSessionTime(PlayerSlot slot)
{
var start = _sessionStartTime[(int)slot];
var start = _sessionStartTime[slot];

return start > 0 ? (float)(_bridge.ModSharp.EngineTime() - start) : 0f;
}

private void FlushPlayerMapStats(PlayerSlot slot, SteamID steamId)
{
var index = (int)slot;
var start = _sessionStartTime[index];
var start = _sessionStartTime[slot];

if (start <= 0)
{
Expand All @@ -428,7 +426,7 @@ private void FlushPlayerMapStats(PlayerSlot slot, SteamID steamId)
var delta = (float)(_bridge.ModSharp.EngineTime() - start);
var mapName = _bridge.GlobalVars.MapName;

_sessionStartTime[index] = _bridge.ModSharp.EngineTime(); // reset for next session segment
_sessionStartTime[slot] = _bridge.ModSharp.EngineTime(); // reset for next session segment

if (delta <= 0f)
{
Expand Down
2 changes: 1 addition & 1 deletion Timer/Modules/Replay/PendingRecordResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ namespace Source2Surf.Timer.Modules.Replay;

/// <summary>
/// Stores a record result when OnRecordSaved arrives before the post-frame timer expires.
/// Consumed by CreateAndStorePendingReplay to bypass PendingReplayStore.
/// Consumed by StorePendingReplay to bypass PendingReplayStore.
/// </summary>
internal sealed class PendingRecordResult
{
Expand Down
3 changes: 2 additions & 1 deletion Timer/Modules/Replay/PlayerFrameData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
using System;
using System.Collections.Generic;
using Sharp.Shared.Units;
using Source2Surf.Timer.Shared.Models.Replay;

namespace Source2Surf.Timer.Modules.Replay;

Expand All @@ -37,7 +38,7 @@ internal class PlayerFrameData

public bool GrabbingPostFrame { get; set; } = false;

public ReplayFrameBuffer Frames { get; set; } = [];
public List<ReplayFrameData> Frames { get; set; } = [];

public int AttemptId { get; set; }

Expand Down
3 changes: 2 additions & 1 deletion Timer/Modules/Replay/ReplayBotData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ namespace Source2Surf.Timer.Modules.Replay;

internal class ReplayBotData : IReplayBotData
{
public required ReplayBotConfig Config { get; init; }
public required ReplayBotConfig Config { get; init; }
public required int ConfigIndex { get; init; }

public EntityIndex Index { get; init; }
public required IPlayerController Controller { get; init; }
Expand Down
157 changes: 0 additions & 157 deletions Timer/Modules/Replay/ReplayFrameBuffer.cs

This file was deleted.

15 changes: 8 additions & 7 deletions Timer/Modules/Replay/ReplayShared.cs
Original file line number Diff line number Diff line change
Expand Up @@ -244,12 +244,13 @@ public static ReplaySaveSnapshot CreateMainReplaySnapshot(PlayerFrameData frame)
{
var framesBuffer = frame.Frames;

// Inherit the old buffer's Capacity to avoid repeated exponential resizing on long maps.
// ReplayFrameBuffer doubles via EnsureCapacity when full (same as List<T>).
// Initial capacity (Tickrate*60*5 ≈ 38400 at 128tick) only covers ~5 min;
// long maps (20+ min) would re-climb the same doubling chain without this.
var inheritedCapacity = Math.Max(framesBuffer.Capacity, TimerConstants.Tickrate * 60 * 5);
frame.Frames = new ReplayFrameBuffer(inheritedCapacity);
// Size the next buffer to ~2x the last run's frame count (or baseline, whichever is larger).
// Steady-length players skip the doubling chain; players whose run length collapses
// (one long warm-up, then short attempts) get memory back instead of holding a
// worst-case buffer forever.
var baseline = TimerConstants.Tickrate * 60 * 5;
var newCapacity = Math.Max(baseline, framesBuffer.Count * 2);
frame.Frames = new List<ReplayFrameData>(newCapacity);

var header = new ReplayFileHeader
{
Expand Down Expand Up @@ -318,7 +319,7 @@ public static void TrimPreRunFrames(PlayerFrameData frameData, int maxPreFrame)

if (excess > 0)
{
frameData.Frames.RemoveOldest(excess);
frameData.Frames.RemoveRange(0, excess);
}
}

Expand Down
Loading
Loading