diff --git a/Sources/NIO/CircularBuffer.swift b/Sources/NIO/CircularBuffer.swift index 622c0ebea3..abd23ab254 100644 --- a/Sources/NIO/CircularBuffer.swift +++ b/Sources/NIO/CircularBuffer.swift @@ -50,36 +50,63 @@ public struct CircularBuffer: CustomStringConvertible, AppendableCollection { /// /// Amortized *O(1)* public mutating func append(_ value: E) { + return self.addLast(value) + } + + /// Add an element to the end of the ring buffer. + /// + /// Amortized *O(1)* + public mutating func addLast(_ value: E) { self.buffer[self.tailIdx] = value self.tailIdx = (self.tailIdx + 1) & self.mask if self.headIdx == self.tailIdx { // No more room left for another append so grow the buffer now. - var newBacking: ContiguousArray = [] - 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.. 0 { - newBacking.append(contentsOf: self.buffer[0.. = [] + 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.. 0 { + newBacking.append(contentsOf: self.buffer[0.. E { - precondition(!self.isEmpty) - let value = self.buffer[self.headIdx] - self.buffer[headIdx] = nil + + precondition(value != nil, "CircularBuffer is empty") + + self.buffer[self.headIdx] = nil self.headIdx = (self.headIdx + 1) & self.mask return value! @@ -89,11 +116,31 @@ public struct CircularBuffer: CustomStringConvertible, AppendableCollection { /// /// *O(1)* public var first: E? { - if self.isEmpty { - return nil - } else { - return self.buffer[self.headIdx] - } + // As self.buffer is filled with `nil` this will correctly return `nil` if the CircularBuffer is empty. + return self.buffer[self.headIdx] + } + + /// Remove the last element of the ring buffer. + /// + /// *O(1)* + public mutating func removeLast() -> E { + let idx = (self.tailIdx - 1) & self.mask + let value = self.buffer[idx] + + precondition(value != nil, "CircularBuffer is empty") + + self.buffer[idx] = nil + self.tailIdx = idx + + return value! + } + + /// Return the last element of the ring. + /// + /// *O(1)* + public var last: E? { + // As self.buffer is filled with `nil` this will correctly return `nil` if the CircularBuffer is empty. + return self.buffer[(self.tailIdx - 1) & self.mask] } private func bufferIndex(ofIndex index: Int) -> Int { diff --git a/Tests/NIOTests/CircularBufferTests+XCTest.swift b/Tests/NIOTests/CircularBufferTests+XCTest.swift index 315f6675ff..d7777850b7 100644 --- a/Tests/NIOTests/CircularBufferTests+XCTest.swift +++ b/Tests/NIOTests/CircularBufferTests+XCTest.swift @@ -39,6 +39,9 @@ extension CircularBufferTests { ("testSliceTheRing", testSliceTheRing), ("testCount", testCount), ("testFirst", testFirst), + ("testLast", testLast), + ("testOperateOnBothSides", testOperateOnBothSides), + ("testAddFirstExpandBuffer", testAddFirstExpandBuffer), ] } } diff --git a/Tests/NIOTests/CircularBufferTests.swift b/Tests/NIOTests/CircularBufferTests.swift index 406b1c0d20..13cd947aa3 100644 --- a/Tests/NIOTests/CircularBufferTests.swift +++ b/Tests/NIOTests/CircularBufferTests.swift @@ -256,4 +256,47 @@ class CircularBufferTests: XCTestCase { XCTAssertEqual(1, ring.removeFirst()) XCTAssertNil(ring.first) } + + func testLast() { + var ring = CircularBuffer(initialRingCapacity: 3) + XCTAssertNil(ring.last) + ring.addFirst(1) + XCTAssertEqual(1, ring.last) + XCTAssertEqual(1, ring.removeLast()) + XCTAssertNil(ring.last) + XCTAssertEqual(0, ring.count) + XCTAssertTrue(ring.isEmpty) + } + + func testOperateOnBothSides() { + var ring = CircularBuffer(initialRingCapacity: 3) + XCTAssertNil(ring.last) + ring.addFirst(1) + ring.addFirst(2) + + XCTAssertEqual(1, ring.last) + XCTAssertEqual(2, ring.first) + + XCTAssertEqual(1, ring.removeLast()) + XCTAssertEqual(2, ring.removeFirst()) + + XCTAssertNil(ring.last) + XCTAssertNil(ring.first) + XCTAssertEqual(0, ring.count) + XCTAssertTrue(ring.isEmpty) + } + + + func testAddFirstExpandBuffer() { + var ring = CircularBuffer(initialRingCapacity: 3) + for f in 1..<1000 { + ring.addFirst(f) + XCTAssertEqual(f, ring.count) + } + for f in 1..<1000 { + XCTAssertEqual(f, ring.removeLast()) + } + XCTAssertTrue(ring.isEmpty) + XCTAssertEqual(0, ring.count) + } }