diff --git a/tools/utils/ring.go b/tools/utils/ring.go new file mode 100644 index 000000000..b33f3b4c8 --- /dev/null +++ b/tools/utils/ring.go @@ -0,0 +1,117 @@ +// License: GPLv3 Copyright: 2022, Kovid Goyal, + +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] +} diff --git a/tools/utils/ring_test.go b/tools/utils/ring_test.go new file mode 100644 index 000000000..c25b1e8fb --- /dev/null +++ b/tools/utils/ring_test.go @@ -0,0 +1,46 @@ +// License: GPLv3 Copyright: 2022, Kovid Goyal, + +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) + +}