Files
akvorado/common/helpers/intern.go
Vincent Bernat 7196ccf73b inlet/bmp: revert new BMP design
It needs more work to be stable. Let's do a release without it.
2022-11-26 11:42:46 +01:00

149 lines
4.0 KiB
Go

// SPDX-FileCopyrightText: 2022 Free Mobile
// SPDX-License-Identifier: AGPL-3.0-only
package helpers
// InternValue is the interface that should be implemented by types
// used in an intern pool. Also, it should be immutable.
type InternValue[T any] interface {
Hash() uint64
Equal(T) bool
}
// InternReference is a reference to an interned value. 0 is not a
// valid reference value.
type InternReference[T any] uint32
// InternPool keeps values in a pool by storing only one distinct copy
// of each. Values will be referred as an uint32 (implemented as an
// index).
type InternPool[T InternValue[T]] struct {
values []internValue[T]
availableIndexes []InternReference[T]
valueIndexes map[uint64]InternReference[T]
}
// internValue is the value stored in an intern pool. It adds resource
// keeping to the raw value.
type internValue[T InternValue[T]] struct {
next InternReference[T] // next value with the same hash
previous InternReference[T] // previous value with the same hash
refCount uint32
value T
}
// NewInternPool creates a new intern pool.
func NewInternPool[T InternValue[T]]() *InternPool[T] {
return &InternPool[T]{
values: make([]internValue[T], 1), // first slot is reserved
availableIndexes: make([]InternReference[T], 0),
valueIndexes: make(map[uint64]InternReference[T]),
}
}
// Get retrieves a (copy of the) value from the intern pool using its reference.
func (p *InternPool[T]) Get(ref InternReference[T]) T {
return p.values[ref].value
}
// Take removes a value from the intern pool. If this is the last
// used reference, it will be deleted from the pool.
func (p *InternPool[T]) Take(ref InternReference[T]) {
value := &p.values[ref]
value.refCount--
if value.refCount == 0 {
p.availableIndexes = append(p.availableIndexes, ref)
if value.previous > 0 {
// Not the first one, link previous to next
p.values[value.previous].next = value.next
p.values[value.next].previous = value.previous
return
}
hash := value.value.Hash()
if value.next > 0 {
// We are the first one of a chain, move the pointer to the next one
p.valueIndexes[hash] = value.next
p.values[value.next].previous = 0
return
}
// Last case, we are the last one, let's find our hash and delete us from here
delete(p.valueIndexes, hash)
}
}
// Ref returns the reference an interned value would have.
func (p *InternPool[T]) Ref(value T) (InternReference[T], bool) {
hash := value.Hash()
if index := p.valueIndexes[hash]; index > 0 {
for index > 0 {
if p.values[index].value.Equal(value) {
return index, true
}
index = p.values[index].next
}
}
return 0, false
}
// Put adds a value to the intern pool, returning its reference.
func (p *InternPool[T]) Put(value T) InternReference[T] {
v := internValue[T]{
value: value,
refCount: 1,
previous: 0,
next: 0,
}
// Allocate a new index
newIndex := func() InternReference[T] {
availCount := len(p.availableIndexes)
if availCount > 0 {
index := p.availableIndexes[availCount-1]
p.availableIndexes = p.availableIndexes[:availCount-1]
return index
}
if len(p.values) == cap(p.values) {
// We need to extend capacity first
temp := make([]internValue[T], len(p.values), (cap(p.values)+1)*2)
copy(temp, p.values)
p.values = temp
}
index := len(p.values)
p.values = p.values[:index+1]
return InternReference[T](index)
}
// Check if we have already something
hash := value.Hash()
if index := p.valueIndexes[hash]; index > 0 {
prevIndex := index
for index > 0 {
if p.values[index].value.Equal(value) {
p.values[index].refCount++
return index
}
prevIndex = index
index = p.values[index].next
}
// We have a collision, add to the chain
index = newIndex()
v.previous = prevIndex
p.values[prevIndex].next = index
p.values[index] = v
return index
}
// Add a new one
index := newIndex()
p.values[index] = v
p.valueIndexes[hash] = index
return index
}
// Len returns the number of elements in the pool.
func (p *InternPool[T]) Len() int {
return len(p.values) - len(p.availableIndexes) - 1
}