vtun/common/x/xchan/ringbuffer.go

128 lines
2.0 KiB
Go

package xchan
import (
"errors"
)
var ErrIsEmpty = errors.New("ringbuffer is empty")
// RingBuffer is a ring buffer for common types.
// It never is full and always grows if it will be full.
// It is not thread-safe(goroutine-safe) so you must use the lock-like synchronization primitive to use it in multiple writers and multiple readers.
type RingBuffer[T any] struct {
buf []T
initialSize int
size int
r int // read pointer
w int // write pointer
}
func NewRingBuffer[T any](initialSize int) *RingBuffer[T] {
if initialSize <= 0 {
panic("initial size must be great than zero")
}
// initial size must >= 2
if initialSize == 1 {
initialSize = 2
}
return &RingBuffer[T]{
buf: make([]T, initialSize),
initialSize: initialSize,
size: initialSize,
}
}
func (r *RingBuffer[T]) Read() (T, error) {
var t T
if r.r == r.w {
return t, ErrIsEmpty
}
v := r.buf[r.r]
r.r++
if r.r == r.size {
r.r = 0
}
return v, nil
}
func (r *RingBuffer[T]) Pop() T {
v, err := r.Read()
if err == ErrIsEmpty { // Empty
panic(ErrIsEmpty.Error())
}
return v
}
func (r *RingBuffer[T]) Peek() T {
if r.r == r.w { // Empty
panic(ErrIsEmpty.Error())
}
v := r.buf[r.r]
return v
}
func (r *RingBuffer[T]) Write(v T) {
r.buf[r.w] = v
r.w++
if r.w == r.size {
r.w = 0
}
if r.w == r.r { // full
r.grow()
}
}
func (r *RingBuffer[T]) grow() {
var size int
if r.size < 1024 {
size = r.size * 2
} else {
size = r.size + r.size/4
}
buf := make([]T, size)
copy(buf[0:], r.buf[r.r:])
copy(buf[r.size-r.r:], r.buf[0:r.r])
r.r = 0
r.w = r.size
r.size = size
r.buf = buf
}
func (r *RingBuffer[T]) IsEmpty() bool {
return r.r == r.w
}
// Capacity returns the size of the underlying buffer.
func (r *RingBuffer[T]) Capacity() int {
return r.size
}
func (r *RingBuffer[T]) Len() int {
if r.r == r.w {
return 0
}
if r.w > r.r {
return r.w - r.r
}
return r.size - r.r + r.w
}
func (r *RingBuffer[T]) Reset() {
r.r = 0
r.w = 0
r.size = r.initialSize
r.buf = make([]T, r.initialSize)
}