Skip to content

Commit 28bd032

Browse files
perf: eliminate LINQ allocations in GameplaySampleTriggerSource and ColourHitErrorMeter hot paths
Agent-Logs-Url: https://github.com/winnerspiros/osu/sessions/b0878070-74cc-41e1-af2d-af6fb8954594 Co-authored-by: winnerspiros <1675249+winnerspiros@users.noreply.github.com>
1 parent da41f94 commit 28bd032

2 files changed

Lines changed: 44 additions & 7 deletions

File tree

osu.Game/Rulesets/UI/GameplaySampleTriggerSource.cs

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -102,10 +102,25 @@ protected override void Update()
102102
{
103103
// We need to use lifetime entries to find the next object (we can't just use `hitObjectContainer.Objects` due to pooling - it may even be empty).
104104
// If required, we can make this lookup more efficient by adding support to get next-future-entry in LifetimeEntryManager.
105-
var candidate =
106-
// Use alive entries first as an optimisation.
107-
hitObjectContainer.AliveEntries.Keys.Where(e => !isAlreadyHit(e)).MinBy(e => e.HitObject.StartTime)
108-
?? hitObjectContainer.Entries.Where(e => !isAlreadyHit(e)).MinBy(e => e.HitObject.StartTime);
105+
106+
// Use alive entries first as an optimisation (single-pass minimum, no LINQ allocation).
107+
HitObjectLifetimeEntry? candidate = null;
108+
109+
foreach (var e in hitObjectContainer.AliveEntries.Keys)
110+
{
111+
if (!isAlreadyHit(e) && (candidate == null || e.HitObject.StartTime < candidate.HitObject.StartTime))
112+
candidate = e;
113+
}
114+
115+
// Fall back to full entries if no alive non-judged entry found.
116+
if (candidate == null)
117+
{
118+
foreach (var e in hitObjectContainer.Entries)
119+
{
120+
if (!isAlreadyHit(e) && (candidate == null || e.HitObject.StartTime < candidate.HitObject.StartTime))
121+
candidate = e;
122+
}
123+
}
109124

110125
// In the case there are no non-judged objects, the last hit object should be used instead.
111126
if (candidate == null)

osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -115,10 +115,32 @@ public void Push(HitErrorShape shape)
115115

116116
private void removeExtraJudgements()
117117
{
118-
var remainingChildren = Children.Where(c => !c.IsRemoved);
118+
// Count non-removed children and remove excess starting from the oldest.
119+
// This avoids re-enumerating via LINQ .Count()/.First() on every iteration.
120+
int remaining = 0;
119121

120-
while (remainingChildren.Count() > JudgementCount.Value)
121-
remainingChildren.First().Remove();
122+
foreach (var c in Children)
123+
{
124+
if (!c.IsRemoved)
125+
remaining++;
126+
}
127+
128+
int target = JudgementCount.Value;
129+
130+
if (remaining <= target)
131+
return;
132+
133+
foreach (var c in Children)
134+
{
135+
if (remaining <= target)
136+
break;
137+
138+
if (!c.IsRemoved)
139+
{
140+
c.Remove();
141+
remaining--;
142+
}
143+
}
122144
}
123145

124146
private void updateMetrics()

0 commit comments

Comments
 (0)