66
77/**
88 * Ring buffer implementation with a fixed size that will overwrite the oldest elements when at capacity.
9- * Backed by `SplFixedArray`, which means that it will always have a constant memory footprint while
10- * also avoiding dynamically resizing.
11- * This is NOT a copy-on-write data structure. Extra cloning is necessary to achieve this.
9+ * Backed by a regular PHP array that will dynamically grow in size until capacity is reached.
1210 *
1311 * `push` and `peek` operations are O(1).
1412 *
15- * `toArray` and `drain` are O(n) where n is the count of the buffer.
16- *
17- * This implementation will never duplicate arrays unless `toArray` or `drain` is called.
13+ * `toArray` and `drain` are O(n) where n is the count of the buffer. As long as no element was
14+ * overwritten or shifted, both are O(1) because the backing array can be returned as-is
1815 *
1916 * @template T
2017 */
2118class RingBuffer implements \Countable
2219{
2320 /**
24- * @var \SplFixedArray< T|null>
21+ * @var array<int, T|null>
2522 */
26- private $ buffer ;
23+ private $ buffer = [] ;
2724
2825 /**
2926 * @var int
@@ -60,7 +57,6 @@ public function __construct(int $capacity)
6057 throw new \RuntimeException ('RingBuffer capacity must be greater than 0 ' );
6158 }
6259 $ this ->capacity = $ capacity ;
63- $ this ->buffer = new \SplFixedArray ($ capacity );
6460 }
6561
6662 /**
@@ -110,7 +106,7 @@ public function push($value): void
110106
111107 $ this ->tail = ($ this ->tail + 1 ) % $ this ->capacity ;
112108
113- if ($ this ->isFull () ) {
109+ if ($ this ->count === $ this -> capacity ) {
114110 $ this ->head = ($ this ->head + 1 ) % $ this ->capacity ;
115111 } else {
116112 ++$ this ->count ;
@@ -125,7 +121,7 @@ public function push($value): void
125121 */
126122 public function shift ()
127123 {
128- if ($ this ->isEmpty () ) {
124+ if ($ this ->count === 0 ) {
129125 return null ;
130126 }
131127 $ value = $ this ->buffer [$ this ->head ];
@@ -146,12 +142,11 @@ public function shift()
146142 */
147143 public function peekBack ()
148144 {
149- if ($ this ->isEmpty () ) {
145+ if ($ this ->count === 0 ) {
150146 return null ;
151147 }
152- $ idx = ($ this ->tail - 1 + $ this ->capacity ) % $ this ->capacity ;
153148
154- return $ this ->buffer [$ idx ];
149+ return $ this ->buffer [( $ this -> tail - 1 + $ this -> capacity ) % $ this -> capacity ];
155150 }
156151
157152 /**
@@ -162,7 +157,7 @@ public function peekBack()
162157 */
163158 public function peekFront ()
164159 {
165- if ($ this ->isEmpty () ) {
160+ if ($ this ->count === 0 ) {
166161 return null ;
167162 }
168163
@@ -174,9 +169,7 @@ public function peekFront()
174169 */
175170 public function clear (): void
176171 {
177- for ($ i = 0 ; $ i < $ this ->count ; ++$ i ) {
178- $ this ->buffer [($ this ->head + $ i ) % $ this ->capacity ] = null ;
179- }
172+ $ this ->buffer = [];
180173 $ this ->count = 0 ;
181174 $ this ->head = 0 ;
182175 $ this ->tail = 0 ;
@@ -190,14 +183,28 @@ public function clear(): void
190183 */
191184 public function toArray (): array
192185 {
193- $ result = [];
194- for ($ i = 0 ; $ i < $ this ->count ; ++$ i ) {
195- $ value = $ this ->buffer [($ this ->head + $ i ) % $ this ->capacity ];
196- /** @var T $value */
197- $ result [] = $ value ;
186+ if ($ this ->count === 0 ) {
187+ return [];
198188 }
199189
200- return $ result ;
190+ // When we have never overwritten an element, or we did a full revolution, we can
191+ // just return the buffer as-is because it has the correct order
192+ if ($ this ->head === 0 && $ this ->count === \count ($ this ->buffer )) {
193+ /** @var array<T> */
194+ return $ this ->buffer ;
195+ }
196+
197+ // If the data doesn't wrap around, we can just return the slice from head to count
198+ if ($ this ->head + $ this ->count <= $ this ->capacity ) {
199+ /** @var array<T> */
200+ return \array_slice ($ this ->buffer , $ this ->head , $ this ->count );
201+ }
202+
203+ /** @var array<T> */
204+ return array_merge (
205+ \array_slice ($ this ->buffer , $ this ->head ),
206+ \array_slice ($ this ->buffer , 0 , $ this ->tail )
207+ );
201208 }
202209
203210 /**
0 commit comments