CircularBuffer: shrink indices to 1 word on 64-bit archs

Motivation:

CircularBuffer and its indices should be small. Also the implementation
of methods like remove/popFirst/Last was suboptimal.

Modifications:

- shrink CircularBuffer and its indices
- improve the conformances to the various collection related protocols
- make CircularBuffer self-slicing
- remove Strideable conformance from the indices

Result:

nicer CircularBuffer
This commit is contained in:
Johannes Weiss 2019-03-15 18:14:02 +00:00
parent 5409acdfb6
commit 35ed9ff754
9 changed files with 820 additions and 309 deletions

View File

@ -95,6 +95,7 @@ extension ByteBuffer {
extension FixedWidthInteger {
/// Returns the next power of two.
@inlinable
func nextPowerOf2() -> Self {
guard self != 0 else {
return 1

View File

@ -12,74 +12,65 @@
//
//===----------------------------------------------------------------------===//
/// AppendableCollection is a protocol partway between Collection and
/// RangeReplaceableCollection. It defines the append method that is present
/// on RangeReplaceableCollection, which makes all RangeReplaceableCollections
/// trivially able to implement this protocol.
protocol AppendableCollection: Collection {
mutating func append(_ newElement: Self.Iterator.Element)
}
/// An automatically expanding ring buffer implementation backed by a `ContiguousArray`. Even though this implementation
/// will automatically expand if more elements than `initialCapacity` are stored, it's advantageous to prevent
/// expansions from happening frequently. Expansions will always force an allocation and a copy to happen.
public struct CircularBuffer<Element>: CustomStringConvertible, AppendableCollection {
public typealias RangeType<Bound> = Range<Bound> where Bound: Strideable, Bound.Stride: SignedInteger
private var buffer: ContiguousArray<Element?>
public struct CircularBuffer<Element>: CustomStringConvertible {
@usableFromInline
internal var _buffer: ContiguousArray<Element?>
/// The index into the buffer of the first item
private(set) /* private but tests */ internal var headIdx: Index
@usableFromInline
internal var headBackingIndex: Int = 0
/// The index into the buffer of the next free slot
private(set) /* private but tests */ internal var tailIdx: Index
@usableFromInline
internal var tailBackingIndex: Int = 0
public struct Index: Strideable, Comparable {
@usableFromInline var backingIndex: ContiguousArray<Element?>.Index
@usableFromInline let backingCount: Int
@inlinable
internal var mask: Int {
return self._buffer.count - 1
}
@inlinable
internal mutating func advanceHeadIdx(by: Int) {
self.headBackingIndex = indexAdvanced(index: self.headBackingIndex, by: by)
}
@inlinable
internal mutating func advanceTailIdx(by: Int) {
self.tailBackingIndex = indexAdvanced(index: self.tailBackingIndex, by: by)
}
@inlinable
internal func indexBeforeHeadIdx() -> Int {
return self.indexAdvanced(index: self.headBackingIndex, by: -1)
}
@inlinable
internal func indexBeforeTailIdx() -> Int {
return self.indexAdvanced(index: self.tailBackingIndex, by: -1)
}
@inlinable
internal func indexAdvanced(index: Int, by: Int) -> Int {
return (index + by) & self.mask
}
public struct Index: Comparable {
@usableFromInline var _backingIndex: UInt32
@usableFromInline var isIndexGEQHeadIndex: Bool
@inlinable var mask: Int { return self.backingCount - 1 }
@inlinable
internal init(backingIndex: ContiguousArray<Element?>.Index, backingIndexOfHead: ContiguousArray<Element?>.Index, backingCount: Int) {
internal var backingIndex: Int {
return Int(self._backingIndex)
}
@inlinable
internal init(backingIndex: Int, backingIndexOfHead: Int) {
self.isIndexGEQHeadIndex = backingIndex >= backingIndexOfHead
self.backingCount = backingCount
self.backingIndex = backingIndex
}
private init(backingIndex: ContiguousArray<Element?>.Index, greater: Bool, backingCount: Int) {
self.backingIndex = backingIndex
self.isIndexGEQHeadIndex = greater
self.backingCount = backingCount
self._backingIndex = UInt32(backingIndex)
}
@inlinable
public func distance(to other: Index) -> Int {
if self.isIndexGEQHeadIndex && other.isIndexGEQHeadIndex {
return self.backingIndex.distance(to: other.backingIndex)
} else if self.isIndexGEQHeadIndex && !other.isIndexGEQHeadIndex {
return self.backingIndex.distance(to: backingCount) + other.backingIndex
} else if !self.isIndexGEQHeadIndex && other.isIndexGEQHeadIndex {
return self.backingIndex.distance(to: 0) + backingCount.distance(to: other.backingIndex)
} else {
return self.backingIndex.distance(to: other.backingIndex)
}
}
public func advanced(by n: Int) -> Index {
var index = Index(backingIndex: 0, greater: self.isIndexGEQHeadIndex, backingCount: self.backingCount)
if n >= 0 {
if backingIndex + n >= self.backingCount {
index.isIndexGEQHeadIndex = !self.isIndexGEQHeadIndex
}
} else {
if backingIndex + n < 0 {
index.isIndexGEQHeadIndex = !self.isIndexGEQHeadIndex
}
}
index.backingIndex = (self.backingIndex + n) & (self.mask)
return index
}
public static func < (lhs: Index, rhs: Index) -> Bool {
if lhs.isIndexGEQHeadIndex && rhs.isIndexGEQHeadIndex {
return lhs.backingIndex < rhs.backingIndex
@ -94,18 +85,169 @@ public struct CircularBuffer<Element>: CustomStringConvertible, AppendableCollec
}
}
// MARK: Collection/MutableCollection implementation
extension CircularBuffer: Collection, MutableCollection {
public typealias RangeType<Bound> = Range<Bound> where Bound: Strideable, Bound.Stride: SignedInteger
public typealias SubSequence = CircularBuffer<Element>
/// Returns the position immediately after the given index.
///
/// The successor of an index must be well defined. For an index `i` into a
/// collection `c`, calling `c.index(after: i)` returns the same index every
/// time.
///
/// - Parameter i: A valid index of the collection. `i` must be less than
/// `endIndex`.
/// - Returns: The index value immediately after `i`.
@inlinable
public func index(after: Index) -> Index {
return self.index(after, offsetBy: 1)
}
/// Returns the index before `index`.
@inlinable
public func index(before: Index) -> Index {
return self.index(before, offsetBy: -1)
}
/// Accesses the element at the specified index.
///
/// You can subscript `CircularBuffer` with any valid index other than the
/// `CircularBuffer`'s end index. The end index refers to the position one
/// past the last element of a collection, so it doesn't correspond with an
/// element.
///
/// - Parameter position: The position of the element to access. `position`
/// must be a valid index of the collection that is not equal to the
/// `endIndex` property.
///
/// - Complexity: O(1)
@inlinable
public subscript(position: Index) -> Element {
get {
return self._buffer[position.backingIndex]!
}
set {
self._buffer[position.backingIndex] = newValue
}
}
/// The position of the first element in a nonempty `CircularBuffer`.
///
/// If the `CircularBuffer` is empty, `startIndex` is equal to `endIndex`.
@inlinable
public var startIndex: Index {
return .init(backingIndex: self.headBackingIndex, backingIndexOfHead: self.headBackingIndex)
}
/// The `CircularBuffer`'s "past the end" position---that is, the position one
/// greater than the last valid subscript argument.
///
/// When you need a range that includes the last element of a collection, use
/// the half-open range operator (`..<`) with `endIndex`. The `..<` operator
/// creates a range that doesn't include the upper bound, so it's always
/// safe to use with `endIndex`.
///
/// If the `CircularBuffer` is empty, `endIndex` is equal to `startIndex`.
@inlinable
public var endIndex: Index {
return .init(backingIndex: self.tailBackingIndex, backingIndexOfHead: self.headBackingIndex)
}
/// Returns the distance between two indices.
///
/// Unless the collection conforms to the `BidirectionalCollection` protocol,
/// `start` must be less than or equal to `end`.
///
/// - Parameters:
/// - start: A valid index of the collection.
/// - end: Another valid index of the collection. If `end` is equal to
/// `start`, the result is zero.
/// - Returns: The distance between `start` and `end`. The result can be
/// negative only if the collection conforms to the
/// `BidirectionalCollection` protocol.
///
/// - Complexity: O(1) if the collection conforms to
/// `RandomAccessCollection`; otherwise, O(*k*), where *k* is the
/// resulting distance.
@inlinable
public func distance(from start: CircularBuffer<Element>.Index, to end: CircularBuffer<Element>.Index) -> Int {
let backingCount = self._buffer.count
switch (start.isIndexGEQHeadIndex, end.isIndexGEQHeadIndex) {
case (true, true):
return end.backingIndex - start.backingIndex
case (true, false):
return backingCount - (start.backingIndex - end.backingIndex)
case (false, true):
return -(backingCount - (end.backingIndex - start.backingIndex))
case (false, false):
return end.backingIndex - start.backingIndex
}
}
}
// MARK: RandomAccessCollection implementation
extension CircularBuffer: RandomAccessCollection {
/// Returns the index offset by `distance` from `index`.
@inlinable
public func index(_ i: Index, offsetBy distance: Int) -> Index {
return .init(backingIndex: (i.backingIndex + distance) & self.mask,
backingIndexOfHead: self.headBackingIndex)
}
/// Returns an index that is the specified distance from the given index.
///
/// The following example obtains an index advanced four positions from a
/// string's starting index and then prints the character at that position.
///
/// let s = "Swift"
/// let i = s.index(s.startIndex, offsetBy: 4)
/// print(s[i])
/// // Prints "t"
///
/// The value passed as `distance` must not offset `i` beyond the bounds of
/// the collection.
///
/// - Parameters:
/// - i: A valid index of the collection.
/// - distance: The distance to offset `i`. `distance` must not be negative
/// unless the collection conforms to the `BidirectionalCollection`
/// protocol.
/// - Returns: An index offset by `distance` from the index `i`. If
/// `distance` is positive, this is the same value as the result of
/// `distance` calls to `index(after:)`. If `distance` is negative, this
/// is the same value as the result of `abs(distance)` calls to
/// `index(before:)`.
///
/// - Complexity: O(1) if the collection conforms to
/// `RandomAccessCollection`; otherwise, O(*k*), where *k* is the absolute
/// value of `distance`.
@inlinable
public subscript(bounds: Range<Index>) -> SubSequence {
precondition(self.distance(from: self.startIndex, to: bounds.lowerBound) >= 0)
precondition(self.distance(from: bounds.upperBound, to: self.endIndex) >= 0)
var newRing = self
newRing.headBackingIndex = bounds.lowerBound.backingIndex
newRing.tailBackingIndex = bounds.upperBound.backingIndex
return newRing
}
}
extension CircularBuffer {
/// Allocates a buffer that can hold up to `initialCapacity` elements and initialise an empty ring backed by
/// the buffer. When the ring grows to more than `initialCapacity` elements the buffer will be expanded.
@inlinable
public init(initialCapacity: Int) {
let capacity = Int(UInt32(initialCapacity).nextPowerOf2())
self.buffer = ContiguousArray<Element?>(repeating: nil, count: capacity)
self.headIdx = Index(backingIndex: 0, backingIndexOfHead: 0, backingCount: capacity)
self.tailIdx = Index(backingIndex: 0, backingIndexOfHead: 0, backingCount: capacity)
assert(self.buffer.count == capacity)
self._buffer = ContiguousArray<Element?>(repeating: nil, count: capacity)
assert(self._buffer.count == capacity)
}
/// Allocates an empty buffer.
@inlinable
public init() {
self.init(initialCapacity: 16)
}
@ -113,55 +255,57 @@ extension CircularBuffer {
/// Append an element to the end of the ring buffer.
///
/// Amortized *O(1)*
@inlinable
public mutating func append(_ value: Element) {
self.buffer[self.tailIdx.backingIndex] = value
self.tailIdx = self.index(after: self.tailIdx)
self.tailIdx.isIndexGEQHeadIndex = self.tailIdx.backingIndex >= self.headIdx.backingIndex
self._buffer[self.tailBackingIndex] = value
self.advanceTailIdx(by: 1)
if self.headIdx == self.tailIdx {
if self.headBackingIndex == self.tailBackingIndex {
// No more room left for another append so grow the buffer now.
self.doubleCapacity()
self._doubleCapacity()
}
}
/// Prepend an element to the front of the ring buffer.
///
/// Amortized *O(1)*
@inlinable
public mutating func prepend(_ value: Element) {
let idx = self.index(before: self.headIdx)
self.buffer[idx.backingIndex] = value
self.headIdx.backingIndex = idx.backingIndex
self.headIdx.isIndexGEQHeadIndex = true
self.tailIdx.isIndexGEQHeadIndex = self.tailIdx.backingIndex >= self.headIdx.backingIndex
let idx = self.indexBeforeHeadIdx()
self._buffer[idx] = value
self.advanceHeadIdx(by: -1)
if self.headIdx == self.tailIdx {
if self.headBackingIndex == self.tailBackingIndex {
// No more room left for another append so grow the buffer now.
self.doubleCapacity()
self._doubleCapacity()
}
}
/// Double the capacity of the buffer and adjust the headIdx and tailIdx.
private mutating func doubleCapacity() {
@inlinable
internal mutating func _doubleCapacity() {
var newBacking: ContiguousArray<Element?> = []
let newCapacity = self.buffer.count << 1 // Double the storage.
precondition(newCapacity > 0, "Can't double capacity of \(self.buffer.count)")
let newCapacity = self._buffer.count << 1 // Double the storage.
precondition(newCapacity > 0, "Can't double capacity of \(self._buffer.count)")
assert(newCapacity % 2 == 0)
newBacking.reserveCapacity(newCapacity)
newBacking.append(contentsOf: self.buffer[self.headIdx.backingIndex..<self.buffer.count])
if self.headIdx.backingIndex > 0 {
newBacking.append(contentsOf: self.buffer[0..<self.headIdx.backingIndex])
newBacking.append(contentsOf: self._buffer[self.headBackingIndex..<self._buffer.count])
if self.headBackingIndex > 0 {
newBacking.append(contentsOf: self._buffer[0..<self.headBackingIndex])
}
let repeatitionCount = newCapacity - newBacking.count
newBacking.append(contentsOf: repeatElement(nil, count: repeatitionCount))
self.headIdx = Index(backingIndex: 0, backingIndexOfHead: 0, backingCount: newBacking.count)
self.tailIdx = Index(backingIndex: newBacking.count - repeatitionCount, backingIndexOfHead: self.headIdx.backingIndex, backingCount: newBacking.count)
self.buffer = newBacking
self.headBackingIndex = 0
self.tailBackingIndex = newBacking.count - repeatitionCount
self._buffer = newBacking
assert(self.verifyInvariants())
}
/// Return element `offset` from first element.
///
/// *O(1)*
@inlinable
public subscript(offset offset: Int) -> Element {
get {
return self[self.index(self.startIndex, offsetBy: offset)]
@ -170,123 +314,216 @@ extension CircularBuffer {
self[self.index(self.startIndex, offsetBy: offset)] = newValue
}
}
// MARK: Collection implementation
/// Return element `index` of the ring.
///
/// *O(1)*
public subscript(index: Index) -> Element {
get {
return self.buffer[index.backingIndex]!
}
set {
self.buffer[index.backingIndex] = newValue
}
}
/// Return all valid indices of the ring.
public var indices: RangeType<Index> {
return self.startIndex ..< self.endIndex
}
/// Returns whether the ring is empty.
@inlinable
public var isEmpty: Bool {
return self.headIdx == self.tailIdx
return self.headBackingIndex == self.tailBackingIndex
}
/// Returns the number of element in the ring.
@inlinable
public var count: Int {
return self.headIdx.distance(to: self.tailIdx)
if self.tailBackingIndex >= self.headBackingIndex {
return self.tailBackingIndex - self.headBackingIndex
} else {
return self._buffer.count - (self.headBackingIndex - self.tailBackingIndex)
}
}
/// The total number of elements that the ring can contain without allocating new storage.
@inlinable
public var capacity: Int {
return self.buffer.count
return self._buffer.count
}
/// Returns the index of the first element of the ring.
public var startIndex: Index {
return self.headIdx
}
/// Returns the ring's "past the end" position -- that is, the position one greater than the last valid subscript argument.
public var endIndex: Index {
return self.tailIdx
}
/// Returns the next index after `index`.
public func index(after: Index) -> Index {
return after.advanced(by: 1)
}
/// Returns the index before `index`.
public func index(before: Index) -> Index {
return before.advanced(by: -1)
}
/// Returns the index offset by `distance` from `index`.
public func index(_ i: Index, offsetBy distance: Int) -> Index {
return i.advanced(by: distance)
}
/// Removes all members from the circular buffer whist keeping the capacity.
@inlinable
public mutating func removeAll(keepingCapacity: Bool = false) {
if keepingCapacity {
for index in self.startIndex..<self.endIndex {
self.buffer[index.backingIndex] = nil
}
self.removeFirst(self.count)
} else {
self.buffer.removeAll(keepingCapacity: false)
self.buffer.append(nil)
self._buffer.removeAll(keepingCapacity: false)
self._buffer.append(nil)
}
self.headIdx = Index(backingIndex: 0, backingIndexOfHead: 0, backingCount: self.buffer.count)
self.tailIdx = Index(backingIndex: 0, backingIndexOfHead: 0, backingCount: self.buffer.count)
assert(self.buffer.allSatisfy { $0 == nil})
self.headBackingIndex = 0
self.tailBackingIndex = 0
assert(self.verifyInvariants())
}
// MARK: CustomStringConvertible implementation
/// Returns a human readable description of the ring.
public var description: String {
var desc = "[ "
for el in self.buffer.enumerated() {
if el.0 == self.headIdx.backingIndex {
for el in self._buffer.enumerated() {
if el.0 == self.headBackingIndex {
desc += "<"
} else if el.0 == self.tailIdx.backingIndex {
} else if el.0 == self.tailBackingIndex {
desc += ">"
}
desc += el.1.map { "\($0) " } ?? "_ "
}
desc += "]"
desc += " (bufferCapacity: \(self.buffer.count), ringLength: \(self.count))"
desc += " (bufferCapacity: \(self._buffer.count), ringLength: \(self.count))"
return desc
}
}
// MARK: - BidirectionalCollection, RandomAccessCollection, RangeReplaceableCollection
extension CircularBuffer: BidirectionalCollection, RandomAccessCollection, RangeReplaceableCollection {
/// Replaces the specified subrange of elements with the given collection.
// MARK: - RangeReplaceableCollection
extension CircularBuffer: RangeReplaceableCollection {
/// Removes and returns the first element of the `CircularBuffer`.
///
/// - Parameter subrange:
/// The subrange of the collection to replace. The bounds of the range must be valid indices of the collection.
/// Calling this method may invalidate all saved indices of this
/// `CircularBuffer`. Do not rely on a previously stored index value after
/// altering a `CircularBuffer` with any operation that can change its length.
///
/// - Parameter newElements:
/// The new elements to add to the collection.
/// - Returns: The first element of the `CircularBuffer` if the `CircularBuffer` is not
/// empty; otherwise, `nil`.
///
/// - Complexity: O(1)
@inlinable
public mutating func popFirst() -> Element? {
if count > 0 {
return self.removeFirst()
} else {
return nil
}
}
/// Removes and returns the last element of the `CircularBuffer`.
///
/// Calling this method may invalidate all saved indices of this
/// `CircularBuffer`. Do not rely on a previously stored index value after
/// altering a `CircularBuffer` with any operation that can change its length.
///
/// - Returns: The last element of the `CircularBuffer` if the `CircularBuffer` is not
/// empty; otherwise, `nil`.
///
/// - Complexity: O(1)
@inlinable
public mutating func popLast() -> Element? {
if count > 0 {
return self.removeLast()
} else {
return nil
}
}
/// Removes the specified number of elements from the end of the
/// `CircularBuffer`.
///
/// Attempting to remove more elements than exist in the `CircularBuffer`
/// triggers a runtime error.
///
/// Calling this method may invalidate all saved indices of this
/// `CircularBuffer`. Do not rely on a previously stored index value after
/// altering a `CircularBuffer` with any operation that can change its length.
///
/// - Parameter k: The number of elements to remove from the `CircularBuffer`.
/// `k` must be greater than or equal to zero and must not exceed the
/// number of elements in the `CircularBuffer`.
///
/// - Complexity: O(*k*), where *k* is the specified number of elements.
@inlinable
public mutating func removeLast(_ k: Int) {
precondition(k <= self.count, "Number of elements to drop bigger than the amount of elements in the buffer.")
var idx = self.tailBackingIndex
for _ in 0 ..< k {
idx = self.indexAdvanced(index: idx, by: -1)
self._buffer[idx] = nil
}
self.tailBackingIndex = idx
}
/// Removes the specified number of elements from the beginning of the
/// `CircularBuffer`.
///
/// Calling this method may invalidate any existing indices for use with this
/// `CircularBuffer`.
///
/// - Parameter k: The number of elements to remove.
/// `k` must be greater than or equal to zero and must not exceed the
/// number of elements in the `CircularBuffer`.
///
/// - Complexity: O(*k*), where *k* is the specified number of elements.
@inlinable
public mutating func removeFirst(_ k: Int) {
precondition(k <= self.count, "Number of elements to drop bigger than the amount of elements in the buffer.")
var idx = self.headBackingIndex
for _ in 0 ..< k {
self._buffer[idx] = nil
idx = self.indexAdvanced(index: idx, by: 1)
}
self.headBackingIndex = idx
}
/// Removes and returns the first element of the `CircularBuffer`.
///
/// The `CircularBuffer` must not be empty.
///
/// Calling this method may invalidate any existing indices for use with this
/// `CircularBuffer`.
///
/// - Returns: The removed element.
///
/// - Complexity: O(*1*)
@discardableResult
@inlinable
public mutating func removeFirst() -> Element {
defer {
self.removeFirst(1)
}
return self.first!
}
/// Removes and returns the last element of the `CircularBuffer`.
///
/// The `CircularBuffer` must not be empty.
///
/// Calling this method may invalidate all saved indices of this
/// `CircularBuffer`. Do not rely on a previously stored index value after
/// altering the `CircularBuffer` with any operation that can change its length.
///
/// - Returns: The last element of the `CircularBuffer`.
///
/// - Complexity: O(*1*)
@discardableResult
@inlinable
public mutating func removeLast() -> Element {
defer {
self.removeLast(1)
}
return self.last!
}
/// Replaces the specified subrange of elements with the given `CircularBuffer`.
///
/// - Parameter subrange: The subrange of the collection to replace. The bounds of the range must be valid indices
/// of the `CircularBuffer`.
///
/// - Parameter newElements: The new elements to add to the `CircularBuffer`.
///
/// *O(n)* where _n_ is the length of the new elements collection if the subrange equals to _n_
///
/// *O(m)* where _m_ is the combined length of the collection and _newElements_
public mutating func replaceSubrange<C>(_ subrange: Range<Index>, with newElements: C) where C : Collection, Element == C.Element {
@inlinable
public mutating func replaceSubrange<C: Collection>(_ subrange: Range<Index>, with newElements: C) where Element == C.Element {
precondition(subrange.lowerBound >= self.startIndex && subrange.upperBound <= self.endIndex, "Subrange out of bounds")
if subrange.count == newElements.count {
for (index, element) in zip(subrange, newElements) {
self.buffer[index.backingIndex] = element
let subrangeCount = self.distance(from: subrange.lowerBound, to: subrange.upperBound)
if subrangeCount == newElements.count {
var index = subrange.lowerBound
for element in newElements {
self._buffer[index.backingIndex] = element
index = self.index(after: index)
}
} else if subrange.count == self.count && newElements.isEmpty {
} else if subrangeCount == self.count && newElements.isEmpty {
self.removeSubrange(subrange)
} else {
var newBuffer: ContiguousArray<Element?> = []
let neededNewCapacity = self.count + newElements.count - subrange.count + 1 /* always one spare */
let neededNewCapacity = self.count + newElements.count - subrangeCount + 1 /* always one spare */
let newCapacity = Swift.max(self.capacity, neededNewCapacity.nextPowerOf2())
newBuffer.reserveCapacity(newCapacity)
@ -301,38 +538,30 @@ extension CircularBuffer: BidirectionalCollection, RandomAccessCollection, Range
if repetitionCount > 0 {
newBuffer.append(contentsOf: repeatElement(nil, count: repetitionCount))
}
self.buffer = newBuffer
self.headIdx = Index(backingIndex: 0, backingIndexOfHead: 0, backingCount: newBuffer.count)
self.tailIdx = Index(backingIndex: newBuffer.count - repetitionCount, backingIndexOfHead: self.headIdx.backingIndex, backingCount: newBuffer.count)
self._buffer = newBuffer
self.headBackingIndex = 0
self.tailBackingIndex = newBuffer.count - repetitionCount
}
assert(self.verifyInvariants())
}
/// Removes the elements in the specified subrange from the circular buffer.
///
/// - Parameter bounds: The range of the circular buffer to be removed. The bounds of the range must be valid indices of the collection.
@inlinable
public mutating func removeSubrange(_ bounds: Range<Index>) {
precondition(bounds.upperBound >= self.startIndex && bounds.upperBound <= self.endIndex, "Invalid bounds.")
switch bounds.count {
let boundsCount = self.distance(from: bounds.lowerBound, to: bounds.upperBound)
switch boundsCount {
case 1:
remove(at: bounds.lowerBound)
case self.count:
self = .init(initialCapacity: self.buffer.count)
self = .init(initialCapacity: self._buffer.count)
default:
replaceSubrange(bounds, with: [])
}
}
/// Removes the given number of elements from the end of the collection.
///
/// - Parameter n: The number of elements to remove from the tail of the buffer.
public mutating func removeLast(_ n: Int) {
precondition(n <= self.count, "Number of elements to drop bigger than the amount of elements in the buffer.")
var idx = self.tailIdx
for _ in 0 ..< n {
self.buffer[idx.backingIndex] = nil
idx = self.index(before: idx)
}
self.tailIdx = self.tailIdx.advanced(by: -n)
assert(self.verifyInvariants())
}
/// Removes & returns the item at `position` from the buffer
@ -343,31 +572,64 @@ extension CircularBuffer: BidirectionalCollection, RandomAccessCollection, Range
/// otherwise
/// *O(n)* where *n* is the number of elements between `position` and `tailIdx`.
@discardableResult
@inlinable
public mutating func remove(at position: Index) -> Element {
defer {
assert(self.verifyInvariants())
}
precondition(self.indices.contains(position), "Position out of bounds.")
var bufferIndex = position
let element = self.buffer[bufferIndex.backingIndex]!
var bufferIndex = position.backingIndex
let element = self._buffer[bufferIndex]!
switch bufferIndex {
case self.headIdx:
self.headIdx = self.headIdx.advanced(by: 1)
self.headIdx.isIndexGEQHeadIndex = true
self.tailIdx.isIndexGEQHeadIndex = self.tailIdx.backingIndex >= self.headIdx.backingIndex
self.buffer[bufferIndex.backingIndex] = nil
case self.index(before: self.tailIdx):
self.tailIdx = self.index(before: self.tailIdx)
self.buffer[bufferIndex.backingIndex] = nil
case self.headBackingIndex:
self.advanceHeadIdx(by: 1)
self._buffer[bufferIndex] = nil
case self.indexBeforeHeadIdx():
self.advanceTailIdx(by: -1)
self.tailBackingIndex = self.indexBeforeTailIdx()
self._buffer[bufferIndex] = nil
default:
var nextIndex = self.index(after: bufferIndex)
while nextIndex != self.tailIdx {
self.buffer[bufferIndex.backingIndex] = self.buffer[nextIndex.backingIndex]
self._buffer[bufferIndex] = nil
var nextIndex = self.indexAdvanced(index: bufferIndex, by: 1)
while nextIndex != self.tailBackingIndex {
self._buffer.swapAt(bufferIndex, nextIndex)
bufferIndex = nextIndex
nextIndex = self.index(after: bufferIndex)
nextIndex = self.indexAdvanced(index: bufferIndex, by: 1)
}
self.buffer[nextIndex.backingIndex] = nil
self.tailIdx = self.index(before: self.tailIdx)
self.advanceTailIdx(by: -1)
}
return element
}
}
extension CircularBuffer {
@usableFromInline
internal func verifyInvariants() -> Bool {
var index = self.headBackingIndex
while index != self.tailBackingIndex {
if self._buffer[index] == nil {
return false
}
index = self.indexAdvanced(index: index, by: 1)
}
return true
}
// this is not a general invariant (not true for CircularBuffer that have been sliced)
private func unreachableAreNil() -> Bool {
var index = self.tailBackingIndex
while index != self.headBackingIndex {
if self._buffer[index] != nil {
return false
}
index = self.indexAdvanced(index: index, by: 1)
}
return true
}
internal func testOnly_verifyInvariantsForNonSlices() -> Bool {
return self.verifyInvariants() && self.unreachableAreNil()
}
}

View File

@ -17,10 +17,7 @@
/// This object is used extensively within SwiftNIO to handle flushable buffers. It can be used to store buffered
/// writes and mark how far through the buffer the user has flushed, and therefore how far through the buffer is
/// safe to write.
public struct MarkedCircularBuffer<Element>: CustomStringConvertible, AppendableCollection {
public typealias RangeType<Bound> = Range<Bound> where Bound: Strideable, Bound.Stride: SignedInteger
public typealias Index = CircularBuffer<Element>.Index
public struct MarkedCircularBuffer<Element>: CustomStringConvertible {
private var buffer: CircularBuffer<Element>
private var markedIndexOffset: Int? = nil /* nil: nothing marked */
@ -41,17 +38,19 @@ public struct MarkedCircularBuffer<Element>: CustomStringConvertible, Appendable
/// Removes the first element from the buffer.
public mutating func removeFirst() -> Element {
return self.popFirst()!
}
public mutating func popFirst() -> Element? {
assert(self.buffer.count > 0)
if let markedIndex = self.markedIndexOffset {
if self.startIndex.advanced(by: markedIndex) == self.startIndex {
self.markedIndexOffset = nil
if let markedIndexOffset = self.markedIndexOffset {
if markedIndexOffset > 0 {
self.markedIndexOffset = markedIndexOffset - 1
} else {
if let markedIndexOffset = self.markedIndexOffset {
self.markedIndexOffset = markedIndexOffset - 1
}
self.markedIndexOffset = nil
}
}
return self.buffer.removeFirst()
return self.buffer.popFirst()
}
/// The first element in the buffer.
@ -69,29 +68,6 @@ public struct MarkedCircularBuffer<Element>: CustomStringConvertible, Appendable
return self.buffer.count
}
/// Retrieves the element at the given index from the buffer, without removing it.
public subscript(index: Index) -> Element {
get {
return self.buffer[index]
}
set {
self.buffer[index] = newValue
}
}
/// The valid indices into the buffer.
public var indices: RangeType<Index> {
return self.buffer.indices
}
public var startIndex: Index { return self.buffer.startIndex }
public var endIndex: Index { return self.buffer.endIndex }
public func index(after i: Index) -> Index {
return self.buffer.index(after: i)
}
public var description: String {
return self.buffer.description
}
@ -113,7 +89,7 @@ public struct MarkedCircularBuffer<Element>: CustomStringConvertible, Appendable
assert(index >= self.startIndex, "index must not be negative")
precondition(index < self.endIndex, "index \(index) out of range (0..<\(self.buffer.count))")
if let markedIndexOffset = self.markedIndexOffset {
return self.startIndex.advanced(by: markedIndexOffset) == index
return self.index(self.startIndex, offsetBy: markedIndexOffset) == index
} else {
return false
}
@ -123,7 +99,7 @@ public struct MarkedCircularBuffer<Element>: CustomStringConvertible, Appendable
public var markedElementIndex: Index? {
if let markedIndexOffset = self.markedIndexOffset {
assert(markedIndexOffset >= 0)
return self.startIndex.advanced(by: markedIndexOffset)
return self.index(self.startIndex, offsetBy: markedIndexOffset)
} else {
return nil
}
@ -139,3 +115,48 @@ public struct MarkedCircularBuffer<Element>: CustomStringConvertible, Appendable
return self.markedIndexOffset != nil
}
}
extension MarkedCircularBuffer: Collection, MutableCollection {
public typealias RangeType<Bound> = Range<Bound> where Bound: Strideable, Bound.Stride: SignedInteger
public typealias Index = CircularBuffer<Element>.Index
public typealias SubSequence = CircularBuffer<Element>
public func index(after i: Index) -> Index {
return self.buffer.index(after: i)
}
public var startIndex: Index { return self.buffer.startIndex }
public var endIndex: Index { return self.buffer.endIndex }
/// Retrieves the element at the given index from the buffer, without removing it.
public subscript(index: Index) -> Element {
get {
return self.buffer[index]
}
set {
self.buffer[index] = newValue
}
}
public subscript(bounds: Range<Index>) -> SubSequence {
get {
return self.buffer[bounds]
}
}
}
extension MarkedCircularBuffer: RandomAccessCollection {
public func index(_ i: Index, offsetBy distance: Int) -> Index {
return self.buffer.index(i, offsetBy: distance)
}
public func distance(from start: Index, to end: Index) -> Int {
return self.buffer.distance(from: start, to: end)
}
public func index(before i: Index) -> Index {
return self.buffer.index(before: i)
}
}

View File

@ -295,10 +295,11 @@ private struct PendingDatagramWritesState {
/// Returns the best mechanism to write pending data at the current point in time.
var currentBestWriteMechanism: WriteMechanism {
switch self.pendingWrites.markedElementIndex {
case .some(let e) where self.pendingWrites.startIndex.distance(to: e) > 0:
case .some(let e) where self.pendingWrites.distance(from: self.pendingWrites.startIndex, to: e) > 0:
return .vectorBufferWrite
case .some(let e):
assert(e.distance(to: self.pendingWrites.startIndex) == 0) // The compiler can't prove this, but it must be so.
// The compiler can't prove this, but it must be so.
assert(self.pendingWrites.distance(from: e, to: self.pendingWrites.startIndex) == 0)
return .scalarBufferWrite
default:
return .nothingToBeWritten
@ -320,9 +321,10 @@ extension PendingDatagramWritesState {
}
mutating func next() -> PendingDatagramWrite? {
while let markedIndex = self.markedIndex, self.index.distance(to: markedIndex) >= 0 {
while let markedIndex = self.markedIndex, self.pendingWrites.pendingWrites.distance(from: self.index,
to: markedIndex) >= 0 {
let element = self.pendingWrites.pendingWrites[index]
index = index.advanced(by: 1)
index = self.pendingWrites.pendingWrites.index(after: index)
return element
}

View File

@ -106,7 +106,9 @@ private struct PendingStreamWritesState {
public private(set) var bytes: Int64 = 0
public var flushedChunks: Int {
return self.pendingWrites.markedElementIndex.map { self.pendingWrites.startIndex.distance(to: $0) + 1 } ?? 0
return self.pendingWrites.markedElementIndex.map {
self.pendingWrites.distance(from: self.pendingWrites.startIndex, to: $0) + 1
} ?? 0
}
/// Subtract `bytes` from the number of outstanding bytes to write.
@ -162,7 +164,7 @@ private struct PendingStreamWritesState {
/// Get the outstanding write at `index`.
public subscript(index: Int) -> PendingStreamWrite {
return self.pendingWrites[self.pendingWrites.startIndex.advanced(by: index)]
return self.pendingWrites[self.pendingWrites.index(self.pendingWrites.startIndex, offsetBy: index)]
}
/// Mark the flush checkpoint.
@ -253,7 +255,8 @@ private struct PendingStreamWritesState {
}
default:
let startIndex = self.pendingWrites.startIndex
switch (self.pendingWrites[startIndex].data, self.pendingWrites[startIndex.advanced(by: 1)].data) {
switch (self.pendingWrites[startIndex].data,
self.pendingWrites[self.pendingWrites.index(after: startIndex)].data) {
case (.byteBuffer, .byteBuffer):
return .vectorBufferWrite
case (.byteBuffer, .fileRegion):

View File

@ -260,7 +260,7 @@ extension MarkedCircularBuffer {
public typealias E = Element
func _makeIndex(value: Int) -> Index {
return self.startIndex.advanced(by: value)
return self.index(self.startIndex, offsetBy: value)
}
@available(*, deprecated, renamed: "init(initialCapacity:)")
@ -377,7 +377,7 @@ extension SocketAddress {
extension CircularBuffer {
func _makeIndex(value: Int) -> Index {
return self.startIndex.advanced(by: value)
return self.index(self.startIndex, offsetBy: value)
}
@available(*, deprecated, renamed: "Element")

View File

@ -65,6 +65,13 @@ extension CircularBufferTests {
("testIntIndexing", testIntIndexing),
("testIndexDistance", testIndexDistance),
("testIndexAdvancing", testIndexAdvancing),
("testPopFirst", testPopFirst),
("testSlicing", testSlicing),
("testRemoveInMiddle", testRemoveInMiddle),
("testLotsOfPrepending", testLotsOfPrepending),
("testLotsOfInsertAtStart", testLotsOfInsertAtStart),
("testLotsOfInsertAtEnd", testLotsOfInsertAtEnd),
("testPopLast", testPopLast),
]
}
}

View File

@ -19,7 +19,9 @@ class CircularBufferTests: XCTestCase {
func testTrivial() {
var ring = CircularBuffer<Int>(initialCapacity: 8)
ring.append(1)
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
XCTAssertEqual(1, ring.removeFirst())
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
}
func testAddRemoveInALoop() {
@ -43,10 +45,12 @@ class CircularBufferTests: XCTestCase {
for f in 1..<1000 {
ring.append(f)
XCTAssertEqual(f, ring.count)
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
}
for f in 1..<1000 {
XCTAssertEqual(f, ring.removeFirst())
XCTAssertEqual(999 - f, ring.count)
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
}
XCTAssertTrue(ring.isEmpty)
}
@ -55,10 +59,12 @@ class CircularBufferTests: XCTestCase {
var ring = CircularBuffer<Int>(initialCapacity: 4)
for idx in 0..<7 {
ring.prepend(idx)
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
}
XCTAssertEqual(7, ring.count)
_ = ring.remove(at: ring.startIndex.advanced(by: 1))
_ = ring.remove(at: ring.index(ring.startIndex, offsetBy: 1))
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
XCTAssertEqual(6, ring.count)
XCTAssertEqual(0, ring.last)
}
@ -69,7 +75,7 @@ class CircularBufferTests: XCTestCase {
ring.prepend(idx)
}
let last = ring.remove(at: ring.endIndex.advanced(by: -1))
let last = ring.remove(at: ring.index(ring.endIndex, offsetBy: -1))
XCTAssertEqual(0, last)
XCTAssertEqual(1, ring.last)
}
@ -79,7 +85,8 @@ class CircularBufferTests: XCTestCase {
ring.prepend(99)
ring.prepend(98)
XCTAssertEqual(2, ring.count)
XCTAssertEqual(99, ring.remove(at: ring.endIndex.advanced(by: -1)))
XCTAssertEqual(99, ring.remove(at: ring.index(ring.endIndex, offsetBy: -1)))
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
XCTAssertFalse(ring.isEmpty)
XCTAssertEqual(1, ring.count)
XCTAssertEqual(98, ring.last)
@ -97,85 +104,131 @@ class CircularBufferTests: XCTestCase {
XCTAssertEqual(5, ring.first)
}
func collectAllIndices<Element>(ring: CircularBuffer<Element>) -> [CircularBuffer<Element>.Index] {
return Array(ring.indices)
}
func collectAllIndices<Element>(ring: CircularBuffer<Element>, range: Range<CircularBuffer<Element>.Index>) -> [CircularBuffer<Element>.Index] {
var index: CircularBuffer<Element>.Index = range.lowerBound
var allIndices: [CircularBuffer<Element>.Index] = []
while index != range.upperBound {
allIndices.append(index)
index = ring.index(after: index)
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
}
return allIndices
}
func testHarderExpansion() {
var ring = CircularBuffer<Int>(initialCapacity: 3)
XCTAssertEqual(ring.indices, ring.startIndex ..< ring.startIndex)
XCTAssertEqual(self.collectAllIndices(ring: ring),
self.collectAllIndices(ring: ring, range: ring.startIndex ..< ring.startIndex))
ring.append(1)
XCTAssertEqual(ring.count, 1)
XCTAssertEqual(ring[ring.startIndex], 1)
XCTAssertEqual(ring.indices, ring.startIndex ..< ring.startIndex.advanced(by: 1))
XCTAssertEqual(self.collectAllIndices(ring: ring),
self.collectAllIndices(ring: ring, range: ring.startIndex ..< ring.index(ring.startIndex,
offsetBy: 1)))
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
ring.append(2)
XCTAssertEqual(ring.count, 2)
XCTAssertEqual(ring[ring.startIndex], 1)
XCTAssertEqual(ring[ring.startIndex.advanced(by: 1)], 2)
XCTAssertEqual(ring.indices, ring.startIndex ..< ring.startIndex.advanced(by: 2))
XCTAssertEqual(ring[ring.index(ring.startIndex, offsetBy: 1)], 2)
XCTAssertEqual(self.collectAllIndices(ring: ring),
self.collectAllIndices(ring: ring, range: ring.startIndex ..< ring.index(ring.startIndex,
offsetBy: 2)))
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
ring.append(3)
XCTAssertEqual(ring.count, 3)
XCTAssertEqual(ring[ring.startIndex], 1)
XCTAssertEqual(ring[ring.startIndex.advanced(by: 1)], 2)
XCTAssertEqual(ring[ring.startIndex.advanced(by: 2)], 3)
XCTAssertEqual(ring.indices, ring.startIndex ..< ring.startIndex.advanced(by: 3))
XCTAssertEqual(ring[ring.index(ring.startIndex, offsetBy: 1)], 2)
XCTAssertEqual(ring[ring.index(ring.startIndex, offsetBy: 2)], 3)
XCTAssertEqual(self.collectAllIndices(ring: ring),
self.collectAllIndices(ring: ring, range: ring.startIndex ..< ring.index(ring.startIndex,
offsetBy: 3)))
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
XCTAssertEqual(1, ring.removeFirst())
XCTAssertEqual(ring.count, 2)
XCTAssertEqual(ring[ring.startIndex], 2)
XCTAssertEqual(ring[ring.startIndex.advanced(by: 1)], 3)
XCTAssertEqual(ring.indices, ring.startIndex ..< ring.startIndex.advanced(by: 2))
XCTAssertEqual(ring[ring.index(ring.startIndex, offsetBy: 1)], 3)
XCTAssertEqual(self.collectAllIndices(ring: ring),
self.collectAllIndices(ring: ring, range: ring.startIndex ..< ring.index(ring.startIndex,
offsetBy: 2)))
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
XCTAssertEqual(2, ring.removeFirst())
XCTAssertEqual(ring.count, 1)
XCTAssertEqual(ring[ring.startIndex], 3)
XCTAssertEqual(ring.indices, ring.startIndex ..< ring.startIndex.advanced(by: 1))
XCTAssertEqual(self.collectAllIndices(ring: ring),
self.collectAllIndices(ring: ring, range: ring.startIndex ..< ring.index(ring.startIndex,
offsetBy: 1)))
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
ring.append(5)
XCTAssertEqual(ring.count, 2)
XCTAssertEqual(ring[ring.startIndex], 3)
XCTAssertEqual(ring[ring.startIndex.advanced(by: 1)], 5)
XCTAssertEqual(ring.indices, ring.startIndex ..< ring.startIndex.advanced(by: 2))
XCTAssertEqual(ring[ring.index(ring.startIndex, offsetBy: 1)], 5)
XCTAssertEqual(self.collectAllIndices(ring: ring),
self.collectAllIndices(ring: ring, range: ring.startIndex ..< ring.index(ring.startIndex,
offsetBy: 2)))
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
ring.append(6)
XCTAssertEqual(ring.count, 3)
XCTAssertEqual(ring[ring.startIndex], 3)
XCTAssertEqual(ring[ring.startIndex.advanced(by: 1)], 5)
XCTAssertEqual(ring[ring.startIndex.advanced(by: 2)], 6)
XCTAssertEqual(ring.indices, ring.startIndex ..< ring.startIndex.advanced(by: 3))
XCTAssertEqual(ring[ring.index(ring.startIndex, offsetBy: 1)], 5)
XCTAssertEqual(ring[ring.index(ring.startIndex, offsetBy: 2)], 6)
XCTAssertEqual(self.collectAllIndices(ring: ring),
self.collectAllIndices(ring: ring, range: ring.startIndex ..< ring.index(ring.startIndex,
offsetBy: 3)))
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
ring.append(7)
XCTAssertEqual(ring.count, 4)
XCTAssertEqual(ring[ring.startIndex], 3)
XCTAssertEqual(ring[ring.startIndex.advanced(by: 1)], 5)
XCTAssertEqual(ring[ring.startIndex.advanced(by: 2)], 6)
XCTAssertEqual(ring[ring.startIndex.advanced(by: 3)], 7)
XCTAssertEqual(ring.indices, ring.startIndex ..< ring.startIndex.advanced(by: 4))
XCTAssertEqual(ring[ring.index(ring.startIndex, offsetBy: 1)], 5)
XCTAssertEqual(ring[ring.index(ring.startIndex, offsetBy: 2)], 6)
XCTAssertEqual(ring[ring.index(ring.startIndex, offsetBy: 3)], 7)
XCTAssertEqual(self.collectAllIndices(ring: ring),
self.collectAllIndices(ring: ring, range: ring.startIndex ..< ring.index(ring.startIndex,
offsetBy: 4)))
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
}
func testCollection() {
var ring = CircularBuffer<Int>(initialCapacity: 4)
XCTAssertEqual(ring.indices, ring.startIndex ..< ring.startIndex)
XCTAssertEqual(0, ring.startIndex.distance(to: ring.endIndex))
XCTAssertEqual(0, ring.endIndex.distance(to: ring.startIndex))
XCTAssertEqual(self.collectAllIndices(ring: ring),
self.collectAllIndices(ring: ring, range: ring.startIndex ..< ring.startIndex))
XCTAssertEqual(0, ring.distance(from: ring.startIndex, to: ring.endIndex))
XCTAssertEqual(0, ring.distance(from: ring.startIndex, to: ring.startIndex))
for idx in 0..<5 {
ring.append(idx)
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
}
XCTAssertFalse(ring.isEmpty)
XCTAssertEqual(5, ring.count)
XCTAssertEqual(ring.indices, ring.startIndex ..< ring.startIndex.advanced(by: 5))
XCTAssertEqual(self.collectAllIndices(ring: ring),
self.collectAllIndices(ring: ring, range: ring.startIndex ..< ring.index(ring.startIndex,
offsetBy: 5)))
XCTAssertEqual(ring.startIndex, ring.startIndex)
XCTAssertEqual(ring.endIndex, ring.startIndex.advanced(by: 5))
XCTAssertEqual(ring.endIndex, ring.index(ring.startIndex, offsetBy: 5))
XCTAssertEqual(ring.index(after: ring.startIndex.advanced(by: 1)), ring.startIndex.advanced(by: 2))
XCTAssertEqual(ring.index(before: ring.startIndex.advanced(by: 3)), ring.startIndex.advanced(by: 2))
XCTAssertEqual(ring.index(after: ring.index(ring.startIndex, offsetBy: 1)),
ring.index(ring.startIndex, offsetBy: 2))
XCTAssertEqual(ring.index(before: ring.index(ring.startIndex, offsetBy: 3)),
ring.index(ring.startIndex, offsetBy: 2))
let actualValues = [Int](ring)
let expectedValues = [0, 1, 2, 3, 4]
XCTAssertEqual(expectedValues, actualValues)
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
}
func testReplaceSubrange5ElementsWith1() {
@ -184,12 +237,14 @@ class CircularBufferTests: XCTestCase {
ring.prepend(idx)
}
XCTAssertEqual(50, ring.count)
ring.replaceSubrange(ring.startIndex.advanced(by: 20) ..< ring.startIndex.advanced(by: 25), with: [99])
ring.replaceSubrange(ring.index(ring.startIndex, offsetBy: 20) ..< ring.index(ring.startIndex, offsetBy: 25),
with: [99])
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
XCTAssertEqual(ring.count, 46)
XCTAssertEqual(ring[ring.startIndex.advanced(by: 19)], 30)
XCTAssertEqual(ring[ring.startIndex.advanced(by: 20)], 99)
XCTAssertEqual(ring[ring.startIndex.advanced(by: 21)], 24)
XCTAssertEqual(ring[ring.index(ring.startIndex, offsetBy: 19)], 30)
XCTAssertEqual(ring[ring.index(ring.startIndex, offsetBy: 20)], 99)
XCTAssertEqual(ring[ring.index(ring.startIndex, offsetBy: 21)], 24)
}
func testReplaceSubrangeAllElementsWithFewerElements() {
@ -200,6 +255,7 @@ class CircularBufferTests: XCTestCase {
XCTAssertEqual(50, ring.count)
ring.replaceSubrange(ring.startIndex..<ring.endIndex, with: [3,4])
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
XCTAssertEqual(2, ring.count)
XCTAssertEqual(3, ring.first)
XCTAssertEqual(4, ring.last)
@ -213,6 +269,7 @@ class CircularBufferTests: XCTestCase {
XCTAssertEqual(50, ring.count)
ring.replaceSubrange(ring.startIndex ..< ring.startIndex, with: [])
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
XCTAssertEqual(50, ring.count)
}
@ -224,6 +281,7 @@ class CircularBufferTests: XCTestCase {
XCTAssertEqual(5, ring.count)
ring.replaceSubrange(ring.startIndex..<ring.endIndex, with: [10,11,12,13,14,15,16,17,18,19])
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
XCTAssertEqual(10, ring.count)
XCTAssertEqual(10, ring.first)
XCTAssertEqual(19, ring.last)
@ -247,6 +305,7 @@ class CircularBufferTests: XCTestCase {
XCTAssertEqual(4, replacement.last)
ring.replaceSubrange(ring.startIndex..<ring.endIndex, with: replacement)
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
XCTAssertEqual(5, ring.count)
XCTAssertEqual(0, ring.first)
XCTAssertEqual(4, ring.last)
@ -262,6 +321,7 @@ class CircularBufferTests: XCTestCase {
XCTAssertEqual(0, ring.last)
ring.replaceSubrange(ring.startIndex..<ring.endIndex, with: [])
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
XCTAssertTrue(ring.isEmpty)
}
@ -270,6 +330,7 @@ class CircularBufferTests: XCTestCase {
XCTAssertTrue(ring.isEmpty)
for idx in 0..<4 {
ring.append(idx)
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
}
XCTAssertEqual(4, ring.count)
XCTAssertFalse(ring.isEmpty)
@ -281,7 +342,7 @@ class CircularBufferTests: XCTestCase {
ring.append(idx)
}
for idx in 0..<5 {
XCTAssertEqual(idx, ring[ring.startIndex.advanced(by: idx)])
XCTAssertEqual(idx, ring[ring.index(ring.startIndex, offsetBy: idx)])
}
}
@ -292,23 +353,23 @@ class CircularBufferTests: XCTestCase {
}
/* the underlying buffer should now be filled from 0 to max */
for idx in 0..<4 {
XCTAssertEqual(idx, ring[ring.startIndex.advanced(by: idx)])
XCTAssertEqual(idx, ring[ring.index(ring.startIndex, offsetBy: idx)])
}
XCTAssertEqual(0, ring.removeFirst())
/* now the first element is gone, ie. the ring starts at index 1 now */
for idx in 0..<3 {
XCTAssertEqual(idx + 1, ring[ring.startIndex.advanced(by: idx)])
XCTAssertEqual(idx + 1, ring[ring.index(ring.startIndex, offsetBy: idx)])
}
ring.append(4)
XCTAssertEqual(1, ring.first!)
/* now the last element should be at ring position 0 */
for idx in 0..<4 {
XCTAssertEqual(idx + 1, ring[ring.startIndex.advanced(by: idx)])
XCTAssertEqual(idx + 1, ring[ring.index(ring.startIndex, offsetBy: idx)])
}
/* and now we'll make it expand */
ring.append(5)
for idx in 0..<5 {
XCTAssertEqual(idx + 1, ring[ring.startIndex.advanced(by: idx)])
XCTAssertEqual(idx + 1, ring[ring.index(ring.startIndex, offsetBy: idx)])
}
}
@ -318,7 +379,7 @@ class CircularBufferTests: XCTestCase {
ring.append(idx)
}
for idx in 0..<4 {
XCTAssertEqual(idx, ring[ring.startIndex.advanced(by: idx)])
XCTAssertEqual(idx, ring[ring.index(ring.startIndex, offsetBy: idx)])
}
for idx in 0..<4 {
XCTAssertEqual(idx, ring.removeFirst())
@ -336,9 +397,11 @@ class CircularBufferTests: XCTestCase {
for (idx, element) in ring.enumerated() {
XCTAssertEqual(idx, element)
changes.append((idx, element * 2))
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
}
for change in changes {
ring[ring.startIndex.advanced(by: change.0)] = change.1
ring[ring.index(ring.startIndex, offsetBy: change.0)] = change.1
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
}
for (idx, element) in ring.enumerated() {
XCTAssertEqual(idx * 2, element)
@ -351,9 +414,10 @@ class CircularBufferTests: XCTestCase {
ring.append(idx)
}
let slice = ring[ring.startIndex.advanced(by: 25) ..< ring.startIndex.advanced(by: 30)]
let slice = ring[ring.index(ring.startIndex, offsetBy: 25) ..< ring.index(ring.startIndex, offsetBy: 30)]
for (idx, element) in slice.enumerated() {
XCTAssertEqual(ring.startIndex.advanced(by: idx + 25), ring.startIndex.advanced(by: element))
XCTAssertEqual(ring.index(ring.startIndex, offsetBy: idx + 25),
ring.index(ring.startIndex, offsetBy: element))
}
}
@ -364,20 +428,25 @@ class CircularBufferTests: XCTestCase {
ring.append(2)
XCTAssertEqual(2, ring.count)
XCTAssertEqual(1, ring.removeFirst())
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
ring.append(3)
XCTAssertEqual(2, ring.count)
XCTAssertEqual(2, ring.removeFirst())
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
ring.append(4)
XCTAssertEqual(2, ring.count)
XCTAssertEqual(3, ring.removeFirst())
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
ring.append(5)
XCTAssertEqual(3, ring.headIdx.backingIndex)
XCTAssertEqual(1, ring.tailIdx.backingIndex)
XCTAssertEqual(3, ring.headBackingIndex)
XCTAssertEqual(1, ring.tailBackingIndex)
XCTAssertEqual(2, ring.count)
XCTAssertEqual(4, ring.removeFirst())
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
XCTAssertEqual(5, ring.removeFirst())
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
XCTAssertEqual(0, ring.count)
XCTAssertTrue(ring.isEmpty)
}
@ -388,6 +457,7 @@ class CircularBufferTests: XCTestCase {
ring.append(1)
XCTAssertEqual(1, ring.first)
XCTAssertEqual(1, ring.removeFirst())
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
XCTAssertNil(ring.first)
}
@ -397,6 +467,7 @@ class CircularBufferTests: XCTestCase {
ring.prepend(1)
XCTAssertEqual(1, ring.last)
XCTAssertEqual(1, ring.removeLast())
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
XCTAssertNil(ring.last)
XCTAssertEqual(0, ring.count)
XCTAssertTrue(ring.isEmpty)
@ -409,6 +480,7 @@ class CircularBufferTests: XCTestCase {
ring.prepend(0)
XCTAssertEqual(1, ring.last)
XCTAssertEqual(1, ring.removeLast())
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
XCTAssertEqual(0, ring.last)
}
@ -447,7 +519,9 @@ class CircularBufferTests: XCTestCase {
XCTAssertEqual(2, ring.first)
XCTAssertEqual(1, ring.removeLast())
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
XCTAssertEqual(2, ring.removeFirst())
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
XCTAssertNil(ring.last)
XCTAssertNil(ring.first)
@ -463,6 +537,7 @@ class CircularBufferTests: XCTestCase {
}
for f in 1..<1000 {
XCTAssertEqual(f, ring.removeLast())
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
}
XCTAssertTrue(ring.isEmpty)
XCTAssertEqual(0, ring.count)
@ -477,6 +552,7 @@ class CircularBufferTests: XCTestCase {
XCTAssertEqual(ring.capacity, 4)
XCTAssertEqual(ring.count, 2)
ring.removeAll(keepingCapacity: true)
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
XCTAssertEqual(ring.capacity, 4)
XCTAssertEqual(ring.count, 0)
}
@ -490,6 +566,7 @@ class CircularBufferTests: XCTestCase {
XCTAssertEqual(ring.capacity, 4)
XCTAssertEqual(ring.count, 2)
ring.removeAll(keepingCapacity: false)
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
// 1 is the smallest capacity we have
XCTAssertEqual(ring.capacity, 1)
XCTAssertEqual(ring.count, 0)
@ -500,6 +577,7 @@ class CircularBufferTests: XCTestCase {
XCTAssertEqual(ring.capacity, 4)
XCTAssertEqual(ring.count, 2)
ring.removeAll() // default should not keep capacity
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
XCTAssertEqual(ring.capacity, 1)
XCTAssertEqual(ring.count, 0)
}
@ -511,7 +589,8 @@ class CircularBufferTests: XCTestCase {
// Now we want to replace the last subrange with two elements. This should
// force an increase in size.
ring.replaceSubrange(ring.startIndex ..< ring.startIndex.advanced(by: 1), with: [3, 4])
ring.replaceSubrange(ring.startIndex ..< ring.index(ring.startIndex, offsetBy: 1), with: [3, 4])
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
XCTAssertEqual(ring.capacity, 4)
}
@ -558,15 +637,21 @@ class CircularBufferTests: XCTestCase {
var ring = CircularBuffer<Int>(initialCapacity: 4)
(0..<16).forEach { ring.append($0) }
(0..<4).forEach { _ in ring.removeFirst() }
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
(0..<4).forEach { _ in _ = ring.removeFirst() }
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
(16..<20).forEach { ring.append($0) }
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
XCTAssertEqual(Array(4..<20), Array(ring))
ring.removeAll(keepingCapacity: shouldKeepCapacity)
(0..<8).forEach { ring.append($0) }
(0..<4).forEach { _ in ring.removeFirst() }
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
(0..<4).forEach { _ in _ = ring.removeFirst() }
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
(8..<64).forEach { ring.append($0) }
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
XCTAssertEqual(Array(4..<64), Array(ring))
}
@ -597,7 +682,7 @@ class CircularBufferTests: XCTestCase {
XCTAssertNotNil(dummy1)
XCTAssertNotNil(dummy2)
ring.removeFirst()
_ = ring.removeFirst()
for _ in 2..<8 {
ring.append(Dummy())
@ -653,42 +738,165 @@ class CircularBufferTests: XCTestCase {
}
func testIndexDistance() {
let index1 = CircularBuffer<Int>.Index(backingIndex: 0, backingIndexOfHead: 0, backingCount: 4)
let index2 = CircularBuffer<Int>.Index(backingIndex: 1, backingIndexOfHead: 0, backingCount: 4)
XCTAssertEqual(index1.distance(to: index2), 1)
var bufferOfBackingSize4 = CircularBuffer<Int>(initialCapacity: 4)
XCTAssertEqual(3, bufferOfBackingSize4.indexBeforeHeadIdx())
let index3 = CircularBuffer<Int>.Index(backingIndex: 2, backingIndexOfHead: 1, backingCount: 4)
let index4 = CircularBuffer<Int>.Index(backingIndex: 0, backingIndexOfHead: 1, backingCount: 4)
XCTAssertEqual(index3.distance(to: index4), 2)
let index1 = CircularBuffer<Int>.Index(backingIndex: 0, backingIndexOfHead: 0)
let index2 = CircularBuffer<Int>.Index(backingIndex: 1, backingIndexOfHead: 0)
XCTAssertEqual(bufferOfBackingSize4.distance(from: index1, to: index2), 1)
let index5 = CircularBuffer<Int>.Index(backingIndex: 0, backingIndexOfHead: 1, backingCount: 4)
let index6 = CircularBuffer<Int>.Index(backingIndex: 2, backingIndexOfHead: 1, backingCount: 4)
XCTAssertEqual(index5.distance(to: index6), -2)
bufferOfBackingSize4.append(1)
XCTAssertEqual(1, bufferOfBackingSize4.removeFirst())
XCTAssertEqual(1, bufferOfBackingSize4.headBackingIndex)
let index3 = CircularBuffer<Int>.Index(backingIndex: 2, backingIndexOfHead: 1)
let index4 = CircularBuffer<Int>.Index(backingIndex: 0, backingIndexOfHead: 1)
XCTAssertEqual(bufferOfBackingSize4.distance(from: index3, to: index4), 2)
let index7 = CircularBuffer<Int>.Index(backingIndex: 0, backingIndexOfHead: 3, backingCount: 4)
let index8 = CircularBuffer<Int>.Index(backingIndex: 2, backingIndexOfHead: 3, backingCount: 4)
XCTAssertEqual(index7.distance(to: index8), 2)
let index5 = CircularBuffer<Int>.Index(backingIndex: 0, backingIndexOfHead: 1)
let index6 = CircularBuffer<Int>.Index(backingIndex: 2, backingIndexOfHead: 1)
XCTAssertEqual(bufferOfBackingSize4.distance(from: index5, to: index6), -2)
bufferOfBackingSize4.append(2)
bufferOfBackingSize4.append(3)
XCTAssertEqual(2, bufferOfBackingSize4.removeFirst())
XCTAssertEqual(3, bufferOfBackingSize4.removeFirst())
XCTAssertEqual(3, bufferOfBackingSize4.headBackingIndex)
let index7 = CircularBuffer<Int>.Index(backingIndex: 0, backingIndexOfHead: 3)
let index8 = CircularBuffer<Int>.Index(backingIndex: 2, backingIndexOfHead: 3)
XCTAssertEqual(bufferOfBackingSize4.distance(from: index7, to: index8), 2)
}
func testIndexAdvancing() {
let index1 = CircularBuffer<Int>.Index(backingIndex: 0, backingIndexOfHead: 0, backingCount: 4)
let index2 = index1.advanced(by: 1)
var bufferOfBackingSize4 = CircularBuffer<Int>(initialCapacity: 4)
XCTAssertEqual(3, bufferOfBackingSize4.indexBeforeHeadIdx())
let index1 = CircularBuffer<Int>.Index(backingIndex: 0, backingIndexOfHead: 0)
let index2 = bufferOfBackingSize4.index(after: index1)
XCTAssertEqual(index2.backingIndex, 1)
XCTAssertEqual(index2.isIndexGEQHeadIndex, true)
let index3 = CircularBuffer<Int>.Index(backingIndex: 3, backingIndexOfHead: 2, backingCount: 4)
let index4 = index3.advanced(by: 1)
bufferOfBackingSize4.append(1)
bufferOfBackingSize4.append(2)
XCTAssertEqual(1, bufferOfBackingSize4.removeFirst())
XCTAssertEqual(2, bufferOfBackingSize4.removeFirst())
XCTAssertEqual(2, bufferOfBackingSize4.headBackingIndex)
let index3 = CircularBuffer<Int>.Index(backingIndex: 3, backingIndexOfHead: 2)
let index4 = bufferOfBackingSize4.index(after: index3)
XCTAssertEqual(index4.backingIndex, 0)
XCTAssertEqual(index4.isIndexGEQHeadIndex, false)
let index5 = CircularBuffer<Int>.Index(backingIndex: 0, backingIndexOfHead: 1, backingCount: 4)
let index6 = index5.advanced(by: -1)
bufferOfBackingSize4.prepend(3)
XCTAssertEqual(1, bufferOfBackingSize4.headBackingIndex)
let index5 = CircularBuffer<Int>.Index(backingIndex: 0, backingIndexOfHead: 1)
let index6 = bufferOfBackingSize4.index(before: index5)
XCTAssertEqual(index6.backingIndex, 3)
XCTAssertEqual(index6.isIndexGEQHeadIndex, true)
let index7 = CircularBuffer<Int>.Index(backingIndex: 2, backingIndexOfHead: 1, backingCount: 4)
let index8 = index7.advanced(by: -1)
let index7 = CircularBuffer<Int>.Index(backingIndex: 2, backingIndexOfHead: 1)
let index8 = bufferOfBackingSize4.index(before: index7)
XCTAssertEqual(index8.backingIndex, 1)
XCTAssertEqual(index8.isIndexGEQHeadIndex, true)
}
func testPopFirst() {
var buf = CircularBuffer([1, 2, 3])
if let element = buf.popFirst() {
XCTAssertEqual(1, element)
} else {
XCTFail("popFirst didn't find first element")
}
if let element = buf.popFirst() {
XCTAssertEqual(2, element)
} else {
XCTFail("popFirst didn't find second element")
}
if let element = buf.popFirst() {
XCTAssertEqual(3, element)
} else {
XCTFail("popFirst didn't find third element")
}
XCTAssertNil(buf.popFirst())
XCTAssertTrue(buf.testOnly_verifyInvariantsForNonSlices())
}
func testSlicing() {
var buf = CircularBuffer<Int>()
for i in -4..<124 {
buf.append(i)
}
XCTAssertEqual(-4, buf.removeFirst())
XCTAssertEqual(-3, buf.removeFirst())
XCTAssertEqual(-2, buf.removeFirst())
XCTAssertEqual(-1, buf.removeFirst())
buf.append(124)
buf.append(125)
buf.append(126)
buf.append(127)
let buf2: CircularBuffer<Int> = buf[buf.index(buf.startIndex, offsetBy: 100) ..< buf.endIndex]
XCTAssertEqual(Array(100..<128), Array(buf2))
XCTAssertEqual(Array(100..<128), Array(buf[buf2.startIndex ..< buf2.endIndex]))
}
func testRemoveInMiddle() {
var buf = CircularBuffer<Int>(initialCapacity: 8)
for i in 0..<7 {
buf.append(i)
}
XCTAssertEqual(0, buf.removeFirst())
XCTAssertTrue(buf.testOnly_verifyInvariantsForNonSlices())
buf.append(7)
XCTAssertEqual(2, buf.remove(at: buf.index(buf.startIndex, offsetBy: 1)))
XCTAssertTrue(buf.testOnly_verifyInvariantsForNonSlices())
XCTAssertEqual([1, 3, 4, 5, 6, 7], Array(buf))
buf.removeAll(keepingCapacity: true)
XCTAssertTrue(buf.testOnly_verifyInvariantsForNonSlices())
XCTAssertEqual([], Array(buf))
}
func testLotsOfPrepending() {
var buf = CircularBuffer<Int>(initialCapacity: 8)
for i in (0..<128).reversed() {
buf.prepend(i)
}
XCTAssertEqual(Array(0..<128), Array(buf))
}
func testLotsOfInsertAtStart() {
var buf = CircularBuffer<Int>(initialCapacity: 8)
for i in (0..<128).reversed() {
buf.insert(i, at: buf.startIndex)
}
XCTAssertEqual(Array(0..<128), Array(buf))
}
func testLotsOfInsertAtEnd() {
var buf = CircularBuffer<Int>(initialCapacity: 8)
for i in (0..<128) {
buf.insert(i, at: buf.endIndex)
}
XCTAssertEqual(Array(0..<128), Array(buf))
}
func testPopLast() {
var buf = CircularBuffer<Int>(initialCapacity: 4)
buf.append(1)
XCTAssertEqual(1, buf.popLast())
XCTAssertNil(buf.popLast())
buf.append(1)
buf.append(2)
buf.append(3)
XCTAssertEqual(3, buf.popLast())
XCTAssertEqual(2, buf.popLast())
XCTAssertEqual(1, buf.popLast())
XCTAssertNil(buf.popLast())
}
}

View File

@ -37,11 +37,11 @@ class MarkedCircularBufferTests: XCTestCase {
XCTAssertTrue(buf.hasMark)
XCTAssertEqual(buf.markedElement, 4)
XCTAssertEqual(buf.markedElementIndex, buf.startIndex.advanced(by: 3))
XCTAssertEqual(buf.markedElementIndex, buf.index(buf.startIndex, offsetBy: 3))
for i in 0..<3 { XCTAssertFalse(buf.isMarked(index: buf.startIndex.advanced(by: i))) }
XCTAssertTrue(buf.isMarked(index: buf.startIndex.advanced(by: 3)))
for i in 4..<8 { XCTAssertFalse(buf.isMarked(index: buf.startIndex.advanced(by: i))) }
for i in 0..<3 { XCTAssertFalse(buf.isMarked(index: buf.index(buf.startIndex, offsetBy: i))) }
XCTAssertTrue(buf.isMarked(index: buf.index(buf.startIndex, offsetBy: 3)))
for i in 4..<8 { XCTAssertFalse(buf.isMarked(index: buf.index(buf.startIndex, offsetBy: i))) }
}
func testPassingTheMark() throws {
@ -55,7 +55,7 @@ class MarkedCircularBufferTests: XCTestCase {
XCTAssertEqual(buf.removeFirst(), j)
XCTAssertTrue(buf.hasMark)
XCTAssertEqual(buf.markedElement, 4)
XCTAssertEqual(buf.markedElementIndex, buf.startIndex.advanced(by: 3 - j))
XCTAssertEqual(buf.markedElementIndex, buf.index(buf.startIndex, offsetBy: 3 - j))
}
XCTAssertEqual(buf.removeFirst(), 4)
@ -73,8 +73,8 @@ class MarkedCircularBufferTests: XCTestCase {
XCTAssertTrue(buf.hasMark)
XCTAssertEqual(buf.markedElement, i)
XCTAssertEqual(buf.markedElementIndex, buf.startIndex.advanced(by: i - 1))
XCTAssertTrue(buf.isMarked(index: buf.startIndex.advanced(by: i - 1)))
XCTAssertEqual(buf.markedElementIndex, buf.index(buf.startIndex, offsetBy: i - 1))
XCTAssertTrue(buf.isMarked(index: buf.index(buf.startIndex, offsetBy: i - 1)))
}
}
func testIndices() throws {
@ -82,7 +82,14 @@ class MarkedCircularBufferTests: XCTestCase {
for i in 1...4 {
buf.append(i)
}
XCTAssertEqual(buf.indices, buf.startIndex ..< buf.startIndex.advanced(by: 4))
var allIndices: [MarkedCircularBuffer<Int>.Index] = []
var index = buf.startIndex
while index != buf.endIndex {
allIndices.append(index)
index = buf.index(after: index)
}
XCTAssertEqual(Array(buf.indices), allIndices)
}
func testFirst() throws {
@ -107,7 +114,7 @@ class MarkedCircularBufferTests: XCTestCase {
buf.append(i)
}
XCTAssertEqual(buf[buf.startIndex], 1)
XCTAssertEqual(buf[buf.startIndex.advanced(by: 3)], 4)
XCTAssertEqual(buf[buf.index(buf.startIndex, offsetBy: 3)], 4)
}
func testIsEmpty() throws {