Skip to content

Commit 565a0b9

Browse files
kalimagYoshiRulz
authored andcommitted
Implement specialized IMemoryCallback collection
1 parent 06c06dd commit 565a0b9

File tree

1 file changed

+196
-90
lines changed

1 file changed

+196
-90
lines changed

src/BizHawk.Emulation.Common/Base Implementations/MemoryCallbackSystem.cs

Lines changed: 196 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,8 @@
44
using System.Linq;
55
using System.Collections;
66
using System.Collections.Generic;
7-
using System.Collections.ObjectModel;
8-
using System.Collections.Specialized;
97
using System.Diagnostics;
8+
using System.Runtime.CompilerServices;
109

1110
namespace 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

Comments
 (0)