You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
70 lines
1.6 KiB
Go
70 lines
1.6 KiB
Go
package crdt
|
|
|
|
import (
|
|
"fmt"
|
|
"constraints"
|
|
)
|
|
|
|
func max[N constraints.Ordered](a, b N) N {
|
|
if a >= b {
|
|
return a
|
|
}
|
|
return b
|
|
}
|
|
|
|
func NewGCounter[K comparable]() GCounter[K] {
|
|
return GCounter[K]{slots: make(map[K]int)}
|
|
}
|
|
|
|
// GCounter is a grow-only counter.
|
|
//
|
|
// In the general case, some N hosts will read and write to the gcounter and
|
|
// periodically merge their state, providing eventual consistency and allowing
|
|
// all nodes to write. Each node must have a unique ID, and should write into
|
|
// the slot that associates to that ID. The slot ID is not embedded into the
|
|
// gcounter itself, and the assignment of IDs to nodes is not provided.
|
|
type GCounter[K comparable] struct {
|
|
slots map[K]int `json:"slots"`
|
|
}
|
|
|
|
// Incr increments the value in the gcounter at the provided slot. Callers must
|
|
// provide the slot to be incremeneted.
|
|
func (g GCounter[K]) Incr(slot K) error {
|
|
var zero K
|
|
if slot == zero {
|
|
return fmt.Errorf("gcounter refuses incr on the zero-value of its key")
|
|
}
|
|
|
|
g.slots[slot]++
|
|
return nil
|
|
}
|
|
|
|
func (g GCounter[K]) Add(slot K, delta int) error {
|
|
var zero K
|
|
if slot == zero {
|
|
return fmt.Errorf("gcounter refuses add on the zero-value of its key")
|
|
}
|
|
|
|
if delta < 0 {
|
|
return fmt.Errorf("gcounters cannot go down, use a pncounter instead")
|
|
}
|
|
g.slots[slot] += delta
|
|
return nil
|
|
}
|
|
|
|
// Merge into some destination val
|
|
func (g *GCounter[K]) MergeInto(dest *GCounter[K]) {
|
|
for slot, count := range g.slots {
|
|
v := max(count, dest.slots[slot])
|
|
dest.slots[slot] = v
|
|
}
|
|
}
|
|
|
|
func (g *GCounter[K]) Total() int {
|
|
var n int
|
|
for _, count := range g.slots {
|
|
n += count
|
|
}
|
|
return n
|
|
}
|