From f94156f564498d06bdc1a7b3f73b4a9098130425 Mon Sep 17 00:00:00 2001 From: neuecc Date: Tue, 24 Aug 2021 19:44:23 +0900 Subject: [PATCH] or concept --- .../NotifyCollectionChangedEventArgs.cs | 3 + .../ObservableRingBuffer.Views.cs | 217 ++++++++++++++++++ .../ObservableRingBuffer.cs | 132 ++++++++++- src/ObservableCollections/RingBuffer.cs | 10 +- 4 files changed, 355 insertions(+), 7 deletions(-) create mode 100644 src/ObservableCollections/ObservableRingBuffer.Views.cs diff --git a/src/ObservableCollections/NotifyCollectionChangedEventArgs.cs b/src/ObservableCollections/NotifyCollectionChangedEventArgs.cs index 874ea37..6e1138b 100644 --- a/src/ObservableCollections/NotifyCollectionChangedEventArgs.cs +++ b/src/ObservableCollections/NotifyCollectionChangedEventArgs.cs @@ -36,6 +36,9 @@ namespace ObservableCollections public readonly int NewStartingIndex; public readonly int OldStartingIndex; + // TODO:is this required? + // byte distinguishedKey; + public NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction action, bool isSingleItem, T newItem = default!, T oldItem = default!, ReadOnlySpan newItems = default, ReadOnlySpan oldItems = default, int newStartingIndex = -1, int oldStartingIndex = -1) { Action = action; diff --git a/src/ObservableCollections/ObservableRingBuffer.Views.cs b/src/ObservableCollections/ObservableRingBuffer.Views.cs new file mode 100644 index 0000000..9811569 --- /dev/null +++ b/src/ObservableCollections/ObservableRingBuffer.Views.cs @@ -0,0 +1,217 @@ +using ObservableCollections.Internal; +using System.Collections; +using System.Collections.Specialized; + +namespace ObservableCollections +{ + public sealed partial class ObservableRingBuffer + { + public ISynchronizedView CreateView(Func transform, bool reverse = false) + { + return new View(this, transform, reverse); + } + + sealed class View : ISynchronizedView + { + readonly ObservableRingBuffer source; + readonly Func selector; + readonly bool reverse; + readonly RingBuffer<(T, TView)> ringBuffer; + + ISynchronizedViewFilter filter; + + public event NotifyCollectionChangedEventHandler? RoutingCollectionChanged; + public event Action? CollectionStateChanged; + + public object SyncRoot { get; } + + public View(ObservableRingBuffer source, Func selector, bool reverse) + { + this.source = source; + this.selector = selector; + this.reverse = reverse; + this.filter = SynchronizedViewFilter.Null; + this.SyncRoot = new object(); + lock (source.SyncRoot) + { + this.ringBuffer = new RingBuffer<(T, TView)>(source.buffer.Select(x => (x, selector(x)))); + this.source.CollectionChanged += SourceCollectionChanged; + } + } + + public int Count + { + get + { + lock (SyncRoot) + { + return ringBuffer.Count; + } + } + } + + public void AttachFilter(ISynchronizedViewFilter filter) + { + lock (SyncRoot) + { + this.filter = filter; + foreach (var (value, view) in ringBuffer) + { + filter.InvokeOnAttach(value, view); + } + } + } + + public void ResetFilter(Action? resetAction) + { + lock (SyncRoot) + { + this.filter = SynchronizedViewFilter.Null; + if (resetAction != null) + { + foreach (var (item, view) in ringBuffer) + { + resetAction(item, view); + } + } + } + } + + public INotifyCollectionChangedSynchronizedView WithINotifyCollectionChanged() + { + lock (SyncRoot) + { + return new NotifyCollectionChangedSynchronizedView(this); + } + } + + public IEnumerator<(T, TView)> GetEnumerator() + { + if (!reverse) + { + return new SynchronizedViewEnumerator(SyncRoot, ringBuffer.AsEnumerable().GetEnumerator(), filter); + } + else + { + return new SynchronizedViewEnumerator(SyncRoot, ringBuffer.AsEnumerable().Reverse().GetEnumerator(), filter); + } + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + public void Dispose() + { + this.source.CollectionChanged -= SourceCollectionChanged; + } + + private void SourceCollectionChanged(in NotifyCollectionChangedEventArgs e) + { + lock (SyncRoot) + { + switch (e.Action) + { + case NotifyCollectionChangedAction.Add: + // can not distinguish AddFirst and AddLast when collection count is 0. + // So, in that case, use AddLast. + // The internal structure may be different from the parent, but the result is same. + // RangeOperation is only exists AddLastRange because we can not distinguish FirstRange or LastRange. + if (e.NewStartingIndex == 0 && ringBuffer.Count != 0) + { + // AddFirst + if (e.IsSingleItem) + { + var v = (e.NewItem, selector(e.NewItem)); + ringBuffer.AddFirst(v); + filter.InvokeOnAdd(v); + } + else + { + foreach (var item in e.NewItems) + { + var v = (item, selector(item)); + ringBuffer.AddFirst(v); + filter.InvokeOnAdd(v); + } + } + } + else + { + // AddLast + if (e.IsSingleItem) + { + var v = (e.NewItem, selector(e.NewItem)); + ringBuffer.AddLast(v); + filter.InvokeOnAdd(v); + } + else + { + foreach (var item in e.NewItems) + { + var v = (item, selector(item)); + ringBuffer.AddLast(v); + filter.InvokeOnAdd(v); + } + } + } + break; + case NotifyCollectionChangedAction.Remove: + if (e.IsSingleItem) + { + var v = ringBuffer[e.OldStartingIndex]; + ringBuffer.RemoveAt(e.OldStartingIndex); + filter.InvokeOnRemove(v.Item1, v.Item2); + } + else + { + var len = e.OldStartingIndex + e.OldItems.Length; + for (int i = e.OldStartingIndex; i < len; i++) + { + var v = ringBuffer[i]; + filter.InvokeOnRemove(v.Item1, v.Item2); + } + + ringBuffer.RemoveRange(e.OldStartingIndex, e.OldItems.Length); + } + break; + case NotifyCollectionChangedAction.Replace: + // ObservableList does not support replace range + { + var v = (e.NewItem, selector(e.NewItem)); + + var oldItem = ringBuffer[e.NewStartingIndex]; + ringBuffer[e.NewStartingIndex] = v; + + filter.InvokeOnRemove(oldItem); + filter.InvokeOnAdd(v); + break; + } + case NotifyCollectionChangedAction.Move: + { + var removeItem = ringBuffer[e.OldStartingIndex]; + ringBuffer.RemoveAt(e.OldStartingIndex); + ringBuffer.Insert(e.NewStartingIndex, removeItem); + + filter.InvokeOnMove(removeItem); + } + break; + case NotifyCollectionChangedAction.Reset: + if (!filter.IsNullFilter()) + { + foreach (var item in ringBuffer) + { + filter.InvokeOnRemove(item); + } + } + ringBuffer.Clear(); + break; + default: + break; + } + + RoutingCollectionChanged?.Invoke(e); + CollectionStateChanged?.Invoke(e.Action); + } + } + } + } +} \ No newline at end of file diff --git a/src/ObservableCollections/ObservableRingBuffer.cs b/src/ObservableCollections/ObservableRingBuffer.cs index a5d6417..13eea04 100644 --- a/src/ObservableCollections/ObservableRingBuffer.cs +++ b/src/ObservableCollections/ObservableRingBuffer.cs @@ -1,10 +1,13 @@ -namespace ObservableCollections -{ +using System.Collections; - public sealed partial class ObservableRingBuffer +namespace ObservableCollections +{ + public sealed partial class ObservableRingBuffer : IList, IReadOnlyList, IObservableCollection { readonly RingBuffer buffer; + public event NotifyCollectionChangedEventHandler? CollectionChanged; + // TODO:SyncRoot public ObservableRingBuffer() @@ -17,10 +20,14 @@ this.buffer = new RingBuffer(collection); } - public int Count => buffer.Count; + public bool IsReadOnly => false; + + public object SyncRoot { get; } = new object(); public T this[int index] { + // TODO:notify! + get { return this.buffer[index]; @@ -31,12 +38,129 @@ } } + public int Count + { + get + { + lock (SyncRoot) + { + return buffer.Count; + } + } + } + + public void AddFirst(T item) + { + lock (SyncRoot) + { + buffer.AddFirst(item); + CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs.Add(item, 0)); + } + } + public void AddLast(T item) { + lock (SyncRoot) + { + buffer.AddLast(item); + CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs.Add(item, buffer.Count - 1)); + } } + // AddFirstRange??? + public void AddLastRange(T[] items) { + lock (SyncRoot) + { + foreach (var item in items) + { + buffer.AddLast(item); + } + } + } + + public int IndexOf(T item) + { + throw new NotImplementedException(); + } + + public void Insert(int index, T item) + { + throw new NotImplementedException(); + } + + public void RemoveAt(int index) + { + throw new NotImplementedException(); + } + + public void Add(T item) + { + throw new NotImplementedException(); + } + + public void Clear() + { + throw new NotImplementedException(); + } + + public bool Contains(T item) + { + throw new NotImplementedException(); + } + + public void CopyTo(T[] array, int arrayIndex) + { + throw new NotImplementedException(); + } + + public bool Remove(T item) + { + throw new NotImplementedException(); + } + + public IEnumerator GetEnumerator() + { + throw new NotImplementedException(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + throw new NotImplementedException(); + } + } + + + // TODO:Is this? + public sealed class ObservableFixedSizeRingBuffer + { + RingBuffer buffer = default!; // TODO:??? + + int fixedSize; + + public event NotifyCollectionChangedEventHandler? CollectionChanged; + + // TODO:SyncRoot + public bool IsReadOnly => false; + + public object SyncRoot { get; } = new object(); + + public void AddLast(T value) + { + lock (SyncRoot) + { + if (buffer.Count == fixedSize) + { + // Remove One. + var removed = buffer.RemoveFirst(); + buffer.AddLast(value); + + CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs.Remove(removed, 0)); + CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs.Add(value, buffer.Count - 1)); + } + } + } } diff --git a/src/ObservableCollections/RingBuffer.cs b/src/ObservableCollections/RingBuffer.cs index 77eaf2c..e56b737 100644 --- a/src/ObservableCollections/RingBuffer.cs +++ b/src/ObservableCollections/RingBuffer.cs @@ -3,7 +3,7 @@ using System.Diagnostics.CodeAnalysis; namespace ObservableCollections { - public sealed class RingBuffer : IList + public sealed class RingBuffer : IList, IReadOnlyList { T[] buffer; int head; @@ -100,23 +100,27 @@ namespace ObservableCollections count++; } - public void RemoveLast() + public T RemoveLast() { if (count == 0) ThrowForEmpty(); var index = (head + count) & mask; + var v = buffer[index]; buffer[index] = default!; count--; + return v; } - public void RemoveFirst() + public T RemoveFirst() { if (count == 0) ThrowForEmpty(); var index = head & mask; + var v = buffer[index]; buffer[index] = default!; head = head + 1; count--; + return v; } void EnsureCapacity()