@@ -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