RingBuffer.swift 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  1. // Copyright 2021 Google LLC
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. import Foundation
  15. /// A generic circular queue structure.
  16. struct RingBuffer<Element>: Sequence {
  17. /// An array of heartbeats treated as a circular queue and intialized with a fixed capacity.
  18. private var circularQueue: [Element?]
  19. /// The current "tail" and insert point for the `circularQueue`.
  20. private var tailIndex: Array<Element?>.Index
  21. /// Error types for `RingBuffer` operations.
  22. enum Error: LocalizedError {
  23. case outOfBoundsPush(pushIndex: Array<Element?>.Index, endIndex: Array<Element?>.Index)
  24. var errorDescription: String {
  25. switch self {
  26. case let .outOfBoundsPush(pushIndex, endIndex):
  27. return "Out-of-bounds push at index \(pushIndex) to ring buffer with" +
  28. "end index of \(endIndex)."
  29. }
  30. }
  31. }
  32. /// Designated initializer.
  33. /// - Parameter capacity: An `Int` representing the capacity.
  34. init(capacity: Int) {
  35. circularQueue = Array(repeating: nil, count: capacity)
  36. tailIndex = circularQueue.startIndex
  37. }
  38. /// Pushes an element to the back of the buffer, returning the element (`Element?`) that was
  39. /// overwritten.
  40. /// - Parameter element: The element to push to the back of the buffer.
  41. /// - Returns: The element that was overwritten or `nil` if nothing was overwritten.
  42. /// - Complexity: O(1)
  43. @discardableResult
  44. mutating func push(_ element: Element) throws -> Element? {
  45. guard circularQueue.count > 0 else {
  46. // Do not push if `circularQueue` is a fixed empty array.
  47. return nil
  48. }
  49. guard circularQueue.indices.contains(tailIndex) else {
  50. // We have somehow entered an invalid state (#10025).
  51. throw Self.Error.outOfBoundsPush(
  52. pushIndex: tailIndex,
  53. endIndex: circularQueue.endIndex
  54. )
  55. }
  56. let replaced = circularQueue[tailIndex]
  57. circularQueue[tailIndex] = element
  58. // Increment index, wrapping around to the start if needed.
  59. tailIndex += 1
  60. if tailIndex >= circularQueue.endIndex {
  61. tailIndex = circularQueue.startIndex
  62. }
  63. return replaced
  64. }
  65. /// Pops an element from the back of the buffer, returning the element (`Element?`) that was
  66. /// popped.
  67. /// - Returns: The element that was popped or `nil` if there was no element to pop.
  68. /// - Complexity: O(1)
  69. @discardableResult
  70. mutating func pop() -> Element? {
  71. guard circularQueue.count > 0 else {
  72. // Do not pop if `circularQueue` is a fixed empty array.
  73. return nil
  74. }
  75. // Decrement index, wrapping around to the back if needed.
  76. tailIndex -= 1
  77. if tailIndex < circularQueue.startIndex {
  78. tailIndex = circularQueue.endIndex - 1
  79. }
  80. guard let popped = circularQueue[tailIndex] else {
  81. return nil // There is no element to pop.
  82. }
  83. circularQueue[tailIndex] = nil
  84. return popped
  85. }
  86. func makeIterator() -> IndexingIterator<[Element]> {
  87. circularQueue
  88. .compactMap { $0 } // Remove `nil` elements.
  89. .makeIterator()
  90. }
  91. }
  92. // MARK: - Codable
  93. extension RingBuffer: Codable where Element: Codable {}