blob: b1a9da416e790dbabe9bcd649d2218019b48a634 [file] [edit]
// Copyright 2025 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cache
type ringBuffer[T any] struct {
buffer []entry[T]
// head is the index immediately after the last non-empty entry in the buffer (i.e., the next write position).
head, tail, size int
revisionOf RevisionOf[T]
}
type entry[T any] struct {
revision int64
item T
}
type (
KeyPredicate = func([]byte) bool
RevisionOf[T any] func(T) int64
IterFunc[T any] func(rev int64, item T) bool
)
func newRingBuffer[T any](capacity int, revisionOf RevisionOf[T]) *ringBuffer[T] {
// assume capacity > 0 – validated by Cache
return &ringBuffer[T]{
buffer: make([]entry[T], capacity),
revisionOf: revisionOf,
}
}
func (r *ringBuffer[T]) Append(item T) {
entry := entry[T]{revision: r.revisionOf(item), item: item}
if r.full() {
r.tail = (r.tail + 1) % len(r.buffer)
} else {
r.size++
}
r.buffer[r.head] = entry
r.head = (r.head + 1) % len(r.buffer)
}
func (r *ringBuffer[T]) full() bool {
return r.size == len(r.buffer)
}
// AscendGreaterOrEqual iterates through entries in ascending order starting from the first entry with revision >= pivot.
// TODO: use binary search on the ring buffer to locate the first entry >= nextRev instead of a full scan
func (r *ringBuffer[T]) AscendGreaterOrEqual(pivot int64, iter IterFunc[T]) {
if r.size == 0 {
return
}
for n, i := 0, r.tail; n < r.size; n, i = n+1, (i+1)%len(r.buffer) {
entry := r.buffer[i]
if entry.revision < pivot {
continue
}
if !iter(entry.revision, entry.item) {
return
}
}
}
// AscendLessThan iterates in ascending order over entries with revision < pivot.
func (r *ringBuffer[T]) AscendLessThan(pivot int64, iter IterFunc[T]) {
if r.size == 0 {
return
}
for n, i := 0, r.tail; n < r.size; n, i = n+1, (i+1)%len(r.buffer) {
entry := r.buffer[i]
if entry.revision >= pivot {
return
}
if !iter(entry.revision, entry.item) {
return
}
}
}
// DescendGreaterThan iterates in descending order over entries with revision > pivot.
func (r *ringBuffer[T]) DescendGreaterThan(pivot int64, iter IterFunc[T]) {
if r.size == 0 {
return
}
for n, i := 0, r.moduloIndex(r.head-1); n < r.size; n, i = n+1, r.moduloIndex(i-1) {
entry := r.buffer[i]
if entry.revision <= pivot {
return
}
if !iter(entry.revision, entry.item) {
return
}
}
}
// DescendLessOrEqual iterates in descending order over entries with revision <= pivot.
func (r *ringBuffer[T]) DescendLessOrEqual(pivot int64, iter IterFunc[T]) {
if r.size == 0 {
return
}
for n, i := 0, r.moduloIndex(r.head-1); n < r.size; n, i = n+1, r.moduloIndex(i-1) {
entry := r.buffer[i]
if entry.revision > pivot {
continue
}
if !iter(entry.revision, entry.item) {
return
}
}
}
// PeekLatest returns the most recently-appended revision (or 0 if empty).
func (r *ringBuffer[T]) PeekLatest() int64 {
if r.size == 0 {
return 0
}
idx := (r.head - 1 + len(r.buffer)) % len(r.buffer)
return r.buffer[idx].revision
}
// PeekOldest returns the oldest revision currently stored (or 0 if empty).
func (r *ringBuffer[T]) PeekOldest() int64 {
if r.size == 0 {
return 0
}
return r.buffer[r.tail].revision
}
func (r *ringBuffer[T]) RebaseHistory() {
r.head, r.tail, r.size = 0, 0, 0
for i := range r.buffer {
r.buffer[i] = entry[T]{}
}
}
func (r *ringBuffer[T]) moduloIndex(index int) int {
return (index + len(r.buffer)) % len(r.buffer)
}