RingBuffer.swift 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  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 overwritten.
  39. /// - Parameter element: The element to push to the back of the buffer.
  40. /// - Returns: The element that was overwritten or `nil` if nothing was overwritten.
  41. /// - Complexity: O(1)
  42. @discardableResult
  43. mutating func push(_ element: Element) throws -> Element? {
  44. guard circularQueue.count > 0 else {
  45. // Do not push if `circularQueue` is a fixed empty array.
  46. return nil
  47. }
  48. guard circularQueue.indices.contains(tailIndex) else {
  49. // We have somehow entered an invalid state (#10025).
  50. throw Self.Error.outOfBoundsPush(
  51. pushIndex: tailIndex,
  52. endIndex: circularQueue.endIndex
  53. )
  54. }
  55. let replaced = circularQueue[tailIndex]
  56. circularQueue[tailIndex] = element
  57. // Increment index, wrapping around to the start if needed.
  58. tailIndex += 1
  59. if tailIndex >= circularQueue.endIndex {
  60. tailIndex = circularQueue.startIndex
  61. }
  62. return replaced
  63. }
  64. /// Pops an element from the back of the buffer, returning the element (`Element?`) that was popped.
  65. /// - Returns: The element that was popped or `nil` if there was no element to pop.
  66. /// - Complexity: O(1)
  67. @discardableResult
  68. mutating func pop() -> Element? {
  69. guard circularQueue.count > 0 else {
  70. // Do not pop if `circularQueue` is a fixed empty array.
  71. return nil
  72. }
  73. // Decrement index, wrapping around to the back if needed.
  74. tailIndex -= 1
  75. if tailIndex < circularQueue.startIndex {
  76. tailIndex = circularQueue.endIndex - 1
  77. }
  78. guard let popped = circularQueue[tailIndex] else {
  79. return nil // There is no element to pop.
  80. }
  81. circularQueue[tailIndex] = nil
  82. return popped
  83. }
  84. func makeIterator() -> IndexingIterator<[Element]> {
  85. circularQueue
  86. .compactMap { $0 } // Remove `nil` elements.
  87. .makeIterator()
  88. }
  89. }
  90. // MARK: - Codable
  91. extension RingBuffer: Codable where Element: Codable {}