Skip to content

Commit 69bc812

Browse files
committed
Add generic buffer.TypedRingGrowing and shrinkable buffer.Ring
1 parent 1f6e0b7 commit 69bc812

File tree

2 files changed

+244
-41
lines changed

2 files changed

+244
-41
lines changed

buffer/ring_growing.go

Lines changed: 99 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,31 +16,54 @@ limitations under the License.
1616

1717
package buffer
1818

19+
// RingGrowingOptions sets parameters for [RingGrowing] and
20+
// [TypedRingGrowing].
21+
type RingGrowingOptions struct {
22+
// InitialSize is the number of pre-allocated elements in the
23+
// initial underlying storage buffer.
24+
InitialSize int
25+
}
26+
1927
// RingGrowing is a growing ring buffer.
2028
// Not thread safe.
21-
type RingGrowing struct {
22-
data []interface{}
29+
//
30+
// Deprecated: Use TypedRingGrowing[any] instead.
31+
type RingGrowing = TypedRingGrowing[any]
32+
33+
// NewRingGrowing constructs a new RingGrowing instance with provided parameters.
34+
//
35+
// Deprecated: Use NewTypedRingGrowing[any] instead.
36+
func NewRingGrowing(initialSize int) *RingGrowing {
37+
return NewTypedRingGrowing[any](RingGrowingOptions{InitialSize: initialSize})
38+
}
39+
40+
// TypedRingGrowing is a growing ring buffer.
41+
// The zero value has an initial size of 0 and is ready to use.
42+
// Not thread safe.
43+
type TypedRingGrowing[T any] struct {
44+
data []T
2345
n int // Size of Data
2446
beg int // First available element
2547
readable int // Number of data items available
2648
}
2749

28-
// NewRingGrowing constructs a new RingGrowing instance with provided parameters.
29-
func NewRingGrowing(initialSize int) *RingGrowing {
30-
return &RingGrowing{
31-
data: make([]interface{}, initialSize),
32-
n: initialSize,
50+
// NewTypedRingGrowing constructs a new TypedRingGrowing instance with provided parameters.
51+
func NewTypedRingGrowing[T any](opts RingGrowingOptions) *TypedRingGrowing[T] {
52+
return &TypedRingGrowing[T]{
53+
data: make([]T, opts.InitialSize),
54+
n: opts.InitialSize,
3355
}
3456
}
3557

3658
// ReadOne reads (consumes) first item from the buffer if it is available, otherwise returns false.
37-
func (r *RingGrowing) ReadOne() (data interface{}, ok bool) {
59+
func (r *TypedRingGrowing[T]) ReadOne() (data T, ok bool) {
3860
if r.readable == 0 {
39-
return nil, false
61+
return
4062
}
4163
r.readable--
4264
element := r.data[r.beg]
43-
r.data[r.beg] = nil // Remove reference to the object to help GC
65+
var zero T
66+
r.data[r.beg] = zero // Remove reference to the object to help GC
4467
if r.beg == r.n-1 {
4568
// Was the last element
4669
r.beg = 0
@@ -51,11 +74,14 @@ func (r *RingGrowing) ReadOne() (data interface{}, ok bool) {
5174
}
5275

5376
// WriteOne adds an item to the end of the buffer, growing it if it is full.
54-
func (r *RingGrowing) WriteOne(data interface{}) {
77+
func (r *TypedRingGrowing[T]) WriteOne(data T) {
5578
if r.readable == r.n {
5679
// Time to grow
5780
newN := r.n * 2
58-
newData := make([]interface{}, newN)
81+
if newN == 0 {
82+
newN = 1
83+
}
84+
newData := make([]T, newN)
5985
to := r.beg + r.readable
6086
if to <= r.n {
6187
copy(newData, r.data[r.beg:to])
@@ -72,11 +98,70 @@ func (r *RingGrowing) WriteOne(data interface{}) {
7298
}
7399

74100
// Len returns the number of items in the buffer.
75-
func (r *RingGrowing) Len() int {
101+
func (r *TypedRingGrowing[T]) Len() int {
76102
return r.readable
77103
}
78104

79105
// Cap returns the capacity of the buffer.
80-
func (r *RingGrowing) Cap() int {
106+
func (r *TypedRingGrowing[T]) Cap() int {
81107
return r.n
82108
}
109+
110+
// RingGrowingOptions sets parameters for [Ring].
111+
type RingOptions struct {
112+
// InitialSize is the number of pre-allocated elements in the
113+
// initial underlying storage buffer.
114+
InitialSize int
115+
// NormalSize is the number of elements to allocate for new storage
116+
// buffers once the Ring is consumed and
117+
// can shrink again.
118+
NormalSize int
119+
}
120+
121+
// Ring is a dynamically-sized ring buffer which can grow and shrink as-needed.
122+
// The zero value has an initial size and normal size of 0 and is ready to use.
123+
// Not thread safe.
124+
type Ring[T any] struct {
125+
growing TypedRingGrowing[T]
126+
normalSize int // Limits the size of the buffer that is kept for reuse. Read-only.
127+
}
128+
129+
// NewRing constructs a new Ring instance with provided parameters.
130+
func NewRing[T any](opts RingOptions) *Ring[T] {
131+
return &Ring[T]{
132+
growing: *NewTypedRingGrowing[T](RingGrowingOptions{InitialSize: opts.InitialSize}),
133+
normalSize: opts.NormalSize,
134+
}
135+
}
136+
137+
// ReadOne reads (consumes) first item from the buffer if it is available,
138+
// otherwise returns false. When the buffer has been totally consumed and has
139+
// grown in size beyond its normal size, it shrinks down to its normal size again.
140+
func (r *Ring[T]) ReadOne() (data T, ok bool) {
141+
element, ok := r.growing.ReadOne()
142+
143+
if r.growing.readable == 0 && r.growing.n > r.normalSize {
144+
// The buffer is empty. Reallocate a new buffer so the old one can be
145+
// garbage collected.
146+
r.growing.data = make([]T, r.normalSize)
147+
r.growing.n = r.normalSize
148+
r.growing.beg = 0
149+
}
150+
151+
return element, ok
152+
}
153+
154+
// WriteOne adds an item to the end of the buffer, growing it if it is full.
155+
func (r *Ring[T]) WriteOne(data T) {
156+
r.growing.WriteOne(data)
157+
}
158+
159+
// Len returns the number of items in the buffer.
160+
func (r *Ring[T]) Len() int {
161+
return r.growing.Len()
162+
}
163+
164+
// Cap returns the capacity of the buffer.
165+
func (r *Ring[T]) Cap() int {
166+
return r.growing.Cap()
167+
}

buffer/ring_growing_test.go

Lines changed: 145 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -17,45 +17,163 @@ limitations under the License.
1717
package buffer
1818

1919
import (
20-
"reflect"
2120
"testing"
2221
)
2322

24-
func TestGrowth(t *testing.T) {
23+
func TestGrowthGrowing(t *testing.T) {
2524
t.Parallel()
26-
x := 10
27-
g := NewRingGrowing(1)
28-
for i := 0; i < x; i++ {
29-
if e, a := i, g.readable; !reflect.DeepEqual(e, a) {
30-
t.Fatalf("expected equal, got %#v, %#v", e, a)
31-
}
32-
g.WriteOne(i)
33-
}
34-
read := 0
35-
for g.readable > 0 {
36-
v, ok := g.ReadOne()
37-
if !ok {
38-
t.Fatal("expected true")
39-
}
40-
if read != v {
41-
t.Fatalf("expected %#v==%#v", read, v)
42-
}
43-
read++
25+
tests := map[string]struct {
26+
ring *TypedRingGrowing[int]
27+
initialSize int
28+
}{
29+
"implicit-zero": {
30+
ring: new(TypedRingGrowing[int]),
31+
},
32+
"explicit-zero": {
33+
ring: NewTypedRingGrowing[int](RingGrowingOptions{InitialSize: 0}),
34+
initialSize: 0,
35+
},
36+
"nonzero": {
37+
ring: NewTypedRingGrowing[int](RingGrowingOptions{InitialSize: 1}),
38+
initialSize: 1,
39+
},
4440
}
45-
if x != read {
46-
t.Fatalf("expected to have read %d items: %d", x, read)
41+
42+
for name, test := range tests {
43+
t.Run(name, func(t *testing.T) {
44+
initialSize := test.initialSize
45+
g := test.ring
46+
47+
if expected, actual := 0, g.Len(); expected != actual {
48+
t.Fatalf("expected Len to be %d, got %d", expected, actual)
49+
}
50+
if expected, actual := initialSize, g.Cap(); expected != actual {
51+
t.Fatalf("expected Cap to be %d, got %d", expected, actual)
52+
}
53+
54+
x := 10
55+
for i := 0; i < x; i++ {
56+
if e, a := i, g.readable; e != a {
57+
t.Fatalf("expected equal, got %#v, %#v", e, a)
58+
}
59+
g.WriteOne(i)
60+
}
61+
62+
if expected, actual := x, g.Len(); expected != actual {
63+
t.Fatalf("expected Len to be %d, got %d", expected, actual)
64+
}
65+
if expected, actual := 16, g.Cap(); expected != actual {
66+
t.Fatalf("expected Cap to be %d, got %d", expected, actual)
67+
}
68+
69+
read := 0
70+
for g.readable > 0 {
71+
v, ok := g.ReadOne()
72+
if !ok {
73+
t.Fatal("expected true")
74+
}
75+
if read != v {
76+
t.Fatalf("expected %#v==%#v", read, v)
77+
}
78+
read++
79+
}
80+
if x != read {
81+
t.Fatalf("expected to have read %d items: %d", x, read)
82+
}
83+
if expected, actual := 0, g.Len(); expected != actual {
84+
t.Fatalf("expected Len to be %d, got %d", expected, actual)
85+
}
86+
if expected, actual := 16, g.Cap(); expected != actual {
87+
t.Fatalf("expected Cap to be %d, got %d", expected, actual)
88+
}
89+
})
4790
}
48-
if g.readable != 0 {
49-
t.Fatalf("expected readable to be zero: %d", g.readable)
91+
92+
}
93+
94+
func TestGrowth(t *testing.T) {
95+
t.Parallel()
96+
97+
tests := map[string]struct {
98+
ring *Ring[int]
99+
initialSize int
100+
normalSize int
101+
}{
102+
"implicit-zero": {
103+
ring: new(Ring[int]),
104+
},
105+
"explicit-zero": {
106+
ring: NewRing[int](RingOptions{InitialSize: 0, NormalSize: 0}),
107+
initialSize: 0,
108+
normalSize: 0,
109+
},
110+
"smaller-initial-size": {
111+
ring: NewRing[int](RingOptions{InitialSize: 1, NormalSize: 2}),
112+
initialSize: 1,
113+
normalSize: 2,
114+
},
115+
"smaller-normal-size": {
116+
ring: NewRing[int](RingOptions{InitialSize: 2, NormalSize: 1}),
117+
initialSize: 2,
118+
normalSize: 1,
119+
},
50120
}
51-
if 16 != g.n {
52-
t.Fatalf("expected N to be 16: %d", g.n)
121+
122+
for name, test := range tests {
123+
t.Run(name, func(t *testing.T) {
124+
initialSize := test.initialSize
125+
normalSize := test.normalSize
126+
g := test.ring
127+
128+
if expected, actual := 0, g.Len(); expected != actual {
129+
t.Fatalf("expected Len to be %d, got %d", expected, actual)
130+
}
131+
if expected, actual := initialSize, g.Cap(); expected != actual {
132+
t.Fatalf("expected Cap to be %d, got %d", expected, actual)
133+
}
134+
135+
x := 10
136+
for i := 0; i < x; i++ {
137+
if e, a := i, g.growing.readable; e != a {
138+
t.Fatalf("expected equal, got %#v, %#v", e, a)
139+
}
140+
g.WriteOne(i)
141+
}
142+
143+
if expected, actual := x, g.Len(); expected != actual {
144+
t.Fatalf("expected Len to be %d, got %d", expected, actual)
145+
}
146+
if expected, actual := 16, g.Cap(); expected != actual {
147+
t.Fatalf("expected Cap to be %d, got %d", expected, actual)
148+
}
149+
150+
read := 0
151+
for g.growing.readable > 0 {
152+
v, ok := g.ReadOne()
153+
if !ok {
154+
t.Fatal("expected true")
155+
}
156+
if read != v {
157+
t.Fatalf("expected %#v==%#v", read, v)
158+
}
159+
read++
160+
}
161+
if x != read {
162+
t.Fatalf("expected to have read %d items: %d", x, read)
163+
}
164+
if expected, actual := 0, g.Len(); expected != actual {
165+
t.Fatalf("expected Len to be %d, got %d", expected, actual)
166+
}
167+
if expected, actual := normalSize, g.Cap(); expected != actual {
168+
t.Fatalf("expected Cap to be %d, got %d", expected, actual)
169+
}
170+
})
53171
}
54172
}
55173

56174
func TestEmpty(t *testing.T) {
57175
t.Parallel()
58-
g := NewRingGrowing(1)
176+
g := NewTypedRingGrowing[struct{}](RingGrowingOptions{InitialSize: 1})
59177
_, ok := g.ReadOne()
60178
if ok != false {
61179
t.Fatal("expected false")

0 commit comments

Comments
 (0)