Files
2024-04-19 19:20:07 -07:00

210 lines
4.9 KiB
Go

package ring
import (
"io"
)
// Ring is a ring buffer implementation that implements the io.Writer and io.Reader interfaces.
type Ring struct {
buf []byte
size int
rp int // Read pointer
wp int // Write pointer
isFull bool
// availableCap tracks the available capacity in the buffer.
// It avoids recalculating the available capacity on every write operation,
// which is needed in case the buffer needs to be resized.
availableCap int
}
const defaultBufferSize = 1 << 12 // 4KB
// NewRingBuffer creates a new ring buffer with the given size.
func NewRingBuffer(size int) *Ring {
return &Ring{
buf: make([]byte, size),
size: size,
availableCap: size,
}
}
// Write writes len(p) bytes from p to the underlying data buffer.
func (r *Ring) Write(p []byte) (int, error) {
if len(p) == 0 {
return 0, nil
}
if len(p) > r.availableCap {
oldLen := r.Len()
newSize := r.size * 2
if newSize < oldLen+len(p) {
newSize = oldLen + len(p)
// Add extra capacity to ensure the buffer is not resized to fit only the new data.
newSize += newSize / 2
}
r.resize(newSize, oldLen) // Ensure there's enough space for new data
}
n := r.write(p)
return n, nil
}
// WriteTo writes the buffer's data to w.
func (r *Ring) WriteTo(w io.Writer) (int64, error) {
if r.isEmpty() {
return 0, nil // Nothing to write
}
var totalWritten int64
// If the data wraps in the buffer, write in two parts: from read pointer to end, and from start to write pointer.
if r.wp <= r.rp {
// Data does not wrap, can directly write from r to w
n, err := w.Write(r.buf[r.rp:r.wp])
totalWritten += int64(n)
if err != nil {
return totalWritten, err
}
r.rp = 0
}
// Data does not wrap, or we are continuing from a previous write after a wrap.
if r.wp > 0 {
// Data wraps, write from r to end of buffer.
n, err := w.Write(r.buf[r.rp:])
totalWritten += int64(n)
if err != nil {
r.rp = (r.rp + n) % r.size
return totalWritten, err
}
}
// Update the ring buffer's read pointer and full flag after successful write.
// Since all data has been read, we can reset the ring buffer.
r.Reset()
return totalWritten, nil
}
func (r *Ring) write(p []byte) int {
endPos := (r.wp + len(p)) % r.size
n := len(p) // The number of bytes to write is determined by the length of p.
// Execute the copy operation based on the relative positions of wp and rp.
if r.wp >= r.rp {
// Write wraps the buffer when the buffer is being re-used.
if endPos < r.wp {
copy(r.buf[r.wp:], p[:r.size-r.wp])
copy(r.buf[:endPos], p[r.size-r.wp:])
} else {
copy(r.buf[r.wp:], p)
}
} else {
copy(r.buf[r.wp:endPos], p)
}
r.wp = endPos
// The buffer is full if after the write, the next write position (wp) would overwrite the read position (rp).
// Calculate the next write position after this write.
nextWp := (r.wp + 1) % r.size
// Initially check if the next position after wp is rp, indicating a full buffer on the next write.
r.isFull = nextWp == r.rp
// Exactly full if the next write position is the read position.
if r.wp == r.rp && n > 0 {
r.isFull = true
}
r.availableCap -= n
if r.availableCap < 0 {
r.availableCap = 0
}
return n
}
func (r *Ring) resize(newSize, oldLen int) {
newBuf := make([]byte, newSize)
r.read(newBuf)
r.buf = newBuf
r.size = newSize
r.rp = 0
r.wp = oldLen
r.isFull = false
r.availableCap = newSize - oldLen
}
// Read reads up to len(p) bytes into p from the underlying data buffer.
func (r *Ring) Read(p []byte) (int, error) {
if r.isEmpty() {
return 0, nil
}
var n int
if r.wp > r.rp {
// Non-wrapped data: directly copy from rp to wp.
n = copy(p, r.buf[r.rp:r.wp])
} else {
// Wrapped data: copy in two steps.
// First, from rp to the end of the buffer.
n = copy(p, r.buf[r.rp:])
// If there's more space in p and data wrapped, copy the remaining data from the beginning.
if n < len(p) {
n += copy(p[n:], r.buf[:r.wp])
}
}
// Update the read pointer and re-calculate the available capacity.
r.rp = (r.rp + n) % r.size
r.isFull = false // The buffer cannot be full after a read operation.
r.availableCap = r.size - r.Len()
return n, nil
}
func (r *Ring) read(p []byte) int {
n := copy(p, r.buf[r.rp:])
if r.wp < r.rp {
n += copy(p[n:], r.buf[:r.wp])
}
return n
}
// Bytes returns a copy of the buffer's data.
func (r *Ring) Bytes() []byte {
if r.isEmpty() {
return nil
}
return r.buf[r.rp:r.wp]
}
// isEmpty returns true if the buffer is empty.
func (r *Ring) isEmpty() bool { return !r.isFull && r.rp == r.wp }
// Len return the length of available read bytes.
func (r *Ring) Len() int {
if r.isFull {
return r.size
}
if r.wp >= r.rp {
return r.wp - r.rp
}
return r.size - r.rp + r.wp
}
// Cap returns the size of the underlying buffer.
func (r *Ring) Cap() int {
return r.size
}
// Reset resets the buffer.
func (r *Ring) Reset() {
r.rp = 0
r.wp = 0
r.isFull = false
r.availableCap = r.size
}