44using System . Linq ;
55using System . Collections ;
66using System . Collections . Generic ;
7- using System . Collections . ObjectModel ;
8- using System . Collections . Specialized ;
97using System . Diagnostics ;
8+ using System . Runtime . CompilerServices ;
109
1110namespace BizHawk . Emulation . Common
1211{
@@ -24,14 +23,17 @@ public MemoryCallbackSystem(string[] availableScopes)
2423 AvailableScopes = availableScopes ;
2524 ExecuteCallbacksAvailable = true ;
2625
27- _reads . CollectionChanged += OnCollectionChanged ;
28- _writes . CollectionChanged += OnCollectionChanged ;
29- _execs . CollectionChanged += OnCollectionChanged ;
26+ _reads . ItemAdded += OnCallbackAdded ;
27+ _reads . ItemRemoved += OnCallbackRemoved ;
28+ _writes . ItemAdded += OnCallbackAdded ;
29+ _writes . ItemRemoved += OnCallbackRemoved ;
30+ _execs . ItemAdded += OnCallbackAdded ;
31+ _execs . ItemRemoved += OnCallbackRemoved ;
3032 }
3133
32- private readonly ObservableCollection < IMemoryCallback > _reads = new ObservableCollection < IMemoryCallback > ( ) ;
33- private readonly ObservableCollection < IMemoryCallback > _writes = new ObservableCollection < IMemoryCallback > ( ) ;
34- private readonly ObservableCollection < IMemoryCallback > _execs = new ObservableCollection < IMemoryCallback > ( ) ;
34+ private readonly MemoryCallbackCollection _reads = new ( ) ;
35+ private readonly MemoryCallbackCollection _writes = new ( ) ;
36+ private readonly MemoryCallbackCollection _execs = new ( ) ;
3537
3638 private bool _hasAny ;
3739
@@ -66,40 +68,15 @@ public void Add(IMemoryCallback callback)
6668 }
6769 }
6870
69- private static void Call ( ObservableCollection < IMemoryCallback > cbs , uint addr , uint value , uint flags , string scope )
71+ private static void Call ( MemoryCallbackCollection cbs , uint addr , uint value , uint flags , string scope )
7072 {
71- var cbsCopy = cbs . ToArray ( ) ;
72- var e = cbsCopy . Length ;
73- var i = - 1 ;
74- void UpdateIndexAndEndpoint ( object _ , NotifyCollectionChangedEventArgs args )
73+ foreach ( var cb in cbs )
7574 {
76- // this was helpful: https://www.codeproject.com/Articles/1004644/ObservableCollection-Simply-Explained
77- switch ( args . Action )
78- {
79- case NotifyCollectionChangedAction . Add :
80- Debug . Assert ( args . NewStartingIndex >= e ) ;
81- // no-op
82- break ;
83- case NotifyCollectionChangedAction . Remove :
84- Debug . Assert ( args . OldItems . Count is 1 ) ;
85- e -- ;
86- if ( args . OldStartingIndex <= i ) i -- ; // if ==, i will be decremented and incremented, so it ends up pointing to the element which was at i + 1, now at i
87- break ;
88- default :
89- Debug . WriteLine ( "Unexpected operation on memory callback collection!" ) ;
90- break ;
91- }
92- }
93- cbs . CollectionChanged += UpdateIndexAndEndpoint ;
94- while ( ++ i < e )
95- {
96- var cb = cbsCopy [ i ] ;
9775 if ( ! cb . Address . HasValue || ( cb . Scope == scope && cb . Address == ( addr & cb . AddressMask ) ) )
9876 {
9977 cb . Callback ( addr , value , flags ) ;
10078 }
10179 }
102- cbs . CollectionChanged -= UpdateIndexAndEndpoint ;
10380 }
10481
10582 public void CallMemoryCallbacks ( uint addr , uint value , uint flags , string scope )
@@ -169,33 +146,18 @@ private bool UpdateHasVariables()
169146 return HasReads != hadReads || HasWrites != hadWrites || HasExecutes != hadExecutes ;
170147 }
171148
172- private int RemoveInternal ( MemoryCallbackDelegate action )
149+ private bool RemoveInternal ( MemoryCallbackDelegate action )
173150 {
174- var readsToRemove = _reads . Where ( imc => imc . Callback == action ) . ToList ( ) ;
175- var writesToRemove = _writes . Where ( imc => imc . Callback == action ) . ToList ( ) ;
176- var execsToRemove = _execs . Where ( imc => imc . Callback == action ) . ToList ( ) ;
177-
178- foreach ( var read in readsToRemove )
179- {
180- _reads . Remove ( read ) ;
181- }
182-
183- foreach ( var write in writesToRemove )
184- {
185- _writes . Remove ( write ) ;
186- }
187-
188- foreach ( var exec in execsToRemove )
189- {
190- _execs . Remove ( exec ) ;
191- }
192-
193- return readsToRemove . Count + writesToRemove . Count + execsToRemove . Count ;
151+ bool anyRemoved = false ;
152+ anyRemoved |= _reads . Remove ( action ) ;
153+ anyRemoved |= _writes . Remove ( action ) ;
154+ anyRemoved |= _execs . Remove ( action ) ;
155+ return anyRemoved ;
194156 }
195157
196158 public void Remove ( MemoryCallbackDelegate action )
197159 {
198- if ( RemoveInternal ( action ) > 0 )
160+ if ( RemoveInternal ( action ) )
199161 {
200162 if ( UpdateHasVariables ( ) )
201163 {
@@ -209,7 +171,7 @@ public void RemoveAll(IEnumerable<MemoryCallbackDelegate> actions)
209171 bool changed = false ;
210172 foreach ( var action in actions )
211173 {
212- changed |= RemoveInternal ( action ) > 0 ;
174+ changed |= RemoveInternal ( action ) ;
213175 }
214176
215177 if ( changed )
@@ -223,21 +185,9 @@ public void RemoveAll(IEnumerable<MemoryCallbackDelegate> actions)
223185
224186 public void Clear ( )
225187 {
226- // Remove one-by-one to avoid NotifyCollectionChangedAction.Reset events.
227- for ( int i = _reads . Count - 1 ; i >= 0 ; i -- )
228- {
229- _reads . RemoveAt ( i ) ;
230- }
231-
232- for ( int i = _writes . Count - 1 ; i >= 0 ; i -- )
233- {
234- _writes . RemoveAt ( i ) ;
235- }
236-
237- for ( int i = _execs . Count - 1 ; i >= 0 ; i -- )
238- {
239- _execs . RemoveAt ( i ) ;
240- }
188+ _reads . Clear ( ) ;
189+ _writes . Clear ( ) ;
190+ _execs . Clear ( ) ;
241191
242192 if ( UpdateHasVariables ( ) )
243193 {
@@ -259,25 +209,14 @@ private void Changes()
259209 ActiveChanged ? . Invoke ( ) ;
260210 }
261211
262- public void OnCollectionChanged ( object sender , NotifyCollectionChangedEventArgs args )
212+ private void OnCallbackAdded ( object sender , IMemoryCallback callback )
263213 {
264- switch ( args . Action )
265- {
266- case NotifyCollectionChangedAction . Add :
267- foreach ( IMemoryCallback callback in args . NewItems )
268- {
269- CallbackAdded ? . Invoke ( callback ) ;
270- }
271-
272- break ;
273- case NotifyCollectionChangedAction . Remove :
274- foreach ( IMemoryCallback callback in args . OldItems )
275- {
276- CallbackRemoved ? . Invoke ( callback ) ;
277- }
214+ CallbackAdded ? . Invoke ( callback ) ;
215+ }
278216
279- break ;
280- }
217+ private void OnCallbackRemoved ( object sender , IMemoryCallback callback )
218+ {
219+ CallbackRemoved ? . Invoke ( callback ) ;
281220 }
282221
283222 public IEnumerator < IMemoryCallback > GetEnumerator ( )
@@ -336,4 +275,171 @@ public MemoryCallback(string scope, MemoryCallbackType type, string name, Memory
336275 public uint ? AddressMask { get ; }
337276 public string Scope { get ; }
338277 }
278+
279+ /// <summary>
280+ /// Specialized collection for memory callbacks with add/remove events and copy-on-write behavior during enumeration.
281+ /// </summary>
282+ /// <remarks>
283+ /// Reentrancy from ItemAdded and ItemRemoved events is not allowed.
284+ /// </remarks>
285+ internal class MemoryCallbackCollection : IReadOnlyCollection < IMemoryCallback >
286+ {
287+ private List < IMemoryCallback > _items = new ( ) ;
288+ private int _copyOnWriteRequired = 0 ;
289+ private bool _modifyInProgress = false ;
290+
291+ public int Count => _items . Count ;
292+
293+ public void Add ( IMemoryCallback item )
294+ {
295+ BeginModify ( ) ;
296+ try
297+ {
298+ CopyIfRequired ( ) ;
299+
300+ _items . Add ( item ) ;
301+ ItemAdded ? . Invoke ( this , item ) ;
302+ }
303+ finally
304+ {
305+ EndModify ( ) ;
306+ }
307+ }
308+
309+ private void RemoveAtInternal ( int index )
310+ {
311+ Debug . Assert ( _modifyInProgress ) ;
312+ CopyIfRequired ( ) ;
313+
314+ var removedItem = _items [ index ] ;
315+ _items . RemoveAt ( index ) ;
316+ ItemRemoved ? . Invoke ( this , removedItem ) ;
317+ }
318+
319+ public bool Remove ( MemoryCallbackDelegate callback )
320+ {
321+ BeginModify ( ) ;
322+ try
323+ {
324+ int removed = 0 ;
325+ for ( int i = 0 ; i < _items . Count ; )
326+ {
327+ if ( _items [ i ] . Callback == callback )
328+ {
329+ RemoveAtInternal ( i ) ;
330+ removed ++ ;
331+ }
332+ else
333+ {
334+ i ++ ;
335+ }
336+ }
337+ return removed > 0 ;
338+ }
339+ finally
340+ {
341+ EndModify ( ) ;
342+ }
343+ }
344+
345+ public void Clear ( )
346+ {
347+ BeginModify ( ) ;
348+ try
349+ {
350+ while ( Count > 0 )
351+ RemoveAtInternal ( Count - 1 ) ;
352+ }
353+ finally
354+ {
355+ EndModify ( ) ;
356+ }
357+ }
358+
359+ private void CopyIfRequired ( )
360+ {
361+ if ( _copyOnWriteRequired > 0 )
362+ {
363+ _items = new List < IMemoryCallback > ( _items ) ;
364+ }
365+ }
366+
367+ private void CheckModifyReentrancy ( )
368+ {
369+ if ( _modifyInProgress )
370+ throw new InvalidOperationException ( "Reentrancy in MemoryCallbackCollection ItemAdded/ItemRemoved is not allowed." ) ;
371+ }
372+
373+ private void BeginModify ( )
374+ {
375+ CheckModifyReentrancy ( ) ;
376+ _modifyInProgress = true ;
377+ }
378+
379+ private void EndModify ( )
380+ {
381+ _modifyInProgress = false ;
382+ }
383+
384+ private void BeginCopyOnWrite ( )
385+ {
386+ _copyOnWriteRequired ++ ;
387+ }
388+
389+ private void EndCopyOnWrite ( )
390+ {
391+ _copyOnWriteRequired -- ;
392+ Debug . Assert ( _copyOnWriteRequired >= 0 ) ;
393+ }
394+
395+ public Enumerator GetEnumerator ( )
396+ {
397+ CheckModifyReentrancy ( ) ;
398+ return new Enumerator ( this ) ;
399+ }
400+
401+ IEnumerator < IMemoryCallback > IEnumerable < IMemoryCallback > . GetEnumerator ( ) => GetEnumerator ( ) ;
402+
403+ IEnumerator IEnumerable . GetEnumerator ( ) => GetEnumerator ( ) ;
404+
405+ public EventHandler < IMemoryCallback > ItemAdded ;
406+ public EventHandler < IMemoryCallback > ItemRemoved ;
407+
408+
409+
410+ /// <remarks>
411+ /// This struct must not be copied.
412+ /// </remarks>
413+ public struct Enumerator : IEnumerator < IMemoryCallback > , IDisposable
414+ {
415+ private readonly MemoryCallbackCollection _collection ;
416+ private List < IMemoryCallback > _items ;
417+ private int _position ;
418+
419+ public Enumerator ( MemoryCallbackCollection collection )
420+ {
421+ _collection = collection ;
422+ _items = collection . _items ;
423+ _position = - 1 ;
424+ _collection . BeginCopyOnWrite ( ) ;
425+ }
426+
427+ public readonly IMemoryCallback Current => _items [ _position ] ;
428+
429+ object IEnumerator . Current => Current ;
430+
431+ public bool MoveNext ( ) => ++ _position < _items . Count ;
432+
433+ public void Dispose ( )
434+ {
435+ if ( _items != null )
436+ {
437+ _items = null ;
438+ _collection . EndCopyOnWrite ( ) ;
439+ }
440+ }
441+
442+ void IEnumerator . Reset ( ) => throw new NotSupportedException ( ) ;
443+ }
444+ }
339445}
0 commit comments