@@ -38,7 +38,9 @@ public float Duration
3838
3939 private float _duration ;
4040 private bool _isRunning ;
41- private SortedList < float , TimerMilestone > _milestones = new SortedList < float , TimerMilestone > ( ) ;
41+ private bool _processingMilestones ;
42+ private Dictionary < Guid , TimerMilestone > _milestonesById = new Dictionary < Guid , TimerMilestone > ( ) ;
43+ private SortedList < float , List < Guid > > _milestonesByTriggerValue = new SortedList < float , List < Guid > > ( ) ;
4244
4345 /// <summary>
4446 /// Initializes a new instance of the SimpleTimer class with a specified duration.
@@ -94,11 +96,17 @@ public virtual void Update(float deltaTime)
9496 {
9597 if ( ! _isRunning ) return ;
9698
99+ // Store the original time remaining to check if we've reached zero
100+ var originalTimeRemaining = TimeRemaining ;
101+
102+ // Update the time remaining
97103 TimeRemaining -= deltaTime ;
104+
98105 CheckAndTriggerMilestones ( ) ;
99106 OnTick ? . Invoke ( this ) ;
100107
101- if ( ! ( TimeRemaining <= 0 ) ) return ;
108+ // Handle completion if we've reached zero
109+ if ( ! ( originalTimeRemaining > 0 ) || ! ( TimeRemaining <= 0 ) ) return ;
102110
103111 _isRunning = false ;
104112 TimeRemaining = 0 ;
@@ -107,11 +115,74 @@ public virtual void Update(float deltaTime)
107115
108116 protected virtual void CheckAndTriggerMilestones ( )
109117 {
110- while ( _milestones . Count > 0 && ShouldTrigger ( _milestones . Values [ 0 ] ) )
118+ if ( _processingMilestones ) return ;
119+ _processingMilestones = true ;
120+
121+ try
122+ {
123+ // Process milestones in order of their trigger values
124+ var processedTriggerValues = new HashSet < float > ( ) ;
125+
126+ while ( true )
127+ {
128+ float ? nextTriggerValue = null ;
129+
130+ foreach ( var key in _milestonesByTriggerValue . Keys )
131+ {
132+ if ( processedTriggerValues . Contains ( key ) ) continue ;
133+
134+ // Get a sample milestone to check if we should process this trigger value
135+ var sampleId = _milestonesByTriggerValue [ key ] [ 0 ] ;
136+ var sampleMilestone = _milestonesById [ sampleId ] ;
137+
138+ if ( ! ShouldTrigger ( sampleMilestone ) ) continue ;
139+
140+ if ( nextTriggerValue == null || key < nextTriggerValue . Value )
141+ {
142+ nextTriggerValue = key ;
143+ }
144+ }
145+
146+ if ( nextTriggerValue == null ) break ;
147+
148+ // Mark this trigger value as processed
149+ processedTriggerValues . Add ( nextTriggerValue . Value ) ;
150+ ProcessMilestonesAtTriggerValue ( nextTriggerValue . Value ) ;
151+ }
152+ }
153+ finally
154+ {
155+ _processingMilestones = false ;
156+ }
157+ }
158+
159+ private void ProcessMilestonesAtTriggerValue ( float triggerValue )
160+ {
161+ if ( ! _milestonesByTriggerValue . TryGetValue ( triggerValue , out var triggerIds ) || triggerIds . Count == 0 ) return ;
162+
163+ var milestonesToTrigger = new List < TimerMilestone > ( triggerIds . Count ) ;
164+ var idsToRemove = new List < Guid > ( triggerIds ) ;
165+
166+ // Collect valid milestones
167+ foreach ( var id in idsToRemove )
168+ {
169+ if ( _milestonesById . TryGetValue ( id , out var milestone ) )
170+ {
171+ milestonesToTrigger . Add ( milestone ) ;
172+ }
173+ }
174+
175+ // Remove all milestones for this trigger value from collections
176+ _milestonesByTriggerValue . Remove ( triggerValue ) ;
177+
178+ foreach ( var id in idsToRemove )
179+ {
180+ _milestonesById . Remove ( id ) ;
181+ }
182+
183+ foreach ( var milestone in milestonesToTrigger )
111184 {
112- var milestone = _milestones . Values [ 0 ] ;
113185 milestone . Callback ? . Invoke ( ) ;
114- _milestones . RemoveAt ( 0 ) ;
115186 }
116187 }
117188
@@ -151,8 +222,19 @@ public virtual void ResetTimer()
151222 /// <param name="seconds">The number of seconds to fast forward.</param>
152223 public virtual void FastForward ( float seconds )
153224 {
225+ if ( seconds < 0 ) return ; // Ignore negative values
226+
227+ // Store original time remaining to check for completion
228+ float originalTimeRemaining = TimeRemaining ;
229+
230+ // Update time remaining
154231 TimeRemaining -= seconds ;
155- if ( TimeRemaining <= 0 )
232+
233+ // Check and trigger milestones based on the new time
234+ CheckAndTriggerMilestones ( ) ;
235+
236+ // Handle completion if we've reached zero
237+ if ( originalTimeRemaining > 0 && TimeRemaining <= 0 )
156238 {
157239 TimeRemaining = 0 ;
158240 _isRunning = false ;
@@ -170,6 +252,8 @@ public virtual void FastForward(float seconds)
170252 /// <param name="seconds">The number of seconds to rewind.</param>
171253 public virtual void Rewind ( float seconds )
172254 {
255+ if ( seconds < 0 ) return ; // Ignore negative values
256+
173257 TimeRemaining += seconds ;
174258 if ( TimeRemaining > Duration )
175259 {
@@ -185,43 +269,101 @@ public virtual void Rewind(float seconds)
185269 /// <param name="milestone">The milestone to add to the timer.</param>
186270 public virtual void AddMilestone ( TimerMilestone milestone )
187271 {
188- _milestones . Add ( milestone . TriggerValue , milestone ) ;
272+ if ( milestone == null ) return ;
273+
274+ // Use a combination of trigger value and a unique part to ensure key uniqueness
275+ // This prevents issues in the original code where milestones with the same trigger value
276+ // would overwrite each other in the SortedList
277+ var milestoneId = Guid . NewGuid ( ) ;
278+ _milestonesById [ milestoneId ] = milestone ;
279+
280+ if ( ! _milestonesByTriggerValue . TryGetValue ( milestone . TriggerValue , out var milestoneList ) )
281+ {
282+ milestoneList = new List < Guid > ( ) ;
283+ _milestonesByTriggerValue [ milestone . TriggerValue ] = milestoneList ;
284+ }
285+
286+ milestoneList . Add ( milestoneId ) ;
189287 }
190288
191289 public virtual void RemoveMilestone ( TimerMilestone milestone )
192290 {
193- _milestones . Remove ( milestone . TriggerValue ) ;
291+ if ( milestone == null ) return ;
292+
293+ // Find the milestone ID
294+ Guid ? milestoneIdToRemove = null ;
295+ foreach ( var pair in _milestonesById )
296+ {
297+ if ( pair . Value == milestone )
298+ {
299+ milestoneIdToRemove = pair . Key ;
300+ break ;
301+ }
302+ }
303+
304+ if ( ! milestoneIdToRemove . HasValue ) return ;
305+
306+ // Remove from both collections
307+ _milestonesById . Remove ( milestoneIdToRemove . Value ) ;
308+
309+ // Find and remove from trigger list
310+ if ( _milestonesByTriggerValue . TryGetValue ( milestone . TriggerValue , out var list ) )
311+ {
312+ list . Remove ( milestoneIdToRemove . Value ) ;
313+ if ( list . Count == 0 )
314+ {
315+ _milestonesByTriggerValue . Remove ( milestone . TriggerValue ) ;
316+ }
317+ }
194318 }
195319
196320 public virtual void RemoveMilestones ( TimerMilestone [ ] milestones )
197321 {
198322 foreach ( var milestone in milestones )
199323 {
200- _milestones . Remove ( milestone . TriggerValue ) ;
324+ RemoveMilestone ( milestone ) ;
201325 }
202326 }
203327
204328 public virtual void RemoveAllMilestones ( )
205329 {
206- _milestones . Clear ( ) ;
330+ _milestonesById . Clear ( ) ;
331+ _milestonesByTriggerValue . Clear ( ) ;
207332 }
208333
209334 public virtual void RemoveMilestonesByCondition ( Predicate < TimerMilestone > condition )
210335 {
211- // Collect keys to remove to avoid modifying the collection during iteration
212- var keysToRemove = new List < float > ( ) ;
213- foreach ( var pair in _milestones )
336+ if ( condition == null ) return ;
337+
338+ var milestonesToRemove = new List < Guid > ( ) ;
339+
340+ // Find all milestones matching the condition
341+ foreach ( var pair in _milestonesById )
214342 {
215343 if ( condition ( pair . Value ) )
216344 {
217- keysToRemove . Add ( pair . Key ) ;
345+ milestonesToRemove . Add ( pair . Key ) ;
218346 }
219347 }
220-
221- // Remove all identified keys
222- foreach ( var key in keysToRemove )
348+
349+ // Remove each milestone
350+ foreach ( var id in milestonesToRemove )
223351 {
224- _milestones . Remove ( key ) ;
352+ if ( _milestonesById . TryGetValue ( id , out var milestone ) )
353+ {
354+ // First remove from the trigger list
355+ if ( _milestonesByTriggerValue . TryGetValue ( milestone . TriggerValue , out var list ) )
356+ {
357+ list . Remove ( id ) ;
358+ if ( list . Count == 0 )
359+ {
360+ _milestonesByTriggerValue . Remove ( milestone . TriggerValue ) ;
361+ }
362+ }
363+
364+ // Then remove from the ID dictionary
365+ _milestonesById . Remove ( id ) ;
366+ }
225367 }
226368 }
227369
0 commit comments