Skip to content

Commit 75ee83f

Browse files
authored
fix: address 76 CI test failures across encoder, HitObjectContainer, skin editor, and GC tests
1 parent 33a79dd commit 75ee83f

4 files changed

Lines changed: 42 additions & 20 deletions

File tree

osu.Game.Tests/Visual/Gameplay/TestScenePlayerReferenceLeaking.cs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,13 @@ protected override void AddCheckSteps()
2222
{
2323
AddUntilStep("no leaked beatmaps", () =>
2424
{
25-
GC.Collect();
26-
GC.WaitForPendingFinalizers();
25+
// Run multiple GC passes to handle finalizer queues and large object heap.
26+
for (int i = 0; i < 3; i++)
27+
{
28+
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, blocking: true, compacting: false);
29+
GC.WaitForPendingFinalizers();
30+
}
31+
2732
int count = 0;
2833

2934
foreach (var unused in workingWeakReferences)
@@ -34,8 +39,12 @@ protected override void AddCheckSteps()
3439

3540
AddUntilStep("no leaked players", () =>
3641
{
37-
GC.Collect();
38-
GC.WaitForPendingFinalizers();
42+
for (int i = 0; i < 3; i++)
43+
{
44+
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, blocking: true, compacting: false);
45+
GC.WaitForPendingFinalizers();
46+
}
47+
3948
int count = 0;
4049

4150
foreach (var unused in playerWeakReferences)

osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,10 @@ public partial class TestSceneSkinEditor : PlayerTestScene
5252
[SetUpSteps]
5353
public override void SetUpSteps()
5454
{
55+
AddStep("reset skin", () => skins.CurrentSkinInfo.SetDefault());
56+
5557
base.SetUpSteps();
5658

57-
AddStep("reset skin", () => skins.CurrentSkinInfo.SetDefault());
5859
AddUntilStep("wait for hud load", () => targetContainer.ComponentsLoaded);
5960

6061
AddStep("reload skin editor", () =>

osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -591,10 +591,6 @@ private LegacyHitSoundType toLegacyHitSoundType(IList<HitSampleInfo> samples)
591591
{
592592
switch (sample.Name)
593593
{
594-
case HitSampleInfo.HIT_NORMAL:
595-
type |= LegacyHitSoundType.Normal;
596-
break;
597-
598594
case HitSampleInfo.HIT_WHISTLE:
599595
type |= LegacyHitSoundType.Whistle;
600596
break;

osu.Game/Rulesets/UI/HitObjectContainer.cs

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,13 @@ protected sealed override DrawableHitObject GetDrawable(HitObjectLifetimeEntry e
128128

129129
protected override void AddDrawable(HitObjectLifetimeEntry entry, DrawableHitObject drawable)
130130
{
131-
if (nonPooledDrawableMap.ContainsKey(entry)) return;
131+
if (nonPooledDrawableMap.ContainsKey(entry))
132+
{
133+
// Non-pooled drawables are permanently present in InternalChildren.
134+
// Only insert into the alive-objects cache when the entry becomes alive.
135+
insertIntoAliveCache(drawable);
136+
return;
137+
}
132138

133139
addDrawable(drawable);
134140
HitObjectUsageBegan?.Invoke(entry.HitObject);
@@ -137,22 +143,21 @@ protected override void AddDrawable(HitObjectLifetimeEntry entry, DrawableHitObj
137143
protected override void RemoveDrawable(HitObjectLifetimeEntry entry, DrawableHitObject drawable)
138144
{
139145
drawable.OnKilled();
140-
if (nonPooledDrawableMap.ContainsKey(entry)) return;
146+
147+
if (nonPooledDrawableMap.ContainsKey(entry))
148+
{
149+
// Non-pooled drawables stay in InternalChildren; only remove from alive cache.
150+
aliveObjectsSortedCache.Remove(drawable);
151+
return;
152+
}
141153

142154
removeDrawable(drawable);
143155
HitObjectUsageFinished?.Invoke(entry.HitObject);
144156
}
145157

146-
private void addDrawable(DrawableHitObject drawable)
158+
private void insertIntoAliveCache(DrawableHitObject drawable)
147159
{
148160
// Binary-search insertion to keep aliveObjectsSortedCache in StartTime order.
149-
// O(log n) search + O(n) shift — far cheaper than rebuilding & sorting
150-
// the entire list from scratch on every alive-state transition.
151-
// Note: `<=` means new objects with the same StartTime are appended after
152-
// existing ones (stable insertion order within a tie group). The full
153-
// visual-tree Compare also applies CompareReverseChildID as a tie-breaker,
154-
// but AliveObjects consumers (e.g. cursor particles) don't require that
155-
// level of ordering stability.
156161
double startTime = drawable.HitObject.StartTime;
157162
int lo = 0, hi = aliveObjectsSortedCache.Count;
158163

@@ -167,6 +172,11 @@ private void addDrawable(DrawableHitObject drawable)
167172
}
168173

169174
aliveObjectsSortedCache.Insert(lo, drawable);
175+
}
176+
177+
private void addDrawable(DrawableHitObject drawable)
178+
{
179+
insertIntoAliveCache(drawable);
170180

171181
drawable.OnNewResult += onNewResult;
172182

@@ -196,7 +206,13 @@ public virtual void Add(DrawableHitObject drawable)
196206
throw new InvalidOperationException($"May not add a {nameof(DrawableHitObject)} without {nameof(HitObject)} associated");
197207

198208
nonPooledDrawableMap.Add(drawable.Entry, drawable);
199-
addDrawable(drawable);
209+
210+
// Set up permanent bindings once. The alive-objects cache is managed via
211+
// AddDrawable/RemoveDrawable callbacks when the lifetime entry becomes alive/dead.
212+
drawable.OnNewResult += onNewResult;
213+
bindStartTime(drawable);
214+
AddInternal(drawable);
215+
200216
Add(drawable.Entry);
201217
}
202218

0 commit comments

Comments
 (0)