complete ringbuffer
This commit is contained in:
parent
f3b47c85e9
commit
8de22f5ba2
@ -86,8 +86,8 @@ namespace ObservableCollections
|
|||||||
{
|
{
|
||||||
if (capacity == buffer.Count)
|
if (capacity == buffer.Count)
|
||||||
{
|
{
|
||||||
var remItem = buffer.RemoveLast();
|
var remItem = buffer.RemoveFirst();
|
||||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Remove(remItem, capacity - 1));
|
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Remove(remItem, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer.AddLast(item);
|
buffer.AddLast(item);
|
||||||
@ -124,10 +124,10 @@ namespace ObservableCollections
|
|||||||
{
|
{
|
||||||
using (var xs = new CloneCollection<T>(items))
|
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
|
// 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))
|
using (var ys = new ResizableArray<T>(remCount))
|
||||||
{
|
{
|
||||||
for (int i = 0; i < remCount; i++)
|
for (int i = 0; i < remCount; i++)
|
||||||
@ -159,12 +159,33 @@ namespace ObservableCollections
|
|||||||
{
|
{
|
||||||
lock (SyncRoot)
|
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 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);
|
buffer.AddLast(item);
|
||||||
}
|
}
|
||||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Add(items, index));
|
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Add(span, index));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -172,33 +193,33 @@ namespace ObservableCollections
|
|||||||
{
|
{
|
||||||
lock (SyncRoot)
|
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<T>(remCount))
|
||||||
{
|
{
|
||||||
// calc remove count
|
for (int i = 0; i < remCount; i++)
|
||||||
var remCount = Math.Min(capacity, buffer.Count + xs.Span.Length - 1 - capacity);
|
|
||||||
using (var ys = new ResizableArray<T>(remCount))
|
|
||||||
{
|
{
|
||||||
for (int i = 0; i < remCount; i++)
|
ys.Add(buffer.RemoveFirst());
|
||||||
{
|
|
||||||
ys.Add(buffer.RemoveFirst());
|
|
||||||
}
|
|
||||||
|
|
||||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Remove(ys.Span, 0));
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
var index = buffer.Count;
|
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Remove(ys.Span, 0));
|
||||||
var span = xs.Span;
|
|
||||||
if (span.Length > capacity)
|
|
||||||
{
|
|
||||||
span = span.Slice(span.Length - capacity);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var item in span)
|
var index = buffer.Count;
|
||||||
{
|
var span = items;
|
||||||
buffer.AddLast(item);
|
if (span.Length > capacity)
|
||||||
}
|
{
|
||||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Add(span, index));
|
span = span.Slice(span.Length - capacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var item in span)
|
||||||
|
{
|
||||||
|
buffer.AddLast(item);
|
||||||
|
}
|
||||||
|
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Add(span, index));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -265,13 +286,9 @@ namespace ObservableCollections
|
|||||||
return GetEnumerator();
|
return GetEnumerator();
|
||||||
}
|
}
|
||||||
|
|
||||||
// View
|
|
||||||
|
|
||||||
public ISynchronizedView<T, TView> CreateView<TView>(Func<T, TView> transform, bool reverse = false)
|
public ISynchronizedView<T, TView> CreateView<TView>(Func<T, TView> transform, bool reverse = false)
|
||||||
{
|
{
|
||||||
// TODO:
|
return new ObservableRingBuffer<T>.View<TView>(this, transform, reverse);
|
||||||
throw new NotImplementedException();
|
|
||||||
// return new View<TView>(this, transform, reverse);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,9 +11,10 @@ namespace ObservableCollections
|
|||||||
return new View<TView>(this, transform, reverse);
|
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 Func<T, TView> selector;
|
||||||
readonly bool reverse;
|
readonly bool reverse;
|
||||||
readonly RingBuffer<(T, TView)> ringBuffer;
|
readonly RingBuffer<(T, TView)> ringBuffer;
|
||||||
@ -25,7 +26,7 @@ namespace ObservableCollections
|
|||||||
|
|
||||||
public object SyncRoot { get; }
|
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.source = source;
|
||||||
this.selector = selector;
|
this.selector = selector;
|
||||||
@ -34,7 +35,7 @@ namespace ObservableCollections
|
|||||||
this.SyncRoot = new object();
|
this.SyncRoot = new object();
|
||||||
lock (source.SyncRoot)
|
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;
|
this.source.CollectionChanged += SourceCollectionChanged;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -202,6 +203,17 @@ namespace ObservableCollections
|
|||||||
ringBuffer.Clear();
|
ringBuffer.Clear();
|
||||||
break;
|
break;
|
||||||
case NotifyCollectionChangedAction.Replace:
|
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:
|
case NotifyCollectionChangedAction.Move:
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
@ -104,7 +104,7 @@ namespace ObservableCollections
|
|||||||
{
|
{
|
||||||
if (count == 0) ThrowForEmpty();
|
if (count == 0) ThrowForEmpty();
|
||||||
|
|
||||||
var index = (head + count) & mask;
|
var index = (head + count - 1) & mask;
|
||||||
var v = buffer[index];
|
var v = buffer[index];
|
||||||
buffer[index] = default!;
|
buffer[index] = default!;
|
||||||
count--;
|
count--;
|
||||||
|
118
tests/ObservableCollections.Tests/ObservableRingBufferTest.cs
Normal file
118
tests/ObservableCollections.Tests/ObservableRingBufferTest.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -23,13 +23,13 @@ namespace ObservableCollections.Tests
|
|||||||
list.AddLast(5); list.Should().Equal(1, 2, 3, 4, 5);
|
list.AddLast(5); list.Should().Equal(1, 2, 3, 4, 5);
|
||||||
list.AddLast(6); list.Should().Equal(1, 2, 3, 4, 5, 6);
|
list.AddLast(6); list.Should().Equal(1, 2, 3, 4, 5, 6);
|
||||||
|
|
||||||
list.RemoveLast();
|
list.RemoveLast().Should().Be(6);
|
||||||
list.RemoveLast();
|
list.RemoveLast().Should().Be(5);
|
||||||
list.Should().Equal(1, 2, 3, 4);
|
list.Should().Equal(1, 2, 3, 4);
|
||||||
list.Reverse().Should().Equal(4, 3, 2, 1);
|
list.Reverse().Should().Equal(4, 3, 2, 1);
|
||||||
|
|
||||||
list.RemoveFirst();
|
list.RemoveFirst().Should().Be(1);
|
||||||
list.RemoveFirst();
|
list.RemoveFirst().Should().Be(2);
|
||||||
list.Should().Equal(3, 4);
|
list.Should().Equal(3, 4);
|
||||||
|
|
||||||
list.AddFirst(99);
|
list.AddFirst(99);
|
||||||
@ -70,12 +70,12 @@ namespace ObservableCollections.Tests
|
|||||||
list.Should().Equal(6, 5, 4, 3, 2, 1);
|
list.Should().Equal(6, 5, 4, 3, 2, 1);
|
||||||
list.Reverse().Should().Equal(1, 2, 3, 4, 5, 6);
|
list.Reverse().Should().Equal(1, 2, 3, 4, 5, 6);
|
||||||
|
|
||||||
list.RemoveLast();
|
list.RemoveLast().Should().Be(1);
|
||||||
list.RemoveLast();
|
list.RemoveLast().Should().Be(2);
|
||||||
list.Should().Equal(6, 5, 4, 3);
|
list.Should().Equal(6, 5, 4, 3);
|
||||||
|
|
||||||
list.RemoveFirst();
|
list.RemoveFirst().Should().Be(6);
|
||||||
list.RemoveFirst();
|
list.RemoveFirst().Should().Be(5);
|
||||||
list.Should().Equal(4, 3);
|
list.Should().Equal(4, 3);
|
||||||
|
|
||||||
list.AddFirst(99);
|
list.AddFirst(99);
|
||||||
|
2
tests/ObservableCollections.Tests/_GlobalUsings.cs
Normal file
2
tests/ObservableCollections.Tests/_GlobalUsings.cs
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
global using Xunit;
|
||||||
|
global using FluentAssertions;
|
Loading…
x
Reference in New Issue
Block a user