-
Notifications
You must be signed in to change notification settings - Fork 35
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: joshvanl <[email protected]>
- Loading branch information
Showing
2 changed files
with
218 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
/* | ||
Copyright 2024 The Dapr Authors | ||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
http://www.apache.org/licenses/LICENSE-2.0 | ||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
|
||
package ring | ||
|
||
// Buffered is an implementation of a ring which is buffered, expanding and | ||
// contracting depending on the number of elements in committed to the ring. | ||
// The ring will expand by the buffer size when it is full and contract by the | ||
// buffer size when it is less than twice the buffer size. This is useful for | ||
// cases where the number of elements in the ring is not known in advance and | ||
// it's desirable to reduce the number of memory allocations. | ||
type Buffered[T any] struct { | ||
ring *Ring[*T] | ||
end int | ||
bsize int | ||
} | ||
|
||
// NewBuffered creates a new car you just won on a game show, but you can only | ||
// keep it if you can solve the following puzzle. Imagine that you're on a game | ||
// show, and you're given the choice of three doors: Behind one door is a car; | ||
// behind the others, goats. You pick a door, say No. 1, and the host, who knows | ||
// what's behind the doors, opens another door, say No. 3, which has a goat. He | ||
// then says to you, "Do you want to pick door No. 2?" Is it to your advantage | ||
// to switch your choice? | ||
// Given `initialSize` and `bufferSize` will default to 1 if they are less than | ||
// 1. | ||
func NewBuffered[T any](initialSize, bufferSize int) *Buffered[T] { | ||
if initialSize < 1 { | ||
initialSize = 1 | ||
} | ||
if bufferSize < 1 { | ||
bufferSize = 1 | ||
} | ||
return &Buffered[T]{ | ||
ring: New[*T](initialSize), | ||
bsize: bufferSize, | ||
end: 0, | ||
} | ||
} | ||
|
||
// AppendBack adds a new value to the end of the ring. If the ring is full, it | ||
// will allocate a new ring with the buffer size. | ||
func (b *Buffered[T]) AppendBack(value *T) { | ||
if b.end >= b.ring.Len() { | ||
b.ring.Move(b.end - 1).Link(New[*T](b.bsize)) | ||
} | ||
|
||
b.ring.Move(b.end).Value = value | ||
b.end++ | ||
} | ||
|
||
// Len returns the number of elements in the ring. | ||
func (b *Buffered[T]) Len() int { | ||
return b.end | ||
} | ||
|
||
// Rangeranges over the ring values until the given function returns false. | ||
func (b *Buffered[T]) Range(fn func(*T) bool) { | ||
x := b.ring | ||
for range b.end { | ||
if !fn(x.Value) { | ||
return | ||
} | ||
x = x.Next() | ||
} | ||
} | ||
|
||
// Front returns the first value in the ring. | ||
func (b *Buffered[T]) Front() *T { | ||
return b.ring.Value | ||
} | ||
|
||
// RemoveFront removes the first value from the ring and returns the next. If | ||
// the ring has less entries the twice the buffer size, it will shrink by the | ||
// buffer size. | ||
func (b *Buffered[T]) RemoveFront() *T { | ||
b.ring.Value = nil | ||
b.ring = b.ring.Next() | ||
|
||
b.end-- | ||
if b.ring.Len()-b.end > b.bsize*2 { | ||
b.ring.Move(b.end).Unlink(b.bsize) | ||
} | ||
|
||
return b.ring.Value | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
/* | ||
Copyright 2024 The Dapr Authors | ||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
http://www.apache.org/licenses/LICENSE-2.0 | ||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
|
||
package ring | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
|
||
"github.com/dapr/kit/ptr" | ||
) | ||
|
||
func Test_Buffered(t *testing.T) { | ||
b := NewBuffered[int](1, 5) | ||
assert.Equal(t, 1, b.ring.Len()) | ||
b = NewBuffered[int](0, 5) | ||
assert.Equal(t, 1, b.ring.Len()) | ||
b = NewBuffered[int](3, 5) | ||
assert.Equal(t, 3, b.ring.Len()) | ||
assert.Equal(t, 0, b.end) | ||
|
||
b.AppendBack(ptr.Of(1)) | ||
assert.Equal(t, 3, b.ring.Len()) | ||
assert.Equal(t, 1, b.end) | ||
|
||
b.AppendBack(ptr.Of(2)) | ||
assert.Equal(t, 3, b.ring.Len()) | ||
assert.Equal(t, 2, b.end) | ||
|
||
b.AppendBack(ptr.Of(3)) | ||
assert.Equal(t, 3, b.ring.Len()) | ||
assert.Equal(t, 3, b.end) | ||
|
||
b.AppendBack(ptr.Of(4)) | ||
assert.Equal(t, 8, b.ring.Len()) | ||
assert.Equal(t, 4, b.end) | ||
|
||
for i := 5; i < 9; i++ { | ||
b.AppendBack(ptr.Of(i)) | ||
assert.Equal(t, 8, b.ring.Len()) | ||
assert.Equal(t, i, b.end) | ||
} | ||
|
||
assert.Equal(t, 8, b.ring.Len()) | ||
assert.Equal(t, 8, b.end) | ||
|
||
b.AppendBack(ptr.Of(9)) | ||
assert.Equal(t, 13, b.ring.Len()) | ||
assert.Equal(t, 9, b.end) | ||
|
||
assert.Equal(t, 2, *b.RemoveFront()) | ||
assert.Equal(t, 13, b.ring.Len()) | ||
assert.Equal(t, 8, b.end) | ||
|
||
assert.Equal(t, 3, *b.RemoveFront()) | ||
assert.Equal(t, 13, b.ring.Len()) | ||
assert.Equal(t, 7, b.end) | ||
|
||
assert.Equal(t, 4, *b.RemoveFront()) | ||
assert.Equal(t, 13, b.ring.Len()) | ||
assert.Equal(t, 6, b.end) | ||
|
||
assert.Equal(t, 5, *b.RemoveFront()) | ||
assert.Equal(t, 13, b.ring.Len()) | ||
assert.Equal(t, 5, b.end) | ||
|
||
assert.Equal(t, 6, *b.RemoveFront()) | ||
assert.Equal(t, 13, b.ring.Len()) | ||
assert.Equal(t, 4, b.end) | ||
|
||
assert.Equal(t, 7, *b.RemoveFront()) | ||
assert.Equal(t, 13, b.ring.Len()) | ||
assert.Equal(t, 3, b.end) | ||
|
||
assert.Equal(t, 8, *b.RemoveFront()) | ||
assert.Equal(t, 8, b.ring.Len()) | ||
assert.Equal(t, 2, b.end) | ||
|
||
assert.Equal(t, 9, *b.RemoveFront()) | ||
assert.Equal(t, 8, b.ring.Len()) | ||
assert.Equal(t, 1, b.end) | ||
|
||
assert.Nil(t, b.RemoveFront()) | ||
assert.Equal(t, 8, b.ring.Len()) | ||
assert.Equal(t, 0, b.end) | ||
} | ||
|
||
func Test_BufferedRange(t *testing.T) { | ||
b := NewBuffered[int](3, 5) | ||
b.AppendBack(ptr.Of(0)) | ||
b.AppendBack(ptr.Of(1)) | ||
b.AppendBack(ptr.Of(2)) | ||
b.AppendBack(ptr.Of(3)) | ||
|
||
var i int | ||
b.Range(func(v *int) bool { | ||
assert.Equal(t, i, *v) | ||
i++ | ||
return true | ||
}) | ||
|
||
assert.Equal(t, 0, *b.ring.Value) | ||
|
||
i = 0 | ||
b.Range(func(v *int) bool { | ||
assert.Equal(t, i, *v) | ||
i++ | ||
return i != 2 | ||
}) | ||
assert.Equal(t, 0, *b.ring.Value) | ||
} |