-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathringbuffer.go
More file actions
211 lines (182 loc) · 6.63 KB
/
ringbuffer.go
File metadata and controls
211 lines (182 loc) · 6.63 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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
package ringbuffer
import (
"errors"
"fmt"
"strconv"
"sync"
)
// BufferType provides constraints on the types that may be used for a New RingBuffer
type BufferType interface {
int | int16 | int32 | int64 |
byte | uint | uint16 | uint32 | uint64 |
float32 | float64 | bool | string
}
// RingBuffer is effectively a fixed-size container as a data structure. Fields defined
// in this struct are named in the context as a fixed-size container.
type RingBuffer[T BufferType] struct {
// buffer contains all data, including undefined elements, of which take a default
// value to their respective type. Bool defaults to False, int defaults to 0, string
// defaults to "", ... etc
buffer []T
mut sync.Mutex // Handles thread safety and concurrency
capacity int // Total size of the buffer
elementCount int // Number of values stored within the buffer
writeIndex int // The next index to write into the buffer when Write() is called
}
// Error handling statements
var (
errCapacityNegativeOrZero = errors.New("failed to create a new ring buffer! " +
"Buffer capacity must be greater than zero")
errCapacityTooSmall = errors.New("failed to write to buffer! Ring buffer total " +
"capacity is too small for all values to be written")
errCapacityResizeTooSmall = errors.New("failed to resize buffer! The number of " +
"elements/values in the existing buffer exceeds the capacity for the new buffer")
errDataLengthIsZero = errors.New("failed to write to buffer! The amount of " +
"data to write is zero")
)
// New is effectively a constructor that creates a new ring buffer with a fixed,
// zero-indexed capacity and specified type constrained by the BufferType interface
func New[T BufferType](capacity int) (*RingBuffer[T], error) {
if capacity <= 0 {
return nil, errCapacityNegativeOrZero
}
return &RingBuffer[T]{
buffer: make([]T, capacity),
capacity: capacity,
}, nil
}
// NewSize recreates a new ring buffer with a different capacity or size, but with the same
// data as the old ring buffer. The NEW capacity cannot be smaller than the number of
// values or elements contained in the OLD buffer.
func (rb *RingBuffer[T]) NewSize(capacity int) (*RingBuffer[T], error) {
rb.mut.Lock()
defer rb.mut.Unlock()
if capacity <= 0 {
return nil, errCapacityNegativeOrZero
}
if rb.elementCount > capacity {
return nil, errCapacityResizeTooSmall
}
newBuffer := make([]T, capacity)
newBuffer = rb.buffer
return &RingBuffer[T]{
buffer: newBuffer,
capacity: capacity,
elementCount: rb.elementCount,
writeIndex: rb.writeIndex,
}, nil
}
// String converts the capacity, writeIndex pointer, count of elements, and contents of
// the ring buffer into a string, then returns that string
func (rb *RingBuffer[T]) String() string {
rb.mut.Lock()
defer rb.mut.Unlock()
bufferStr := "capacity=" + strconv.Itoa(rb.capacity) +
", writeIndex=" + strconv.Itoa(rb.writeIndex) +
", elementCount=" + strconv.Itoa(rb.elementCount) +
", buffer=["
lastElement := len(rb.buffer) - 1
for i := range rb.buffer {
if i != lastElement {
bufferStr += fmt.Sprintf("%v", rb.buffer[i]) + ","
} else {
bufferStr += fmt.Sprintf("%v", rb.buffer[i]) + "]"
}
}
return bufferStr
}
// Read returns the contents of the buffer in "First-In First-Out" (FIFO) order
func (rb *RingBuffer[T]) Read() (result []T) {
rb.mut.Lock()
defer rb.mut.Unlock()
result = make([]T, 0, rb.elementCount)
for i := 0; i < rb.elementCount; i++ {
index := (rb.writeIndex + rb.capacity - rb.elementCount + i) % rb.capacity
result = append(result, rb.buffer[index])
}
return result
}
// Write inserts one element into the thread-safe buffer, overwriting the oldest element
// (without error) if the buffer is full
func (rb *RingBuffer[T]) Write(value T) {
rb.mut.Lock()
defer rb.mut.Unlock()
rb.buffer[rb.writeIndex] = value
// rb.writeIndex acts as a logical pointer that moves forward each time Write(...)
// is called.
// When writeIndex reaches the buffer capacity, it wraps around to the beginning of
// the buffer by using the modulo operator
rb.writeIndex = (rb.writeIndex + 1) % rb.capacity
// Only increment the elementCount of elements in the buffer when the buffer
// isn't full
if rb.elementCount < rb.capacity {
rb.elementCount++
}
}
// WriteMany first checks if the number of values is greater than the buffer or if the
// length of values is zero. If either of those conditions are true, then their respective
// error is returned.
//
// Otherwise, WriteMany iterates over each slice of elements or values passed into it and
// calls Write for each element / value.
func (rb *RingBuffer[T]) WriteMany(values []T) error {
if len(values) > rb.capacity {
return errCapacityTooSmall
} else if len(values) == 0 {
return errDataLengthIsZero
}
for _, val := range values {
rb.Write(val)
}
return nil
}
// Reset deletes all data within the buffer by re-allocation but retains the same exact
// capacity
func (rb *RingBuffer[T]) Reset() {
rb.mut.Lock()
defer rb.mut.Unlock()
rb.buffer = make([]T, rb.capacity)
rb.elementCount = 0 // there's nothing (no elements/values) in the buffer, of course
rb.writeIndex = 0 // reset the logical pointer to the beginning of the buffer
}
// Length returns the number of elements or values within the buffer.
//
// For getting the total capacity of the buffer, use Capacity() or Size()
func (rb *RingBuffer[T]) Length() int {
rb.mut.Lock()
defer rb.mut.Unlock()
return rb.elementCount
}
// Capacity returns the zero-indexed capacity of the ring buffer itself, as opposed to the
// number of elements within the buffer.
//
// For getting the number of elements in a buffer, use Length()
func (rb *RingBuffer[T]) Capacity() int {
rb.mut.Lock()
defer rb.mut.Unlock()
return rb.capacity
}
// Size returns the zero-indexed capacity of the ring buffer itself, as opposed to the
// number of elements within the buffer.
//
// Size works exactly the same as Capacity() because it calls Capacity() directly in order to
// retain backwards compatability.
//
// For getting the number of elements in a buffer, use Length()
func (rb *RingBuffer[T]) Size() int {
return rb.Capacity()
}
// IsFull returns a boolean indicating if the number of elements or values of the buffer
// equals the buffer capacity or size.
func (rb *RingBuffer[T]) IsFull() bool {
rb.mut.Lock()
defer rb.mut.Unlock()
return rb.elementCount == rb.capacity
}
// IsEmpty returns a boolean indicating if the number of elements or values within the
// buffer is zero. Additionally, the write index must be zero.
func (rb *RingBuffer[T]) IsEmpty() bool {
rb.mut.Lock()
defer rb.mut.Unlock()
return rb.elementCount == 0 && rb.writeIndex == 0
}