Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

ringbuffer.gno

3.21 Kb · 112 lines
  1// Package ringbuffer is a fixed-capacity FIFO that overwrites its oldest entry
  2// 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 ring
  6// buffer's cost is decided once, at construction. It is the right shape for
  7// "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 at
 13// [r/moul/x/daily/ringbufferdemo](/r/moul/x/daily/ringbufferdemo/v0).
 14package ringbuffer
 15
 16// MaxCap bounds a buffer so allocation stays predictable.
 17const MaxCap = 4096
 18
 19// RingBuffer is a fixed-capacity FIFO of strings.
 20type RingBuffer struct {
 21	buf  []string
 22	head int // index of the oldest element
 23	n    int // number of live elements
 24}
 25
 26// New returns an empty buffer of the given capacity, clamped to [0, MaxCap].
 27func New(capacity int) *RingBuffer {
 28	if capacity < 0 {
 29		capacity = 0
 30	}
 31	if capacity > MaxCap {
 32		capacity = MaxCap
 33	}
 34	return &RingBuffer{buf: make([]string, capacity)}
 35}
 36
 37// Cap returns the capacity.
 38func (r *RingBuffer) Cap() int { return len(r.buf) }
 39
 40// Len returns how many elements are live.
 41func (r *RingBuffer) Len() int { return r.n }
 42
 43// Full reports whether the next Push will overwrite.
 44func (r *RingBuffer) Full() bool { return len(r.buf) > 0 && r.n == len(r.buf) }
 45
 46// Empty reports whether there is nothing to read.
 47func (r *RingBuffer) Empty() bool { return r.n == 0 }
 48
 49// Push appends v. When the buffer is full the OLDEST element is dropped to make
 50// room, and that element is returned with dropped=true — losing data silently
 51// 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, true
 57	}
 58	if r.n == len(r.buf) {
 59		evicted = r.buf[r.head]
 60		r.buf[r.head] = v
 61		r.head = (r.head + 1) % len(r.buf)
 62		return evicted, true
 63	}
 64	r.buf[(r.head+r.n)%len(r.buf)] = v
 65	r.n++
 66	return "", false
 67}
 68
 69// 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 "", false
 73	}
 74	v = r.buf[r.head]
 75	r.buf[r.head] = "" // release the reference; don't pin a string we no longer own
 76	r.head = (r.head + 1) % len(r.buf)
 77	r.n--
 78	return v, true
 79}
 80
 81// Peek returns the oldest element without removing it.
 82func (r *RingBuffer) Peek() (v string, ok bool) {
 83	if r.n == 0 {
 84		return "", false
 85	}
 86	return r.buf[r.head], true
 87}
 88
 89// 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 "", false
 93	}
 94	return r.buf[(r.head+i)%len(r.buf)], true
 95}
 96
 97// 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 out
104}
105
106// 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, 0
112}