complete ringbuffer

This commit is contained in:
neuecc 2021-09-01 19:51:08 +09:00
parent f3b47c85e9
commit 8de22f5ba2
6 changed files with 194 additions and 45 deletions

View File

@ -86,8 +86,8 @@ namespace ObservableCollections
{
if (capacity == buffer.Count)
{
var remItem = buffer.RemoveLast();
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Remove(remItem, capacity - 1));
var remItem = buffer.RemoveFirst();
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Remove(remItem, 0));
}
buffer.AddLast(item);
@ -124,10 +124,10 @@ namespace ObservableCollections
{
using (var xs = new CloneCollection<T>(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<T>(remCount))
{
for (int i = 0; i < remCount; i++)
@ -159,23 +159,10 @@ namespace ObservableCollections
{
lock (SyncRoot)
{
var index = buffer.Count;
foreach (var item in items)
{
buffer.AddLast(item);
}
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Add(items, index));
}
}
public void AddLastRange(ReadOnlySpan<T> items)
{
lock (SyncRoot)
{
if (capacity >= buffer.Count + xs.Span.Length - 1)
if (capacity <= buffer.Count + items.Length)
{
// calc remove count
var remCount = Math.Min(capacity, buffer.Count + xs.Span.Length - 1 - capacity);
var remCount = Math.Min(buffer.Count, buffer.Count + items.Length - capacity);
using (var ys = new ResizableArray<T>(remCount))
{
for (int i = 0; i < remCount; i++)
@ -188,7 +175,41 @@ namespace ObservableCollections
}
var index = buffer.Count;
var span = xs.Span;
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<T>.Add(span, index));
}
}
public void AddLastRange(ReadOnlySpan<T> items)
{
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<T>(remCount))
{
for (int i = 0; i < remCount; i++)
{
ys.Add(buffer.RemoveFirst());
}
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Remove(ys.Span, 0));
}
}
var index = buffer.Count;
var span = items;
if (span.Length > capacity)
{
span = span.Slice(span.Length - capacity);
@ -265,13 +286,9 @@ namespace ObservableCollections
return GetEnumerator();
}
// View
public ISynchronizedView<T, TView> CreateView<TView>(Func<T, TView> transform, bool reverse = false)
{
// TODO:
throw new NotImplementedException();
// return new View<TView>(this, transform, reverse);
return new ObservableRingBuffer<T>.View<TView>(this, transform, reverse);
}
}
}

View File

@ -11,9 +11,10 @@ namespace ObservableCollections
return new View<TView>(this, transform, reverse);
}
sealed class View<TView> : ISynchronizedView<T, TView>
// used with ObservableFixedSizeRingBuffer
internal sealed class View<TView> : ISynchronizedView<T, TView>
{
readonly ObservableRingBuffer<T> source;
readonly IObservableCollection<T> source;
readonly Func<T, TView> selector;
readonly bool reverse;
readonly RingBuffer<(T, TView)> ringBuffer;
@ -25,7 +26,7 @@ namespace ObservableCollections
public object SyncRoot { get; }
public View(ObservableRingBuffer<T> source, Func<T, TView> selector, bool reverse)
public View(IObservableCollection<T> source, Func<T, TView> 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;

View File

@ -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--;

View File

@ -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<int>();
var view = buf.CreateView(x => new ViewContainer<int>(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<int>(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<int>(5);
var view = buf.CreateView(x => new ViewContainer<int>(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<int>(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);
}
}
}

View File

@ -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);

View File

@ -0,0 +1,2 @@
global using Xunit;
global using FluentAssertions;