mirror of
https://github.com/bloeys/nterm.git
synced 2025-12-29 06:28:20 +00:00
422 lines
10 KiB
Go
Executable File
422 lines
10 KiB
Go
Executable File
package ring
|
|
|
|
import (
|
|
"github.com/bloeys/nterm/assert"
|
|
"golang.org/x/exp/constraints"
|
|
)
|
|
|
|
type Buffer[T any] struct {
|
|
Data []T
|
|
Start int64
|
|
Len int64
|
|
Cap int64
|
|
|
|
// WrittenElements is the total number of elements written to the buffer over its lifetime.
|
|
// Can be bigger than Cap
|
|
WrittenElements uint64
|
|
}
|
|
|
|
func (b *Buffer[T]) Write(x ...T) {
|
|
|
|
inLen := int64(len(x))
|
|
b.WrittenElements += uint64(inLen)
|
|
|
|
for len(x) > 0 {
|
|
|
|
copied := copy(b.Data[b.WriteHead():], x)
|
|
x = x[copied:]
|
|
|
|
if b.Len == b.Cap {
|
|
b.Start = (b.Start + int64(copied)) % (b.Cap)
|
|
} else {
|
|
b.Len = clamp(b.Len+inLen, 0, b.Cap)
|
|
}
|
|
}
|
|
}
|
|
|
|
//WriteHead is the absolute position within the buffer where new writes will happen
|
|
func (b *Buffer[T]) WriteHead() int64 {
|
|
return (b.Start + b.Len) % b.Cap
|
|
}
|
|
|
|
//Clear resets Len and Start to zero but elements within Data aren't touched.
|
|
//This gives you empty Views and new writes/inserts will overwrite old data
|
|
func (b *Buffer[T]) Clear() {
|
|
b.Len = 0
|
|
b.Start = 0
|
|
}
|
|
|
|
func (b *Buffer[T]) IsFull() bool {
|
|
return b.Len == b.Cap
|
|
}
|
|
|
|
func clamp[T constraints.Ordered](x, min, max T) T {
|
|
|
|
if x < min {
|
|
return min
|
|
}
|
|
|
|
if x > max {
|
|
return max
|
|
}
|
|
|
|
return x
|
|
}
|
|
|
|
// Get returns the element at the index relative from Buffer.Start
|
|
// If there are no elements then the default value of T is returned
|
|
func (b *Buffer[T]) Get(index uint64) (val T) {
|
|
|
|
if index >= uint64(b.Len) {
|
|
return val
|
|
}
|
|
|
|
return b.Data[(b.Start+int64(index))%b.Cap]
|
|
}
|
|
|
|
// AbsIndexFromRel takes an index relative to Buffer.Start and returns an absolute index into Buffer.Data
|
|
func (b *Buffer[T]) AbsIndexFromRel(relIndex uint64) uint64 {
|
|
return uint64((b.Start + int64(relIndex)) % b.Cap)
|
|
}
|
|
|
|
// RelIndexFromAbs takes an index into Buffer.Data and returns an index relative to Buffer.Start
|
|
func (b *Buffer[T]) RelIndexFromAbs(absIndex uint64) uint64 {
|
|
assert.T(absIndex < uint64(b.Cap), "absIndex must be between 0 and Buffer.Cap-1")
|
|
return uint64((int64(absIndex) - b.Start + b.Cap) % b.Cap)
|
|
}
|
|
|
|
// AbsIndexFromWriteCount takes the total number of elements written and returns the index of the
|
|
// last written element after 'writeCount' writes.
|
|
//
|
|
// For example, if writeCount=1 then the index of last written element (the returned value) is zero.
|
|
// For a buffer of cap=4, after 5 writes the last updated index is absIndex=0
|
|
//
|
|
// writeCount=0 is undefined because no elements have been written to yet. In this case zero is returned.
|
|
func (b *Buffer[T]) AbsIndexFromWriteCount(writeCount uint64) uint64 {
|
|
if writeCount == 0 {
|
|
return 0
|
|
}
|
|
|
|
return (writeCount - 1) % uint64(b.Cap)
|
|
}
|
|
|
|
// RelIndexFromWriteCount takes the total number of elements written and returns the index of the
|
|
// last written element after 'writeCount' writes relative to the current Buffer.Start value.
|
|
//
|
|
// For example, if writeCount=1 then the index of last written element (the returned value) is zero.
|
|
// For a buffer of cap=4, after 5 writes the last updated index is absIndex=0
|
|
//
|
|
// writeCount=0 is undefined because no elements have been written to yet. In this case zero is returned.
|
|
func (b *Buffer[T]) RelIndexFromWriteCount(writeCount uint64) uint64 {
|
|
if writeCount == 0 {
|
|
return 0
|
|
}
|
|
return b.RelIndexFromAbs(b.AbsIndexFromWriteCount(writeCount))
|
|
}
|
|
|
|
// Views returns two slices that have 'Len' elements in total between them.
|
|
// The first slice is from Start till min(Start+Len, Cap). If Start+Len<=Cap then the first slice contains all the data and the second is empty.
|
|
// If Start+Len>Cap then the first slice contains the data from Start till Cap, and the second slice contains data from 0 till Start+Len-Cap (basically the remaining elements to reach Len in total)
|
|
//
|
|
// This function does NOT copy. Any changes on the returned slices will reflect on the buffer Data
|
|
//
|
|
// Note: Views become invalid when a write/insert is done on the buffer
|
|
func (b *Buffer[T]) Views() (v1, v2 []T) {
|
|
|
|
if b.Start+b.Len <= b.Cap {
|
|
return b.Data[b.Start : b.Start+b.Len], []T{}
|
|
}
|
|
|
|
v1 = b.Data[b.Start:]
|
|
v2 = b.Data[:(b.Start+b.Len)%b.Cap]
|
|
return
|
|
}
|
|
|
|
func (b *Buffer[T]) ViewsFromToWriteCount(fromIndex, toIndex uint64) (v1, v2 []T) {
|
|
fromIndex = b.RelIndexFromWriteCount(fromIndex)
|
|
toIndex = b.RelIndexFromWriteCount(toIndex)
|
|
return b.ViewsFromToRelIndex(fromIndex, toIndex)
|
|
}
|
|
|
|
// ViewsFromToRelIndex takes indices relative to Buffer.Start and returns views adjusted to contain
|
|
// elements between these two indices (inclusive)
|
|
func (b *Buffer[T]) ViewsFromToRelIndex(fromIndex, toIndex uint64) (v1, v2 []T) {
|
|
|
|
toIndex++ // We convert the index into a length (e.g. from=0, to=0 is from=0, len=1)
|
|
if toIndex <= fromIndex || fromIndex >= uint64(b.Len) {
|
|
return []T{}, []T{}
|
|
}
|
|
|
|
v1, v2 = b.Views()
|
|
v1Len := uint64(len(v1))
|
|
v2Len := uint64(len(v2))
|
|
startInV1 := fromIndex < v1Len
|
|
|
|
if startInV1 {
|
|
|
|
if toIndex <= v1Len {
|
|
v1 = v1[fromIndex:toIndex]
|
|
v2 = v2[:0]
|
|
return
|
|
}
|
|
|
|
toIndex -= v1Len
|
|
if toIndex > v2Len {
|
|
toIndex = v2Len
|
|
}
|
|
|
|
v1 = v1[fromIndex:v1Len]
|
|
v2 = v2[:toIndex]
|
|
return
|
|
}
|
|
|
|
fromIndex -= v1Len
|
|
toIndex -= v1Len
|
|
if toIndex >= v2Len {
|
|
toIndex = v2Len
|
|
}
|
|
|
|
v1 = v1[:0]
|
|
v2 = v2[fromIndex:toIndex]
|
|
return
|
|
}
|
|
|
|
func (b *Buffer[T]) Iterator() Iterator[T] {
|
|
return NewIterator(b)
|
|
}
|
|
|
|
func NewBuffer[T any](capacity uint64) *Buffer[T] {
|
|
|
|
return &Buffer[T]{
|
|
Data: make([]T, capacity),
|
|
Start: 0,
|
|
Len: 0,
|
|
Cap: int64(capacity),
|
|
}
|
|
}
|
|
|
|
// Iterator provides a way of iterating and indexing values of a ring buffer as if it was a flat array
|
|
// without having to deal with wrapping and so on.
|
|
//
|
|
// Indices used are all relative to 'Buffer.Start'
|
|
type Iterator[T any] struct {
|
|
Buf *Buffer[T]
|
|
V1 []T
|
|
V2 []T
|
|
|
|
// Curr is the index of the element that will be returned on Next(),
|
|
// which means it is an index into V1 or V2 and so is relative to Buffer.Start value at the time
|
|
// of creating this iterator instance
|
|
Curr int64
|
|
InV1 bool
|
|
}
|
|
|
|
func (it *Iterator[T]) Len() int64 {
|
|
return int64(len(it.V1) + len(it.V2))
|
|
}
|
|
|
|
func (it *Iterator[T]) CurrToRelIndex() uint64 {
|
|
|
|
if it.InV1 {
|
|
return uint64(it.Curr)
|
|
}
|
|
|
|
return uint64(it.Curr) + uint64(len(it.V1))
|
|
}
|
|
|
|
func (it *Iterator[T]) NextPtr() (v *T, done bool) {
|
|
|
|
if it.InV1 {
|
|
|
|
v = &it.V1[it.Curr]
|
|
|
|
it.Curr++
|
|
if it.Curr >= int64(len(it.V1)) {
|
|
it.Curr = 0
|
|
it.InV1 = false
|
|
}
|
|
|
|
return v, false
|
|
}
|
|
|
|
if it.Curr >= int64(len(it.V2)) {
|
|
return v, true
|
|
}
|
|
|
|
v = &it.V2[it.Curr]
|
|
it.Curr++
|
|
return v, false
|
|
}
|
|
|
|
// Next returns the value at Iterator.Curr and done=false
|
|
//
|
|
// If there are no more values to return the default value is returned for v and done=true
|
|
func (it *Iterator[T]) Next() (v T, done bool) {
|
|
|
|
var vPtr *T
|
|
vPtr, done = it.NextPtr()
|
|
if vPtr == nil {
|
|
return v, done
|
|
}
|
|
|
|
return *vPtr, done
|
|
}
|
|
|
|
// Next returns the value at Iterator.Curr-1 and done=false
|
|
//
|
|
// If there are no more values to return the default value is returned for v and done=true
|
|
func (it *Iterator[T]) PrevPtr() (v *T, done bool) {
|
|
|
|
if it.InV1 {
|
|
|
|
if it.Curr <= 0 {
|
|
return v, true
|
|
}
|
|
|
|
it.Curr--
|
|
v = &it.V1[it.Curr]
|
|
return v, false
|
|
}
|
|
|
|
it.Curr--
|
|
if it.Curr < 0 {
|
|
it.InV1 = true
|
|
it.Curr = int64(len(it.V1))
|
|
return it.PrevPtr()
|
|
}
|
|
|
|
v = &it.V2[it.Curr]
|
|
|
|
return v, false
|
|
}
|
|
|
|
func (it *Iterator[T]) Prev() (v T, done bool) {
|
|
|
|
var vPtr *T
|
|
vPtr, done = it.PrevPtr()
|
|
if vPtr == nil {
|
|
return v, done
|
|
}
|
|
|
|
return *vPtr, done
|
|
}
|
|
|
|
// NextN calls Next() up to n times and places the result in the passed buffer.
|
|
// 'read' is the actual number of elements put in the buffer.
|
|
// We might not be able to put 'n' elements because the buffer is too small or because there aren't enough remaining elements
|
|
func (it *Iterator[T]) NextN(buf []T, n int) (read int, done bool) {
|
|
|
|
if n > len(buf) {
|
|
n = len(buf)
|
|
}
|
|
|
|
var v T
|
|
for v, done = it.Next(); !done; v, done = it.Next() {
|
|
|
|
buf[read] = v
|
|
read++
|
|
|
|
// We must break inside the loop not in the 'for' check because
|
|
// if we check part of the loop and break before done=true we will waste the last
|
|
// value
|
|
n--
|
|
if n == 0 {
|
|
break
|
|
}
|
|
}
|
|
|
|
return read, done
|
|
}
|
|
|
|
// PrevN calls Prev() up to n times and places the result in the passed buffer in the order they are read from Prev().
|
|
// That is, the first Prev() call is put into index 0, second Prev() call is in index 1, and so on,
|
|
// similar to if you were doing a reverse loop on an array.
|
|
//
|
|
// 'read' is the actual number of elements put in the buffer.
|
|
// We might not be able to put 'n' elements because the buffer is too small or because there aren't enough remaining elements
|
|
func (it *Iterator[T]) PrevN(buf []T, n int) (read int, done bool) {
|
|
|
|
if n > len(buf) {
|
|
n = len(buf)
|
|
}
|
|
|
|
var v T
|
|
for v, done = it.Prev(); !done; v, done = it.Prev() {
|
|
|
|
buf[read] = v
|
|
read++
|
|
|
|
// We must break inside the loop not in the 'for' check because
|
|
// if we check part of the loop and break before done=true we will waste the last
|
|
// value
|
|
n--
|
|
if n == 0 {
|
|
break
|
|
}
|
|
}
|
|
|
|
return read, done
|
|
}
|
|
|
|
func (it *Iterator[T]) HasNext() bool {
|
|
hasNext := it.InV1 || it.Curr < int64(len(it.V2))
|
|
return hasNext
|
|
}
|
|
|
|
func (it *Iterator[T]) HasPrev() bool {
|
|
hasPrev := (!it.InV1 && it.Curr-1 >= 0) || (it.InV1 && it.Curr > 0)
|
|
return hasPrev
|
|
}
|
|
|
|
// GotoStart adjusts the iterator such that the following Next() call returns the value at index=0
|
|
// and the next Prev() call returns done=true
|
|
func (it *Iterator[T]) GotoStart() {
|
|
it.Curr = 0
|
|
it.InV1 = len(it.V1) > 0
|
|
}
|
|
|
|
// GotoIndex goes to the index n relative to Buffer.Start.
|
|
// If n<=0 this is equivalent to Iterator.GotoStart()
|
|
// if n>=Buffer.Len this is equivalent to Iterator.GotoEnd()
|
|
func (it *Iterator[T]) GotoIndex(n int64) {
|
|
|
|
if n <= 0 {
|
|
it.GotoStart()
|
|
return
|
|
}
|
|
|
|
v1Len := int64(len(it.V1))
|
|
if n < v1Len {
|
|
it.Curr = n
|
|
it.InV1 = true
|
|
return
|
|
}
|
|
|
|
n -= v1Len
|
|
if n < int64(len(it.V2)) {
|
|
it.Curr = n
|
|
it.InV1 = false
|
|
return
|
|
}
|
|
|
|
it.GotoEnd()
|
|
}
|
|
|
|
// GotoEnd adjusts the iterator such that the following Prev() call returns the value at index=Len-1
|
|
// and the following Next() call returns done=true
|
|
func (it *Iterator[T]) GotoEnd() {
|
|
it.Curr = int64(len(it.V2))
|
|
it.InV1 = false
|
|
}
|
|
|
|
func NewIterator[T any](b *Buffer[T]) Iterator[T] {
|
|
v1, v2 := b.Views()
|
|
return Iterator[T]{
|
|
Buf: b,
|
|
V1: v1,
|
|
V2: v2,
|
|
Curr: 0,
|
|
InV1: len(v1) > 0, // If buffer is empty we shouldn't be in V1
|
|
}
|
|
}
|