Add a generic ring buffer
Go's stdlib is very anemic
This commit is contained in:
parent
7264bea8c9
commit
c0f17c279e
117
tools/utils/ring.go
Normal file
117
tools/utils/ring.go
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
// License: GPLv3 Copyright: 2022, Kovid Goyal, <kovid at kovidgoyal.net>
|
||||||
|
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = fmt.Print
|
||||||
|
|
||||||
|
func rb_min(a, b uint64) uint64 {
|
||||||
|
if a < b {
|
||||||
|
return a
|
||||||
|
} else {
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type RingBuffer[T any] struct {
|
||||||
|
buffer []T
|
||||||
|
read_pos uint64
|
||||||
|
use_count uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRingBuffer[T any](size uint64) *RingBuffer[T] {
|
||||||
|
return &RingBuffer[T]{
|
||||||
|
buffer: make([]T, size),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *RingBuffer[T]) Len() uint64 {
|
||||||
|
return self.use_count
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *RingBuffer[T]) Capacity() uint64 {
|
||||||
|
return uint64(len(self.buffer))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *RingBuffer[T]) Clear() {
|
||||||
|
self.read_pos = 0
|
||||||
|
self.use_count = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *RingBuffer[T]) Grow(new_size uint64) {
|
||||||
|
if new_size <= self.Capacity() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
buf := make([]T, new_size)
|
||||||
|
if self.use_count > 0 {
|
||||||
|
self.ReadTillEmpty(buf)
|
||||||
|
}
|
||||||
|
self.buffer = buf
|
||||||
|
self.read_pos = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *RingBuffer[T]) WriteTillFull(p []T) uint64 {
|
||||||
|
ssz := self.Capacity()
|
||||||
|
available := ssz - self.use_count
|
||||||
|
sz := rb_min(uint64(len(p)), available)
|
||||||
|
if sz == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
tail := (self.read_pos + self.use_count) % ssz
|
||||||
|
write_end := (self.read_pos + self.use_count + sz) % ssz
|
||||||
|
self.use_count += sz
|
||||||
|
p = p[:sz]
|
||||||
|
if tail <= write_end {
|
||||||
|
copy(self.buffer[tail:], p)
|
||||||
|
} else {
|
||||||
|
first_write := ssz - tail
|
||||||
|
copy(self.buffer[tail:], p[:first_write])
|
||||||
|
copy(self.buffer, p[first_write:])
|
||||||
|
}
|
||||||
|
return sz
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *RingBuffer[T]) WriteAllAndDiscardOld(p []T) {
|
||||||
|
ssz := self.Capacity()
|
||||||
|
left := uint64(len(p))
|
||||||
|
if left >= ssz { // Fast path
|
||||||
|
extra := left - ssz
|
||||||
|
copy(self.buffer, p[extra:])
|
||||||
|
self.read_pos = 0
|
||||||
|
self.use_count = ssz
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
written := self.WriteTillFull(p)
|
||||||
|
p = p[written:]
|
||||||
|
left = uint64(len(p))
|
||||||
|
if left == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
self.slices_to_read(left)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *RingBuffer[T]) ReadTillEmpty(p []T) uint64 {
|
||||||
|
a, b := self.slices_to_read(uint64(len(p)))
|
||||||
|
copy(p, a)
|
||||||
|
copy(p[len(a):], b)
|
||||||
|
return uint64(len(a)) + uint64(len(b))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *RingBuffer[T]) slices_to_read(sz uint64) ([]T, []T) {
|
||||||
|
ssz := self.Capacity()
|
||||||
|
sz = rb_min(sz, self.use_count)
|
||||||
|
head := self.read_pos
|
||||||
|
end_read := (head + sz) % ssz
|
||||||
|
self.use_count -= sz
|
||||||
|
self.read_pos = end_read
|
||||||
|
if end_read > head || sz == 0 {
|
||||||
|
return self.buffer[head:end_read], self.buffer[0:0]
|
||||||
|
}
|
||||||
|
first_read := ssz - head
|
||||||
|
return self.buffer[head : head+first_read], self.buffer[0 : sz-first_read]
|
||||||
|
}
|
||||||
46
tools/utils/ring_test.go
Normal file
46
tools/utils/ring_test.go
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
// License: GPLv3 Copyright: 2022, Kovid Goyal, <kovid at kovidgoyal.net>
|
||||||
|
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = fmt.Print
|
||||||
|
|
||||||
|
func TestRingBuffer(t *testing.T) {
|
||||||
|
r := NewRingBuffer[int](8)
|
||||||
|
|
||||||
|
test_contents := func(expected ...int) {
|
||||||
|
actual := make([]int, len(expected))
|
||||||
|
num_read := r.ReadTillEmpty(actual)
|
||||||
|
if num_read != uint64(len(actual)) {
|
||||||
|
t.Fatalf("Did not read expected num of items: %d != %d", num_read, len(expected))
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(expected, actual) {
|
||||||
|
t.Fatalf("Did not read expected items:\n%#v != %#v", actual, expected)
|
||||||
|
}
|
||||||
|
if r.Len() != 0 {
|
||||||
|
t.Fatalf("Reading contents did not empty the buffer")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
r.WriteTillFull([]int{1, 2, 3, 4})
|
||||||
|
test_contents(1, 2, 3, 4)
|
||||||
|
r.WriteTillFull([]int{1, 2, 3, 4})
|
||||||
|
test_contents(1, 2, 3, 4)
|
||||||
|
|
||||||
|
r.Clear()
|
||||||
|
r.WriteTillFull([]int{1, 2, 3, 4})
|
||||||
|
r.ReadTillEmpty([]int{0, 1})
|
||||||
|
test_contents(3, 4)
|
||||||
|
r.WriteTillFull([]int{1, 2, 3, 4, 5})
|
||||||
|
test_contents(1, 2, 3, 4, 5)
|
||||||
|
|
||||||
|
r.Clear()
|
||||||
|
r.WriteTillFull([]int{1, 2, 3, 4})
|
||||||
|
r.WriteAllAndDiscardOld([]int{5, 6, 7, 8, 9})
|
||||||
|
test_contents(2, 3, 4, 5, 6, 7, 8, 9)
|
||||||
|
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user