From 8de22f5ba23b7be84c994204250dac15936ec3d2 Mon Sep 17 00:00:00 2001 From: neuecc Date: Wed, 1 Sep 2021 19:51:08 +0900 Subject: [PATCH] complete ringbuffer --- .../ObservableFixedSizeRingBuffer.cs | 81 +++++++----- .../ObservableRingBuffer.Views.cs | 20 ++- src/ObservableCollections/RingBuffer.cs | 2 +- .../ObservableRingBufferTest.cs | 118 ++++++++++++++++++ .../RingBufferTest.cs | 16 +-- .../_GlobalUsings.cs | 2 + 6 files changed, 194 insertions(+), 45 deletions(-) create mode 100644 tests/ObservableCollections.Tests/ObservableRingBufferTest.cs create mode 100644 tests/ObservableCollections.Tests/_GlobalUsings.cs diff --git a/src/ObservableCollections/ObservableFixedSizeRingBuffer.cs b/src/ObservableCollections/ObservableFixedSizeRingBuffer.cs index 1604806..a450564 100644 --- a/src/ObservableCollections/ObservableFixedSizeRingBuffer.cs +++ b/src/ObservableCollections/ObservableFixedSizeRingBuffer.cs @@ -86,8 +86,8 @@ namespace ObservableCollections { if (capacity == buffer.Count) { - var remItem = buffer.RemoveLast(); - CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs.Remove(remItem, capacity - 1)); + var remItem = buffer.RemoveFirst(); + CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs.Remove(remItem, 0)); } buffer.AddLast(item); @@ -124,10 +124,10 @@ namespace ObservableCollections { using (var xs = new CloneCollection(items)) { - if (capacity >= buffer.Count + xs.Span.Length - 1) + if (capacity <= buffer.Count + xs.Span.Length) { // calc remove count - var remCount = Math.Min(capacity, buffer.Count + xs.Span.Length - 1 - capacity); + var remCount = Math.Min(buffer.Count, buffer.Count + xs.Span.Length - capacity); using (var ys = new ResizableArray(remCount)) { for (int i = 0; i < remCount; i++) @@ -159,12 +159,33 @@ namespace ObservableCollections { lock (SyncRoot) { + if (capacity <= buffer.Count + items.Length) + { + // calc remove count + var remCount = Math.Min(buffer.Count, buffer.Count + items.Length - capacity); + using (var ys = new ResizableArray(remCount)) + { + for (int i = 0; i < remCount; i++) + { + ys.Add(buffer.RemoveFirst()); + } + + CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs.Remove(ys.Span, 0)); + } + } + var index = buffer.Count; - foreach (var item in items) + var span = items.AsSpan(); + if (span.Length > capacity) + { + span = span.Slice(span.Length - capacity); + } + + foreach (var item in span) { buffer.AddLast(item); } - CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs.Add(items, index)); + CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs.Add(span, index)); } } @@ -172,33 +193,33 @@ namespace ObservableCollections { lock (SyncRoot) { - if (capacity >= buffer.Count + xs.Span.Length - 1) + if (capacity <= buffer.Count + items.Length) + { + // calc remove count + var remCount = Math.Min(buffer.Count, buffer.Count + items.Length - capacity); + using (var ys = new ResizableArray(remCount)) { - // calc remove count - var remCount = Math.Min(capacity, buffer.Count + xs.Span.Length - 1 - capacity); - using (var ys = new ResizableArray(remCount)) + for (int i = 0; i < remCount; i++) { - for (int i = 0; i < remCount; i++) - { - ys.Add(buffer.RemoveFirst()); - } - - CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs.Remove(ys.Span, 0)); + ys.Add(buffer.RemoveFirst()); } - } - var index = buffer.Count; - var span = xs.Span; - if (span.Length > capacity) - { - span = span.Slice(span.Length - capacity); + CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs.Remove(ys.Span, 0)); } + } - foreach (var item in span) - { - buffer.AddLast(item); - } - CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs.Add(span, index)); + var index = buffer.Count; + var span = items; + if (span.Length > capacity) + { + span = span.Slice(span.Length - capacity); + } + + foreach (var item in span) + { + buffer.AddLast(item); + } + CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs.Add(span, index)); } } @@ -265,13 +286,9 @@ namespace ObservableCollections return GetEnumerator(); } - // View - public ISynchronizedView CreateView(Func transform, bool reverse = false) { - // TODO: - throw new NotImplementedException(); - // return new View(this, transform, reverse); + return new ObservableRingBuffer.View(this, transform, reverse); } } } diff --git a/src/ObservableCollections/ObservableRingBuffer.Views.cs b/src/ObservableCollections/ObservableRingBuffer.Views.cs index 77b7481..aa7fc94 100644 --- a/src/ObservableCollections/ObservableRingBuffer.Views.cs +++ b/src/ObservableCollections/ObservableRingBuffer.Views.cs @@ -11,9 +11,10 @@ namespace ObservableCollections return new View(this, transform, reverse); } - sealed class View : ISynchronizedView + // used with ObservableFixedSizeRingBuffer + internal sealed class View : ISynchronizedView { - readonly ObservableRingBuffer source; + readonly IObservableCollection source; readonly Func selector; readonly bool reverse; readonly RingBuffer<(T, TView)> ringBuffer; @@ -25,7 +26,7 @@ namespace ObservableCollections public object SyncRoot { get; } - public View(ObservableRingBuffer source, Func selector, bool reverse) + public View(IObservableCollection source, Func selector, bool reverse) { this.source = source; this.selector = selector; @@ -34,7 +35,7 @@ namespace ObservableCollections this.SyncRoot = new object(); lock (source.SyncRoot) { - this.ringBuffer = new RingBuffer<(T, TView)>(source.buffer.Select(x => (x, selector(x)))); + this.ringBuffer = new RingBuffer<(T, TView)>(source.Select(x => (x, selector(x)))); this.source.CollectionChanged += SourceCollectionChanged; } } @@ -202,6 +203,17 @@ namespace ObservableCollections ringBuffer.Clear(); break; case NotifyCollectionChangedAction.Replace: + // range is not supported + { + 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: default: break; diff --git a/src/ObservableCollections/RingBuffer.cs b/src/ObservableCollections/RingBuffer.cs index 61fd4ce..5ba6d0d 100644 --- a/src/ObservableCollections/RingBuffer.cs +++ b/src/ObservableCollections/RingBuffer.cs @@ -104,7 +104,7 @@ namespace ObservableCollections { if (count == 0) ThrowForEmpty(); - var index = (head + count) & mask; + var index = (head + count - 1) & mask; var v = buffer[index]; buffer[index] = default!; count--; diff --git a/tests/ObservableCollections.Tests/ObservableRingBufferTest.cs b/tests/ObservableCollections.Tests/ObservableRingBufferTest.cs new file mode 100644 index 0000000..d2fa603 --- /dev/null +++ b/tests/ObservableCollections.Tests/ObservableRingBufferTest.cs @@ -0,0 +1,118 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ObservableCollections.Tests +{ + public class ObservableRingBufferTest + { + [Fact] + public void Standard() + { + var buf = new ObservableRingBuffer(); + var view = buf.CreateView(x => new ViewContainer(x)); + + buf.AddLast(10); + buf.AddLast(50); + buf.AddLast(30); + buf.AddLast(20); + buf.AddLast(40); + + void Equal(params int[] expected) + { + buf.Should().Equal(expected); + view.Select(x => x.Value).Should().Equal(expected); + view.Select(x => x.View).Should().Equal(expected.Select(x => new ViewContainer(x))); + } + + Equal(10, 50, 30, 20, 40); + + buf[2] = 99; + Equal(10, 50, 99, 20, 40); + + buf.AddFirst(1000); + Equal(1000, 10, 50, 99, 20, 40); + + buf.RemoveFirst().Should().Be(1000); + Equal(10, 50, 99, 20, 40); + + buf.RemoveLast().Should().Be(40); + Equal(10, 50, 99, 20); + + buf.AddLastRange(new[] { 1, 2, 3 }); + buf.AddLastRange(new[] { 4, 5, 6 }.AsSpan()); + buf.AddLastRange(new[] { 7, 8, 9 }.AsEnumerable()); + + Equal(10, 50, 99, 20, 1, 2, 3, 4, 5, 6, 7, 8, 9); + + buf.Clear(); + Equal(); + + buf.AddFirst(9999); + Equal(9999); + } + + + [Fact] + public void FixedSize() + { + var buf = new ObservableFixedSizeRingBuffer(5); + var view = buf.CreateView(x => new ViewContainer(x)); + + void Equal(params int[] expected) + { + buf.Should().Equal(expected); + view.Select(x => x.Value).Should().Equal(expected); + view.Select(x => x.View).Should().Equal(expected.Select(x => new ViewContainer(x))); + } + + buf.AddLast(10); + buf.AddLast(50); + buf.AddLast(30); + buf.AddLast(20); + buf.AddLast(40); + + Equal(10, 50, 30, 20, 40); + + buf.AddLast(100); + Equal(50, 30, 20, 40, 100); + + buf.AddFirst(99); + Equal(99, 50, 30, 20, 40); + + buf[0] = 10; + buf[2] = 99; + Equal(10, 50, 99, 20, 40); + + buf.AddFirst(1000); + Equal(1000, 10, 50, 99, 20); + + buf.RemoveFirst().Should().Be(1000); + Equal(10, 50, 99, 20); + + buf.RemoveLast().Should().Be(20); + Equal(10, 50, 99); + + buf.AddLastRange(new[] { 1, 2, 3 }); + Equal(50, 99, 1, 2, 3); + buf.AddLastRange(new[] { 4, 5, 6 }.AsSpan()); + Equal(2, 3, 4, 5, 6); + buf.AddLastRange(new[] { 7, 8, 9 }.AsEnumerable()); + Equal(5, 6, 7, 8, 9); + + buf.Clear(); + Equal(); + + buf.AddLastRange(new int[] { }); + Equal(); + + buf.AddLastRange(new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 }); + Equal(8, 9, 10, 11, 12); + + buf.AddLastRange(new[] { 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120 }.AsSpan()); + Equal(80, 90, 100, 110, 120); + } + } +} diff --git a/tests/ObservableCollections.Tests/RingBufferTest.cs b/tests/ObservableCollections.Tests/RingBufferTest.cs index 04e7b62..f8c4e52 100644 --- a/tests/ObservableCollections.Tests/RingBufferTest.cs +++ b/tests/ObservableCollections.Tests/RingBufferTest.cs @@ -23,13 +23,13 @@ namespace ObservableCollections.Tests list.AddLast(5); list.Should().Equal(1, 2, 3, 4, 5); list.AddLast(6); list.Should().Equal(1, 2, 3, 4, 5, 6); - list.RemoveLast(); - list.RemoveLast(); + list.RemoveLast().Should().Be(6); + list.RemoveLast().Should().Be(5); list.Should().Equal(1, 2, 3, 4); list.Reverse().Should().Equal(4, 3, 2, 1); - list.RemoveFirst(); - list.RemoveFirst(); + list.RemoveFirst().Should().Be(1); + list.RemoveFirst().Should().Be(2); list.Should().Equal(3, 4); list.AddFirst(99); @@ -70,12 +70,12 @@ namespace ObservableCollections.Tests list.Should().Equal(6, 5, 4, 3, 2, 1); list.Reverse().Should().Equal(1, 2, 3, 4, 5, 6); - list.RemoveLast(); - list.RemoveLast(); + list.RemoveLast().Should().Be(1); + list.RemoveLast().Should().Be(2); list.Should().Equal(6, 5, 4, 3); - list.RemoveFirst(); - list.RemoveFirst(); + list.RemoveFirst().Should().Be(6); + list.RemoveFirst().Should().Be(5); list.Should().Equal(4, 3); list.AddFirst(99); diff --git a/tests/ObservableCollections.Tests/_GlobalUsings.cs b/tests/ObservableCollections.Tests/_GlobalUsings.cs new file mode 100644 index 0000000..30b9735 --- /dev/null +++ b/tests/ObservableCollections.Tests/_GlobalUsings.cs @@ -0,0 +1,2 @@ +global using Xunit; +global using FluentAssertions;