-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathringbuffer.go
More file actions
157 lines (144 loc) · 4.36 KB
/
Copy pathringbuffer.go
File metadata and controls
157 lines (144 loc) · 4.36 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
package carousel
import "fmt"
// RingBuffer is a fixed-capacity FIFO circular buffer.
//
// The zero value is not usable; create instances with [NewRingBuffer].
// Not safe for concurrent use — callers are responsible for synchronization
// when shared across goroutines.
//
// Internal layout: head is the index of the oldest element; the write
// position (tail) is derived as (head+size)%len(data) and is not stored.
type RingBuffer[T any] struct {
data []T
head int // index of the oldest element
size int // number of elements currently stored
}
// NewRingBuffer creates a RingBuffer with the given capacity.
//
// Panics if capacity < 1.
func NewRingBuffer[T any](capacity int) *RingBuffer[T] {
if capacity < 1 {
panic(fmt.Sprintf("carousel: ring buffer: capacity must be at least 1, got %d", capacity))
}
return &RingBuffer[T]{
data: make([]T, capacity),
}
}
// Push appends item to the back of the buffer.
// Returns false if the buffer is full; the item is not added.
func (rb *RingBuffer[T]) Push(item T) bool {
if rb.size == len(rb.data) {
return false
}
rb.data[(rb.head+rb.size)%len(rb.data)] = item
rb.size++
return true
}
// ForcePush appends item to the back of the buffer. If the buffer is full,
// the oldest item is evicted to make room.
// Returns true if an item was evicted.
func (rb *RingBuffer[T]) ForcePush(item T) (evicted bool) {
if rb.size == len(rb.data) {
// Overwrite the oldest slot and advance head.
rb.data[rb.head] = item
rb.head = (rb.head + 1) % len(rb.data)
return true
}
rb.data[(rb.head+rb.size)%len(rb.data)] = item
rb.size++
return false
}
// Pop removes and returns the oldest item.
// Returns the zero value of T and false if the buffer is empty.
func (rb *RingBuffer[T]) Pop() (T, bool) {
if rb.size == 0 {
var zero T
return zero, false
}
item := rb.data[rb.head]
var zero T
rb.data[rb.head] = zero // release reference for GC
rb.head = (rb.head + 1) % len(rb.data)
rb.size--
return item, true
}
// Peek returns the oldest item without removing it.
// Returns the zero value of T and false if the buffer is empty.
func (rb *RingBuffer[T]) Peek() (T, bool) {
if rb.size == 0 {
var zero T
return zero, false
}
return rb.data[rb.head], true
}
// Drain removes and returns all items in FIFO order.
// Returns nil if the buffer is empty.
// After Drain, the buffer is empty and all internal slots are zeroed.
func (rb *RingBuffer[T]) Drain() []T {
if rb.size == 0 {
return nil
}
h, t := rb.segments()
out := make([]T, len(h)+len(t))
// Process each segment fully (copy then clear) before moving to the next
// so the cachelines touched by copy stay hot for the immediate clear.
n := copy(out, h)
clear(h) // release references for GC
copy(out[n:], t)
clear(t) // release references for GC; no-op when t is nil
rb.head = 0
rb.size = 0
return out
}
// Snapshot returns a copy of all items in FIFO order (oldest first).
// Returns nil if the buffer is empty.
//
// The returned slice is independent of the buffer; mutations to either do
// not affect the other. Non-destructive: buffer state is unchanged.
//
// Independence is shallow: if T is a pointer type or contains pointers,
// the pointed-to values are shared between the snapshot and the buffer.
func (rb *RingBuffer[T]) Snapshot() []T {
if rb.size == 0 {
return nil
}
h, t := rb.segments()
out := make([]T, len(h)+len(t))
n := copy(out, h)
copy(out[n:], t)
return out
}
// Len returns the number of items currently in the buffer.
func (rb *RingBuffer[T]) Len() int {
return rb.size
}
// Cap returns the maximum number of items the buffer can hold.
func (rb *RingBuffer[T]) Cap() int {
return len(rb.data)
}
// Clear removes all items and releases slot references for GC.
// No-op when the buffer is already empty.
func (rb *RingBuffer[T]) Clear() {
if rb.size == 0 {
return
}
h, t := rb.segments()
clear(h)
clear(t) // no-op when t is nil
rb.head = 0
rb.size = 0
}
// segments returns the live region as one or two contiguous slice views over
// rb.data. tail is nil when the region does not wrap past the physical array
// end. Both slices alias rb.data; callers must not retain them past the next
// mutation of the buffer.
func (rb *RingBuffer[T]) segments() (head, tail []T) {
if rb.size == 0 {
return nil, nil
}
end := rb.head + rb.size
if end <= len(rb.data) {
return rb.data[rb.head:end], nil
}
return rb.data[rb.head:], rb.data[:end-len(rb.data)]
}