From 881bad485a3859d397e3413a01ba9f2c54336bb7 Mon Sep 17 00:00:00 2001 From: Yoh Deadfall Date: Sun, 15 Dec 2024 18:33:25 +0300 Subject: [PATCH] Moved list types to own files --- src/Kinetic/ObservableList.cs | 482 ------------------------- src/Kinetic/ReadOnlyObservableList.cs | 485 ++++++++++++++++++++++++++ 2 files changed, 485 insertions(+), 482 deletions(-) create mode 100644 src/Kinetic/ReadOnlyObservableList.cs diff --git a/src/Kinetic/ObservableList.cs b/src/Kinetic/ObservableList.cs index 1fa852b..2d7c8a3 100644 --- a/src/Kinetic/ObservableList.cs +++ b/src/Kinetic/ObservableList.cs @@ -1,489 +1,7 @@ -using System; -using System.Collections; using System.Collections.Generic; -using System.Diagnostics; -using System.Runtime.CompilerServices; namespace Kinetic; -/// A read-only list with observable collection changes. -/// -[DebuggerDisplay("Count = {Count}")] -public abstract class ReadOnlyObservableList : ObservableObject, IReadOnlyList -{ - private const int DefaultCapacity = 4; - - private T[] _items; - private int _count; - private int _version; - - /// - /// Initializes a new instance of the class - /// that is empty and has the default initial capacity. - /// - protected ReadOnlyObservableList() => - _items = Array.Empty(); - - /// - /// Initializes a new instance of the class - /// that is empty and has the specified initial capacity. - /// - protected ReadOnlyObservableList(int capacity) => - _items = capacity < 0 - ? throw new ArgumentOutOfRangeException(nameof(capacity)) - : capacity > 0 ? new T[capacity] : Array.Empty(); - - /// Gets an notifying about collection changes of this list. - /// An notifying about collection changes of this list. - public IObservable> Changed => EnsureChangeObservable(); - - /// Gets the number of elements contained in the list. - /// A providing the number of elements contained in the list. - public ReadOnlyProperty Count => Property(ref _count); - - int IReadOnlyCollection.Count => ItemCount; - - /// Gets the number of elements contained in the list. - /// The number of elements contained in the list. - /// - protected int ItemCount => _count; - - /// Gets the element at the specified index. - /// The zero-based index of the element to get. - /// The element at the specified index. - public T this[int index] - { - get - { - if ((uint) index >= (uint) _count) - { - throw new ArgumentOutOfRangeException(nameof(index)); - } - - return _items[index]; - } - } - - /// Adds an object to the end of the list. - /// The object to be added to the end of the list. - protected void AddItem(T item) - { - var index = _count; - - if ((uint) index == (uint) _items.Length) - { - Grow(index + 1); - } - - _count = index + 1; - _items[index] = item; - _version += 1; - - if (NotificationsEnabled) - { - GetCountObservable()?.Changed(index); - GetChangeObservable()?.Inserted(index, item); - } - } - - /// Removes all elements from the list. - protected void ClearItems() - { - if (RuntimeHelpers.IsReferenceOrContainsReferences()) - { - Array.Clear(_items, 0, _count); - } - - _count = 0; - _version += 1; - - if (NotificationsEnabled) - { - GetCountObservable()?.Changed(0); - GetChangeObservable()?.RemovedAll(); - } - } - - /// Determines whether an element is in the list. - /// The object to locate in the list. - /// if item is found in the list; otherwise, . - public bool Contains(T item) => - _count != 0 && IndexOf(item) >= -1; - - /// - /// Copies the entire list to a compatible one-dimensional array, - /// starting at the beginning of the target array. - /// The one-dimensional array that is the destination of the elements copied from list. - public void CopyTo(T[] array) => - CopyTo(array, 0); - - /// - /// Copies the entire list to a compatible one-dimensional array, - /// starting at the specified index of the target array. - /// - /// The one-dimensional array that is the destination of the elements copied from list. - /// The zero-based index in at which copying begins. - public void CopyTo(T[] array, int arrayIndex) => - Array.Copy(_items, 0, array, arrayIndex, _count); - - private void Grow(int capacity) - { - Debug.Assert(_items.Length < capacity); - - var newCapacity = _items.Length == 0 - ? DefaultCapacity - : 2 * _items.Length; - - if (newCapacity > 0) - { - var newItems = new T[newCapacity]; - if (_count > 0) - { - Array.Copy(_items, newItems, _count); - } - - _items = newItems; - } - else - { - _items = Array.Empty(); - } - } - - /// Returns an enumerator that iterates through the list. - /// A for the list. - public Enumerator GetEnumerator() => - new Enumerator(this); - - IEnumerator IEnumerable.GetEnumerator() => - GetEnumerator(); - - IEnumerator IEnumerable.GetEnumerator() => - GetEnumerator(); - - /// - /// Searches for the specified object and returns the zero-based index - /// of the first occurrence within the entire list. - /// - /// The object to locate in the list. - /// - /// The zero-based index of the first occurrence of - /// within the entire list, if found; otherwise, -1. - /// - public int IndexOf(T item) => - Array.IndexOf(_items, item, 0, _count); - - /// - /// Searches for the specified object and returns the zero-based index - /// of the first occurrence within the range of elements in the list - /// that extends from the specified index to the last element. - /// - /// The object to locate in the list. - /// The zero-based starting index of the search. - /// - /// The zero-based index of the first occurrence of - /// within the range of elements in the list that extends from - /// to the last element, if found; otherwise, -1. - /// - public int IndexOf(T item, int index) => - index > _count ? throw new ArgumentOutOfRangeException(nameof(index)) : - Array.IndexOf(_items, item, index, _count - index); - - /// - /// Searches for the specified object and returns the zero-based index - /// of the first occurrence within the range of elements in the list - /// that starts at the specified index and contains the specified number of elements. - /// - /// The object to locate in the list. - /// The zero-based starting index of the search. - /// - /// The zero-based index of the first occurrence of - /// within the range of elements in the list that starts at - /// and contains number of elements, if found; otherwise, -1. - /// - public int IndexOf(T item, int index, int count) => - index > _count ? throw new ArgumentOutOfRangeException(nameof(index)) : - index > _count - count || count < 0 ? throw new ArgumentOutOfRangeException(nameof(count)) : - Array.IndexOf(_items, item, index, count); - - /// Inserts an element into the list at the specified index. - /// The zero-based index at which should be inserted. - /// The object to insert. - protected void InsertItem(int index, T item) - { - if ((uint) index > (uint) _count) - { - throw new ArgumentOutOfRangeException(nameof(index)); - } - - if (_count == _items.Length) - { - Grow(_count + 1); - } - - if (index < _count) - { - Array.Copy( - sourceArray: _items, - sourceIndex: index, - destinationArray: _items, - destinationIndex: index + 1, - length: _count - index); - } - - _items[index] = item; - _count += 1; - _version += 1; - - if (NotificationsEnabled) - { - GetCountObservable()?.Changed(_count); - GetChangeObservable()?.Inserted(index, item); - } - } - - /// Removes the first occurrence of a specific object from the list. - /// The object to remove. - /// - /// if item is found and removed; otherwise, - protected bool RemoveItem(T item) - { - var index = IndexOf(item); - if (index != -1) - { - RemoveItemAt(index); - - return true; - } - - return false; - } - - /// Removes the element at the specified index of the list. - /// The zero-based index of the element to remove. - protected void RemoveItemAt(int index) - { - if ((uint) index >= (uint) _count) - { - throw new ArgumentOutOfRangeException(nameof(index)); - } - - _count -= 1; - _version += 1; - - if (index < _count) - { - Array.Copy( - sourceArray: _items, - sourceIndex: index + 1, - destinationArray: _items, - destinationIndex: index, - length: _count - index); - } - - if (RuntimeHelpers.IsReferenceOrContainsReferences()) - { - _items[_count] = default!; - } - - if (NotificationsEnabled) - { - GetCountObservable()?.Changed(_count); - GetChangeObservable()?.Removed(index); - } - } - - /// Replaces the element at the specified index of the list by the provided one. - /// The zero-based index of the element to replace. - /// The item to set at the specified index. - protected void ReplaceItem(int index, T item) - { - if ((uint) index >= (uint) _count) - { - throw new ArgumentOutOfRangeException(nameof(index)); - } - - _items[index] = item; - _version += 1; - - if (NotificationsEnabled) - { - GetChangeObservable()?.Replaced(index, item); - } - } - - /// Moves the item at the specified index to a new location in the collection. - /// The zero-based index specifying the location of the item to be moved. - /// The zero-based index specifying the new location of the item. - protected void MoveItem(int oldIndex, int newIndex) - { - if ((uint) oldIndex >= (uint) _count) - { - throw new ArgumentOutOfRangeException(nameof(oldIndex)); - } - - if ((uint) newIndex >= (uint) _count) - { - throw new ArgumentOutOfRangeException(nameof(newIndex)); - } - - var item = _items[oldIndex]; - - Array.Copy( - sourceArray: _items, - sourceIndex: oldIndex + 1, - destinationArray: _items, - destinationIndex: oldIndex, - length: _count - oldIndex - 1); - - Array.Copy( - sourceArray: _items, - sourceIndex: newIndex, - destinationArray: _items, - destinationIndex: newIndex + 1, - length: _count - newIndex - 1); - - _items[newIndex] = item; - _version += 1; - - if (NotificationsEnabled) - { - GetCountObservable()?.Changed(_count); - GetChangeObservable()?.Moved(oldIndex, newIndex); - } - } - - private ItemsObservable EnsureChangeObservable() => - Unsafe.As(EnsureObservable(GetOffsetOf(ref _items), static (self, offset, next) => new ItemsObservable(self, offset, next))); - - private ItemsObservable? GetChangeObservable() => - Unsafe.As(GetObservable(GetOffsetOf(ref _items))); - - private PropertyObservable? GetCountObservable() => - Unsafe.As?>(GetObservable(GetOffsetOf(ref _count))); - - /// Enumerates the elements of a list. - public struct Enumerator : IEnumerator - { - private readonly ReadOnlyObservableList _items; - private int _index; - private readonly int _version; - private T? _current; - - internal Enumerator(ReadOnlyObservableList items) - { - _items = items; - _index = 0; - _version = items._version; - _current = default; - } - - /// - public void Dispose() - { - } - - /// - public bool MoveNext() - { - var items = _items; - - if (_version == items._version && ((uint) _index < (uint) items._count)) - { - _current = items._items[_index]; - _index++; - - return true; - } - - return MoveNextRare(); - } - - private bool MoveNextRare() - { - if (_version != _items._version) - { - throw new InvalidOperationException(); - } - - _index = _items._count + 1; - _current = default; - return false; - } - - /// - public T Current => _current!; - - object? IEnumerator.Current => - _index == 0 || _index == _items._count + 1 - ? throw new InvalidOperationException() - : Current; - - void IEnumerator.Reset() - { - if (_version != _items._version) - { - throw new InvalidOperationException(); - } - - _index = 0; - _current = default; - } - } - - private sealed class ItemsObservable : PropertyObservable, IObservableInternal> - { - private ObservableSubscriptions> _subscriptions; - - public ItemsObservable(ObservableObject owner, IntPtr offset, PropertyObservable? next) - : base(owner, offset, next) { } - - public override void Changed() - { - _subscriptions.OnNext(ListChange.RemoveAll()); - - var items = Unsafe.As>(Owner); - - for (int index = 0, count = items.Count; index < count; index += 1) - _subscriptions.OnNext(ListChange.Insert(index, items[index])); - } - - public void RemovedAll() => - _subscriptions.OnNext(ListChange.RemoveAll()); - - public void Removed(int index) => - _subscriptions.OnNext(ListChange.Remove(index)); - - public void Inserted(int index, T item) => - _subscriptions.OnNext(ListChange.Insert(index, item)); - - public void Replaced(int index, T item) => - _subscriptions.OnNext(ListChange.Replace(index, item)); - - public void Moved(int oldIndex, int newIndex) => - _subscriptions.OnNext(ListChange.Move(oldIndex, newIndex)); - - public IDisposable Subscribe(IObserver> observer) - { - observer.OnNext(ListChange.RemoveAll()); - - var items = Unsafe.As>(Owner); - - for (int index = 0, count = items.Count; index < count; index += 1) - observer.OnNext(ListChange.Insert(index, items[index])); - - return _subscriptions.Subscribe(this, observer); - } - - public void Subscribe(ObservableSubscription> subscription) => - _subscriptions.Subscribe(this, subscription); - - public void Unsubscribe(ObservableSubscription> subscription) => - _subscriptions.Unsubscribe(subscription); - } -} - /// /// A list with observable collection changes. /// diff --git a/src/Kinetic/ReadOnlyObservableList.cs b/src/Kinetic/ReadOnlyObservableList.cs new file mode 100644 index 0000000..820e211 --- /dev/null +++ b/src/Kinetic/ReadOnlyObservableList.cs @@ -0,0 +1,485 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace Kinetic; + +/// A read-only list with observable collection changes. +/// +[DebuggerDisplay("Count = {Count}")] +public abstract class ReadOnlyObservableList : ObservableObject, IReadOnlyList +{ + private const int DefaultCapacity = 4; + + private T[] _items; + private int _count; + private int _version; + + /// + /// Initializes a new instance of the class + /// that is empty and has the default initial capacity. + /// + protected ReadOnlyObservableList() => + _items = Array.Empty(); + + /// + /// Initializes a new instance of the class + /// that is empty and has the specified initial capacity. + /// + protected ReadOnlyObservableList(int capacity) => + _items = capacity < 0 + ? throw new ArgumentOutOfRangeException(nameof(capacity)) + : capacity > 0 ? new T[capacity] : Array.Empty(); + + /// Gets an notifying about collection changes of this list. + /// An notifying about collection changes of this list. + public IObservable> Changed => EnsureChangeObservable(); + + /// Gets the number of elements contained in the list. + /// A providing the number of elements contained in the list. + public ReadOnlyProperty Count => Property(ref _count); + + int IReadOnlyCollection.Count => ItemCount; + + /// Gets the number of elements contained in the list. + /// The number of elements contained in the list. + /// + protected int ItemCount => _count; + + /// Gets the element at the specified index. + /// The zero-based index of the element to get. + /// The element at the specified index. + public T this[int index] + { + get + { + if ((uint) index >= (uint) _count) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + return _items[index]; + } + } + + /// Adds an object to the end of the list. + /// The object to be added to the end of the list. + protected void AddItem(T item) + { + var index = _count; + + if ((uint) index == (uint) _items.Length) + { + Grow(index + 1); + } + + _count = index + 1; + _items[index] = item; + _version += 1; + + if (NotificationsEnabled) + { + GetCountObservable()?.Changed(index); + GetChangeObservable()?.Inserted(index, item); + } + } + + /// Removes all elements from the list. + protected void ClearItems() + { + if (RuntimeHelpers.IsReferenceOrContainsReferences()) + { + Array.Clear(_items, 0, _count); + } + + _count = 0; + _version += 1; + + if (NotificationsEnabled) + { + GetCountObservable()?.Changed(0); + GetChangeObservable()?.RemovedAll(); + } + } + + /// Determines whether an element is in the list. + /// The object to locate in the list. + /// if item is found in the list; otherwise, . + public bool Contains(T item) => + _count != 0 && IndexOf(item) >= -1; + + /// + /// Copies the entire list to a compatible one-dimensional array, + /// starting at the beginning of the target array. + /// The one-dimensional array that is the destination of the elements copied from list. + public void CopyTo(T[] array) => + CopyTo(array, 0); + + /// + /// Copies the entire list to a compatible one-dimensional array, + /// starting at the specified index of the target array. + /// + /// The one-dimensional array that is the destination of the elements copied from list. + /// The zero-based index in at which copying begins. + public void CopyTo(T[] array, int arrayIndex) => + Array.Copy(_items, 0, array, arrayIndex, _count); + + private void Grow(int capacity) + { + Debug.Assert(_items.Length < capacity); + + var newCapacity = _items.Length == 0 + ? DefaultCapacity + : 2 * _items.Length; + + if (newCapacity > 0) + { + var newItems = new T[newCapacity]; + if (_count > 0) + { + Array.Copy(_items, newItems, _count); + } + + _items = newItems; + } + else + { + _items = Array.Empty(); + } + } + + /// Returns an enumerator that iterates through the list. + /// A for the list. + public Enumerator GetEnumerator() => + new Enumerator(this); + + IEnumerator IEnumerable.GetEnumerator() => + GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => + GetEnumerator(); + + /// + /// Searches for the specified object and returns the zero-based index + /// of the first occurrence within the entire list. + /// + /// The object to locate in the list. + /// + /// The zero-based index of the first occurrence of + /// within the entire list, if found; otherwise, -1. + /// + public int IndexOf(T item) => + Array.IndexOf(_items, item, 0, _count); + + /// + /// Searches for the specified object and returns the zero-based index + /// of the first occurrence within the range of elements in the list + /// that extends from the specified index to the last element. + /// + /// The object to locate in the list. + /// The zero-based starting index of the search. + /// + /// The zero-based index of the first occurrence of + /// within the range of elements in the list that extends from + /// to the last element, if found; otherwise, -1. + /// + public int IndexOf(T item, int index) => + index > _count ? throw new ArgumentOutOfRangeException(nameof(index)) : + Array.IndexOf(_items, item, index, _count - index); + + /// + /// Searches for the specified object and returns the zero-based index + /// of the first occurrence within the range of elements in the list + /// that starts at the specified index and contains the specified number of elements. + /// + /// The object to locate in the list. + /// The zero-based starting index of the search. + /// + /// The zero-based index of the first occurrence of + /// within the range of elements in the list that starts at + /// and contains number of elements, if found; otherwise, -1. + /// + public int IndexOf(T item, int index, int count) => + index > _count ? throw new ArgumentOutOfRangeException(nameof(index)) : + index > _count - count || count < 0 ? throw new ArgumentOutOfRangeException(nameof(count)) : + Array.IndexOf(_items, item, index, count); + + /// Inserts an element into the list at the specified index. + /// The zero-based index at which should be inserted. + /// The object to insert. + protected void InsertItem(int index, T item) + { + if ((uint) index > (uint) _count) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + if (_count == _items.Length) + { + Grow(_count + 1); + } + + if (index < _count) + { + Array.Copy( + sourceArray: _items, + sourceIndex: index, + destinationArray: _items, + destinationIndex: index + 1, + length: _count - index); + } + + _items[index] = item; + _count += 1; + _version += 1; + + if (NotificationsEnabled) + { + GetCountObservable()?.Changed(_count); + GetChangeObservable()?.Inserted(index, item); + } + } + + /// Removes the first occurrence of a specific object from the list. + /// The object to remove. + /// + /// if item is found and removed; otherwise, + protected bool RemoveItem(T item) + { + var index = IndexOf(item); + if (index != -1) + { + RemoveItemAt(index); + + return true; + } + + return false; + } + + /// Removes the element at the specified index of the list. + /// The zero-based index of the element to remove. + protected void RemoveItemAt(int index) + { + if ((uint) index >= (uint) _count) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + _count -= 1; + _version += 1; + + if (index < _count) + { + Array.Copy( + sourceArray: _items, + sourceIndex: index + 1, + destinationArray: _items, + destinationIndex: index, + length: _count - index); + } + + if (RuntimeHelpers.IsReferenceOrContainsReferences()) + { + _items[_count] = default!; + } + + if (NotificationsEnabled) + { + GetCountObservable()?.Changed(_count); + GetChangeObservable()?.Removed(index); + } + } + + /// Replaces the element at the specified index of the list by the provided one. + /// The zero-based index of the element to replace. + /// The item to set at the specified index. + protected void ReplaceItem(int index, T item) + { + if ((uint) index >= (uint) _count) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + _items[index] = item; + _version += 1; + + if (NotificationsEnabled) + { + GetChangeObservable()?.Replaced(index, item); + } + } + + /// Moves the item at the specified index to a new location in the collection. + /// The zero-based index specifying the location of the item to be moved. + /// The zero-based index specifying the new location of the item. + protected void MoveItem(int oldIndex, int newIndex) + { + if ((uint) oldIndex >= (uint) _count) + { + throw new ArgumentOutOfRangeException(nameof(oldIndex)); + } + + if ((uint) newIndex >= (uint) _count) + { + throw new ArgumentOutOfRangeException(nameof(newIndex)); + } + + var item = _items[oldIndex]; + + Array.Copy( + sourceArray: _items, + sourceIndex: oldIndex + 1, + destinationArray: _items, + destinationIndex: oldIndex, + length: _count - oldIndex - 1); + + Array.Copy( + sourceArray: _items, + sourceIndex: newIndex, + destinationArray: _items, + destinationIndex: newIndex + 1, + length: _count - newIndex - 1); + + _items[newIndex] = item; + _version += 1; + + if (NotificationsEnabled) + { + GetCountObservable()?.Changed(_count); + GetChangeObservable()?.Moved(oldIndex, newIndex); + } + } + + private ItemsObservable EnsureChangeObservable() => + Unsafe.As(EnsureObservable(GetOffsetOf(ref _items), static (self, offset, next) => new ItemsObservable(self, offset, next))); + + private ItemsObservable? GetChangeObservable() => + Unsafe.As(GetObservable(GetOffsetOf(ref _items))); + + private PropertyObservable? GetCountObservable() => + Unsafe.As?>(GetObservable(GetOffsetOf(ref _count))); + + /// Enumerates the elements of a list. + public struct Enumerator : IEnumerator + { + private readonly ReadOnlyObservableList _items; + private int _index; + private readonly int _version; + private T? _current; + + internal Enumerator(ReadOnlyObservableList items) + { + _items = items; + _index = 0; + _version = items._version; + _current = default; + } + + /// + public void Dispose() + { + } + + /// + public bool MoveNext() + { + var items = _items; + + if (_version == items._version && ((uint) _index < (uint) items._count)) + { + _current = items._items[_index]; + _index++; + + return true; + } + + return MoveNextRare(); + } + + private bool MoveNextRare() + { + if (_version != _items._version) + { + throw new InvalidOperationException(); + } + + _index = _items._count + 1; + _current = default; + return false; + } + + /// + public T Current => _current!; + + object? IEnumerator.Current => + _index == 0 || _index == _items._count + 1 + ? throw new InvalidOperationException() + : Current; + + void IEnumerator.Reset() + { + if (_version != _items._version) + { + throw new InvalidOperationException(); + } + + _index = 0; + _current = default; + } + } + + private sealed class ItemsObservable : PropertyObservable, IObservableInternal> + { + private ObservableSubscriptions> _subscriptions; + + public ItemsObservable(ObservableObject owner, IntPtr offset, PropertyObservable? next) + : base(owner, offset, next) { } + + public override void Changed() + { + _subscriptions.OnNext(ListChange.RemoveAll()); + + var items = Unsafe.As>(Owner); + + for (int index = 0, count = items.Count; index < count; index += 1) + _subscriptions.OnNext(ListChange.Insert(index, items[index])); + } + + public void RemovedAll() => + _subscriptions.OnNext(ListChange.RemoveAll()); + + public void Removed(int index) => + _subscriptions.OnNext(ListChange.Remove(index)); + + public void Inserted(int index, T item) => + _subscriptions.OnNext(ListChange.Insert(index, item)); + + public void Replaced(int index, T item) => + _subscriptions.OnNext(ListChange.Replace(index, item)); + + public void Moved(int oldIndex, int newIndex) => + _subscriptions.OnNext(ListChange.Move(oldIndex, newIndex)); + + public IDisposable Subscribe(IObserver> observer) + { + observer.OnNext(ListChange.RemoveAll()); + + var items = Unsafe.As>(Owner); + + for (int index = 0, count = items.Count; index < count; index += 1) + observer.OnNext(ListChange.Insert(index, items[index])); + + return _subscriptions.Subscribe(this, observer); + } + + public void Subscribe(ObservableSubscription> subscription) => + _subscriptions.Subscribe(this, subscription); + + public void Unsubscribe(ObservableSubscription> subscription) => + _subscriptions.Unsubscribe(subscription); + } +} \ No newline at end of file