4
4
using System . Linq ;
5
5
using System . Collections ;
6
6
using System . Collections . Generic ;
7
- using System . Collections . ObjectModel ;
8
- using System . Collections . Specialized ;
9
7
using System . Diagnostics ;
8
+ using System . Runtime . CompilerServices ;
10
9
11
10
namespace BizHawk . Emulation . Common
12
11
{
@@ -24,14 +23,17 @@ public MemoryCallbackSystem(string[] availableScopes)
24
23
AvailableScopes = availableScopes ;
25
24
ExecuteCallbacksAvailable = true ;
26
25
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 ;
30
32
}
31
33
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 ( ) ;
35
37
36
38
private bool _hasAny ;
37
39
@@ -66,40 +68,15 @@ public void Add(IMemoryCallback callback)
66
68
}
67
69
}
68
70
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 )
70
72
{
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 )
75
74
{
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 ] ;
97
75
if ( ! cb . Address . HasValue || ( cb . Scope == scope && cb . Address == ( addr & cb . AddressMask ) ) )
98
76
{
99
77
cb . Callback ( addr , value , flags ) ;
100
78
}
101
79
}
102
- cbs . CollectionChanged -= UpdateIndexAndEndpoint ;
103
80
}
104
81
105
82
public void CallMemoryCallbacks ( uint addr , uint value , uint flags , string scope )
@@ -169,33 +146,18 @@ private bool UpdateHasVariables()
169
146
return HasReads != hadReads || HasWrites != hadWrites || HasExecutes != hadExecutes ;
170
147
}
171
148
172
- private int RemoveInternal ( MemoryCallbackDelegate action )
149
+ private bool RemoveInternal ( MemoryCallbackDelegate action )
173
150
{
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 ;
194
156
}
195
157
196
158
public void Remove ( MemoryCallbackDelegate action )
197
159
{
198
- if ( RemoveInternal ( action ) > 0 )
160
+ if ( RemoveInternal ( action ) )
199
161
{
200
162
if ( UpdateHasVariables ( ) )
201
163
{
@@ -209,7 +171,7 @@ public void RemoveAll(IEnumerable<MemoryCallbackDelegate> actions)
209
171
bool changed = false ;
210
172
foreach ( var action in actions )
211
173
{
212
- changed |= RemoveInternal ( action ) > 0 ;
174
+ changed |= RemoveInternal ( action ) ;
213
175
}
214
176
215
177
if ( changed )
@@ -223,21 +185,9 @@ public void RemoveAll(IEnumerable<MemoryCallbackDelegate> actions)
223
185
224
186
public void Clear ( )
225
187
{
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 ( ) ;
241
191
242
192
if ( UpdateHasVariables ( ) )
243
193
{
@@ -259,25 +209,14 @@ private void Changes()
259
209
ActiveChanged ? . Invoke ( ) ;
260
210
}
261
211
262
- public void OnCollectionChanged ( object sender , NotifyCollectionChangedEventArgs args )
212
+ private void OnCallbackAdded ( object sender , IMemoryCallback callback )
263
213
{
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
+ }
278
216
279
- break ;
280
- }
217
+ private void OnCallbackRemoved ( object sender , IMemoryCallback callback )
218
+ {
219
+ CallbackRemoved ? . Invoke ( callback ) ;
281
220
}
282
221
283
222
public IEnumerator < IMemoryCallback > GetEnumerator ( )
@@ -336,4 +275,171 @@ public MemoryCallback(string scope, MemoryCallbackType type, string name, Memory
336
275
public uint ? AddressMask { get ; }
337
276
public string Scope { get ; }
338
277
}
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
+ }
339
445
}
0 commit comments