1// Package ringbuffer is a fixed-capacity FIFO that overwrites its oldest entry2// when full, as a pure, reusable package.3//4// This is the bounded cousin of an unbounded queue, and the bound is the point:5// on chain an unbounded queue is an unbounded storage bill, whereas a ring6// buffer's cost is decided once, at construction. It is the right shape for7// "last N events", "recent messages", or any rolling window.8//9// Backed by a flat slice with head/length indices — no per-element allocation,10// no shifting on Pop.11//12// A live demo of this package is at13// [r/moul/x/daily/ringbufferdemo](/r/moul/x/daily/ringbufferdemo/v0).14package ringbuffer1516// MaxCap bounds a buffer so allocation stays predictable.17const MaxCap = 40961819// RingBuffer is a fixed-capacity FIFO of strings.20type RingBuffer struct {21 buf []string22 head int // index of the oldest element23 n int // number of live elements24}2526// New returns an empty buffer of the given capacity, clamped to [0, MaxCap].27func New(capacity int) *RingBuffer {28 if capacity < 0 {29 capacity = 030 }31 if capacity > MaxCap {32 capacity = MaxCap33 }34 return &RingBuffer{buf: make([]string, capacity)}35}3637// Cap returns the capacity.38func (r *RingBuffer) Cap() int { return len(r.buf) }3940// Len returns how many elements are live.41func (r *RingBuffer) Len() int { return r.n }4243// Full reports whether the next Push will overwrite.44func (r *RingBuffer) Full() bool { return len(r.buf) > 0 && r.n == len(r.buf) }4546// Empty reports whether there is nothing to read.47func (r *RingBuffer) Empty() bool { return r.n == 0 }4849// Push appends v. When the buffer is full the OLDEST element is dropped to make50// room, and that element is returned with dropped=true — losing data silently51// is the one thing a rolling window must not do.52//53// A zero-capacity buffer accepts nothing and reports the value straight back.54func (r *RingBuffer) Push(v string) (evicted string, dropped bool) {55 if len(r.buf) == 0 {56 return v, true57 }58 if r.n == len(r.buf) {59 evicted = r.buf[r.head]60 r.buf[r.head] = v61 r.head = (r.head + 1) % len(r.buf)62 return evicted, true63 }64 r.buf[(r.head+r.n)%len(r.buf)] = v65 r.n++66 return "", false67}6869// Pop removes and returns the oldest element; ok is false when empty.70func (r *RingBuffer) Pop() (v string, ok bool) {71 if r.n == 0 {72 return "", false73 }74 v = r.buf[r.head]75 r.buf[r.head] = "" // release the reference; don't pin a string we no longer own76 r.head = (r.head + 1) % len(r.buf)77 r.n--78 return v, true79}8081// Peek returns the oldest element without removing it.82func (r *RingBuffer) Peek() (v string, ok bool) {83 if r.n == 0 {84 return "", false85 }86 return r.buf[r.head], true87}8889// At returns the i-th element counting from the oldest (0 = oldest).90func (r *RingBuffer) At(i int) (v string, ok bool) {91 if i < 0 || i >= r.n {92 return "", false93 }94 return r.buf[(r.head+i)%len(r.buf)], true95}9697// Slice returns the live elements oldest-first, as an independent copy.98func (r *RingBuffer) Slice() []string {99 out := make([]string, 0, r.n)100 for i := 0; i < r.n; i++ {101 out = append(out, r.buf[(r.head+i)%len(r.buf)])102 }103 return out104}105106// Reset empties the buffer, releasing every stored reference.107func (r *RingBuffer) Reset() {108 for i := range r.buf {109 r.buf[i] = ""110 }111 r.head, r.n = 0, 0112}113Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.