or concept

This commit is contained in:
neuecc 2021-08-24 19:44:23 +09:00
parent eec98af604
commit f94156f564
4 changed files with 355 additions and 7 deletions

View File

@ -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<T> newItems = default, ReadOnlySpan<T> oldItems = default, int newStartingIndex = -1, int oldStartingIndex = -1)
{
Action = action;

View File

@ -0,0 +1,217 @@
using ObservableCollections.Internal;
using System.Collections;
using System.Collections.Specialized;
namespace ObservableCollections
{
public sealed partial class ObservableRingBuffer<T>
{
public ISynchronizedView<T, TView> CreateView<TView>(Func<T, TView> transform, bool reverse = false)
{
return new View<TView>(this, transform, reverse);
}
sealed class View<TView> : ISynchronizedView<T, TView>
{
readonly ObservableRingBuffer<T> source;
readonly Func<T, TView> selector;
readonly bool reverse;
readonly RingBuffer<(T, TView)> ringBuffer;
ISynchronizedViewFilter<T, TView> filter;
public event NotifyCollectionChangedEventHandler<T>? RoutingCollectionChanged;
public event Action<NotifyCollectionChangedAction>? CollectionStateChanged;
public object SyncRoot { get; }
public View(ObservableRingBuffer<T> source, Func<T, TView> selector, bool reverse)
{
this.source = source;
this.selector = selector;
this.reverse = reverse;
this.filter = SynchronizedViewFilter<T, TView>.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<T, TView> filter)
{
lock (SyncRoot)
{
this.filter = filter;
foreach (var (value, view) in ringBuffer)
{
filter.InvokeOnAttach(value, view);
}
}
}
public void ResetFilter(Action<T, TView>? resetAction)
{
lock (SyncRoot)
{
this.filter = SynchronizedViewFilter<T, TView>.Null;
if (resetAction != null)
{
foreach (var (item, view) in ringBuffer)
{
resetAction(item, view);
}
}
}
}
public INotifyCollectionChangedSynchronizedView<T, TView> WithINotifyCollectionChanged()
{
lock (SyncRoot)
{
return new NotifyCollectionChangedSynchronizedView<T, TView>(this);
}
}
public IEnumerator<(T, TView)> GetEnumerator()
{
if (!reverse)
{
return new SynchronizedViewEnumerator<T, TView>(SyncRoot, ringBuffer.AsEnumerable().GetEnumerator(), filter);
}
else
{
return new SynchronizedViewEnumerator<T, TView>(SyncRoot, ringBuffer.AsEnumerable().Reverse().GetEnumerator(), filter);
}
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public void Dispose()
{
this.source.CollectionChanged -= SourceCollectionChanged;
}
private void SourceCollectionChanged(in NotifyCollectionChangedEventArgs<T> 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);
}
}
}
}
}

View File

@ -1,10 +1,13 @@
namespace ObservableCollections
{
using System.Collections;
public sealed partial class ObservableRingBuffer<T>
namespace ObservableCollections
{
public sealed partial class ObservableRingBuffer<T> : IList<T>, IReadOnlyList<T>, IObservableCollection<T>
{
readonly RingBuffer<T> buffer;
public event NotifyCollectionChangedEventHandler<T>? CollectionChanged;
// TODO:SyncRoot
public ObservableRingBuffer()
@ -17,10 +20,14 @@
this.buffer = new RingBuffer<T>(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<T>.Add(item, 0));
}
}
public void AddLast(T item)
{
lock (SyncRoot)
{
buffer.AddLast(item);
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.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<T> GetEnumerator()
{
throw new NotImplementedException();
}
IEnumerator IEnumerable.GetEnumerator()
{
throw new NotImplementedException();
}
}
// TODO:Is this?
public sealed class ObservableFixedSizeRingBuffer<T>
{
RingBuffer<T> buffer = default!; // TODO:???
int fixedSize;
public event NotifyCollectionChangedEventHandler<T>? 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<T>.Remove(removed, 0));
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Add(value, buffer.Count - 1));
}
}
}
}

View File

@ -3,7 +3,7 @@ using System.Diagnostics.CodeAnalysis;
namespace ObservableCollections
{
public sealed class RingBuffer<T> : IList<T>
public sealed class RingBuffer<T> : IList<T>, IReadOnlyList<T>
{
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()