File tree Expand file tree Collapse file tree
Screens/Play/HUD/ClicksPerSecond Expand file tree Collapse file tree Original file line number Diff line number Diff 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>
Original file line number Diff line number Diff 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 ;
Original file line number Diff line number Diff 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 ;
You can’t perform that action at this time.
0 commit comments