Skip to content

Commit c2c3927

Browse files
perf: optimize gameplay hot paths — ClicksPerSecondController, GameplaySampleTriggerSource, HitEventExtensions
Agent-Logs-Url: https://github.com/winnerspiros/osu/sessions/adbddea2-3da1-47fd-b556-24d1a5d61ad5 Co-authored-by: winnerspiros <1675249+winnerspiros@users.noreply.github.com>
1 parent 74cdcb4 commit c2c3927

3 files changed

Lines changed: 46 additions & 16 deletions

File tree

osu.Game/Rulesets/Scoring/HitEventExtensions.cs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,12 +63,23 @@ public static class HitEventExtensions
6363
/// </returns>
6464
public static double? CalculateAverageHitError(this IEnumerable<HitEvent> hitEvents)
6565
{
66-
double[] timeOffsets = hitEvents.Where(AffectsUnstableRate).Select(ev => ev.TimeOffset).ToArray();
66+
// Single-pass mean using running sum — avoids allocating a temporary array.
67+
double sum = 0;
68+
int count = 0;
6769

68-
if (timeOffsets.Length == 0)
70+
foreach (var ev in hitEvents)
71+
{
72+
if (!AffectsUnstableRate(ev))
73+
continue;
74+
75+
sum += ev.TimeOffset;
76+
count++;
77+
}
78+
79+
if (count == 0)
6980
return null;
7081

71-
return timeOffsets.Average();
82+
return sum / count;
7283
}
7384

7485
/// <summary>

osu.Game/Rulesets/UI/GameplaySampleTriggerSource.cs

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,8 @@ public virtual void Play()
6464
if (nextObject == null)
6565
return;
6666

67-
var samples = nextObject.Samples
68-
.Cast<ISampleInfo>()
69-
.ToArray();
67+
// HitSampleInfo implements ISampleInfo, so array covariance lets us skip .Cast<>().
68+
var samples = nextObject.Samples.ToArray();
7069

7170
PlaySamples(samples);
7271
}
@@ -135,7 +134,23 @@ protected override void Update()
135134

136135
// Else we want the earliest valid nested.
137136
// In cases of nested objects, they will always have earlier sample data than their parent object.
138-
return getAllNested(mostValidObject.HitObject).OrderBy(h => h.GetEndTime()).SkipWhile(h => h.GetEndTime() <= getReferenceTime()).FirstOrDefault() ?? mostValidObject.HitObject;
137+
// Single-pass scan avoids the OrderBy + SkipWhile + FirstOrDefault LINQ chain.
138+
double referenceTime = getReferenceTime();
139+
HitObject? best = null;
140+
double bestEnd = double.MaxValue;
141+
142+
foreach (var nested in getAllNested(mostValidObject.HitObject))
143+
{
144+
double end = nested.GetEndTime();
145+
146+
if (end > referenceTime && end < bestEnd)
147+
{
148+
best = nested;
149+
bestEnd = end;
150+
}
151+
}
152+
153+
return best ?? mostValidObject.HitObject;
139154
}
140155

141156
private bool isAlreadyHit(HitObjectLifetimeEntry h) => h.AllJudged;

osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondController.cs

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,19 +36,23 @@ protected override void Update()
3636
double latestValidTime = clock.CurrentTime;
3737
double earliestTimeValid = latestValidTime - 1000 * gameplayClock.GetTrueGameplayRate();
3838

39+
// Timestamps are added in chronological order (from clock.CurrentTime),
40+
// so we can use binary-search-style trimming instead of per-element RemoveAt.
41+
42+
// Trim future timestamps caused by rewinding (remove from the end in one batch).
43+
while (timestamps.Count > 0 && timestamps[^1] > latestValidTime)
44+
timestamps.RemoveAt(timestamps.Count - 1);
45+
46+
// Count timestamps within the valid 1-second window.
47+
// Since the list is in chronological order, scan backwards until we leave the window.
3948
int count = 0;
4049

4150
for (int i = timestamps.Count - 1; i >= 0; i--)
4251
{
43-
// handle rewinding by removing future timestamps as we go
44-
if (timestamps[i] > latestValidTime)
45-
{
46-
timestamps.RemoveAt(i);
47-
continue;
48-
}
49-
50-
if (timestamps[i] >= earliestTimeValid)
51-
count++;
52+
if (timestamps[i] < earliestTimeValid)
53+
break;
54+
55+
count++;
5256
}
5357

5458
Value = count;

0 commit comments

Comments
 (0)