Skip to content

Commit 73593d5

Browse files
committed
go2go: overhaul the Buffer and PubSub types to use generics
Use the go2go generics experiment to enforce type checking at compile time instead of runtime. Switch the Buffer type to manipulate a slice of comparable T's instead of a slice of empty interfaces. Remove the global EmptyMarker in favor of a per-Buffer instance empty marker value. Add an any T type parameter to the PubSub type. Use an internal Buffer of cells of T, which hold a T value or a marker channel. The marker channels indicate an unsubscribing consumer or the closing of the PubSub instance. Drop the Publisher & Subscriber API, it is no longer needed now that the publish & subscribe methods for both functions & channels support the instance's type parameter.
1 parent 86eaf77 commit 73593d5

File tree

7 files changed

+311
-202
lines changed

7 files changed

+311
-202
lines changed

buffer.go2

Lines changed: 26 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,12 @@ import (
77
"github.com/benburkert/pubsub/cursor"
88
)
99

10-
type Marker byte
10+
type ReaderFunc[T comparable] func(T) bool
1111

12-
const (
13-
EmptyMarker Marker = iota
14-
)
15-
16-
type ReaderFunc func(interface{}) bool
17-
18-
type Buffer struct {
19-
mu sync.RWMutex
20-
data []interface{}
12+
type Buffer[T comparable] struct {
13+
mu sync.RWMutex
14+
data []T
15+
empty T
2116

2217
wcond *sync.Cond
2318
wcursor *cursor.Cursor
@@ -26,26 +21,27 @@ type Buffer struct {
2621
rcursors cursor.Slice
2722
}
2823

29-
func NewBuffer(minSize, maxReaders int) *Buffer {
30-
size := calcBufferSize(minSize)
24+
func NewBuffer[T comparable](empty T, minSize, maxReaders int) *Buffer[T] {
25+
size := CalcBufferSize(minSize)
3126
mask := size - 1
3227

33-
b := &Buffer{
34-
data: make([]interface{}, size),
28+
b := &Buffer[T]{
29+
data: make([]T, size),
30+
empty: empty,
3531
wcursor: cursor.New(0, mask),
3632
rcursors: cursor.MakeSlice(maxReaders, mask),
3733
}
3834

3935
for i := range b.data {
40-
b.data[i] = EmptyMarker
36+
b.data[i] = empty
4137
}
4238

4339
b.wcond = sync.NewCond(&b.mu)
4440
b.rcond = sync.NewCond(b.mu.RLocker())
4541
return b
4642
}
4743

48-
func (b *Buffer) FullReadTo(rfn ReaderFunc) []interface{} {
44+
func (b *Buffer[T]) FullReadTo(rfn ReaderFunc[T]) []T {
4945
b.mu.RLock() // unlocked in readTo
5046

5147
c := b.getCursor() // reset in readTo
@@ -55,7 +51,7 @@ func (b *Buffer) FullReadTo(rfn ReaderFunc) []interface{} {
5551
return s
5652
}
5753

58-
func (b *Buffer) Read() []interface{} {
54+
func (b *Buffer[T]) Read() []T {
5955
b.mu.RLock()
6056
defer b.mu.RUnlock()
6157

@@ -65,20 +61,20 @@ func (b *Buffer) Read() []interface{} {
6561
return b.read(c)
6662
}
6763

68-
func (b *Buffer) ReadTo(rfn ReaderFunc) {
64+
func (b *Buffer[T]) ReadTo(rfn ReaderFunc[T]) {
6965
b.mu.RLock() // unlocked in readTo
7066

7167
go b.readTo(b.getCursor(), rfn)
7268
}
7369

74-
func (b *Buffer) Write(v interface{}) {
70+
func (b *Buffer[T]) Write(v T) {
7571
b.mu.Lock()
7672
defer b.mu.Unlock()
7773

7874
b.write(v)
7975
}
8076

81-
func (b *Buffer) WriteSlice(vs []interface{}) {
77+
func (b *Buffer[T]) WriteSlice(vs []T) {
8278
b.mu.Lock()
8379
defer b.mu.Unlock()
8480

@@ -88,33 +84,33 @@ func (b *Buffer) WriteSlice(vs []interface{}) {
8884
}
8985

9086
// assumes b.mu RLock held
91-
func (b *Buffer) getCursor() *cursor.Cursor {
87+
func (b *Buffer[T]) getCursor() *cursor.Cursor {
9288
return b.rcursors.Alloc(b.wcursor.Pos())
9389
}
9490

9591
// assumes b.mu Rlock held
96-
func (b *Buffer) read(c *cursor.Cursor) []interface{} {
92+
func (b *Buffer[T]) read(c *cursor.Cursor) []T {
9793
rpos := c.Pos()
98-
if b.data[rpos] == EmptyMarker {
99-
s := make([]interface{}, rpos)
94+
if b.data[rpos] == b.empty {
95+
s := make([]T, rpos)
10096
copy(s, b.data[:rpos])
10197
return s
10298
}
10399

104100
size := int(len(b.data))
105-
s := make([]interface{}, size)
101+
s := make([]T, size)
106102
copy(s[:(size-rpos)], b.data[rpos:])
107103
copy(s[(size-rpos):], b.data[:rpos])
108104
return s
109105
}
110106

111107
// assumes b.mu RLock held
112-
func (b *Buffer) readBarrier(c *cursor.Cursor) bool {
108+
func (b *Buffer[T]) readBarrier(c *cursor.Cursor) bool {
113109
return c.Pos() == b.wcursor.Pos()
114110
}
115111

116112
// asumes b.mu RLock held
117-
func (b *Buffer) readTo(c *cursor.Cursor, rfn ReaderFunc) {
113+
func (b *Buffer[T]) readTo(c *cursor.Cursor, rfn ReaderFunc[T]) {
118114
defer b.mu.RUnlock()
119115
defer b.wcond.Signal()
120116
defer c.Reset()
@@ -136,7 +132,7 @@ func (b *Buffer) readTo(c *cursor.Cursor, rfn ReaderFunc) {
136132
}
137133

138134
// asumes b.mu Lock held
139-
func (b *Buffer) write(v interface{}) {
135+
func (b *Buffer[T]) write(v T) {
140136
for b.writeBarrier() {
141137
b.wcond.Wait()
142138
}
@@ -149,7 +145,7 @@ func (b *Buffer) write(v interface{}) {
149145
}
150146

151147
// assumes b.mu Lock held
152-
func (b *Buffer) writeBarrier() bool {
148+
func (b *Buffer[T]) writeBarrier() bool {
153149
npos := b.wcursor.Next()
154150
for _, c := range b.rcursors {
155151
if npos == c.Pos() {
@@ -159,6 +155,6 @@ func (b *Buffer) writeBarrier() bool {
159155
return false
160156
}
161157

162-
func calcBufferSize(minSize int) int {
158+
func CalcBufferSize(minSize int) int {
163159
return int(math.Pow(2, math.Ceil(math.Log2(float64(minSize)))))
164160
}

0 commit comments

Comments
 (0)