Files
legacy-version/internal/chunk/palette.go
2024-12-31 20:57:21 +07:00

132 lines
4.0 KiB
Go

package chunk
import (
"math"
)
// paletteSize is the size of a palette. It indicates the amount of bits occupied per value stored.
type paletteSize byte
// Palette is a palette of values that every PalettedStorage has. Storages hold 'pointers' to indices
// in this palette.
type Palette struct {
last uint32
lastIndex int16
size paletteSize
// values is a map of values. A PalettedStorage points to the index to this value.
values []uint32
}
// newPalette returns a new Palette with size and a slice of added values.
func newPalette(size paletteSize, values []uint32) *Palette {
return &Palette{size: size, values: values, last: math.MaxUint32}
}
// Len returns the amount of unique values in the Palette.
func (palette *Palette) Len() int {
return len(palette.values)
}
// Add adds a values to the Palette. It does not first check if the value was already set in the Palette.
// The index at which the value was added is returned. Another bool is returned indicating if the Palette
// was resized as a result of adding the value.
func (palette *Palette) Add(v uint32) (index int16, resize bool) {
i := int16(len(palette.values))
palette.values = append(palette.values, v)
if palette.needsResize() {
palette.increaseSize()
return i, true
}
return i, false
}
// Replace calls the function passed for each value present in the Palette. The value returned by the
// function replaces the value present at the index of the value passed.
func (palette *Palette) Replace(f func(v uint32) uint32) {
// Reset last runtime ID as it now has a different offset.
palette.last = math.MaxUint32
for index, v := range palette.values {
palette.values[index] = f(v)
}
}
// Index loops through the values of the Palette and looks for the index of the given value. If the value could
// not be found, -1 is returned.
func (palette *Palette) Index(runtimeID uint32) int16 {
if runtimeID == palette.last {
// Fast path out.
return palette.lastIndex
}
// Slow path in a separate function allows for inlining the fast path.
return palette.indexSlow(runtimeID)
}
// indexSlow searches the index of a value in the Palette's values by iterating through the Palette's values.
func (palette *Palette) indexSlow(runtimeID uint32) int16 {
l := len(palette.values)
for i := 0; i < l; i++ {
if palette.values[i] == runtimeID {
palette.last = runtimeID
v := int16(i)
palette.lastIndex = v
return v
}
}
return -1
}
// Value returns the value in the Palette at a specific index.
func (palette *Palette) Value(i uint16) uint32 {
return palette.values[i]
}
// needsResize checks if the Palette, and with it the holding PalettedStorage, needs to be resized to a bigger
// size.
func (palette *Palette) needsResize() bool {
return len(palette.values) > (1 << palette.size)
}
var sizes = [...]paletteSize{0, 1, 2, 3, 4, 5, 6, 8, 16}
var offsets = [...]int{0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 8: 7, 16: 8}
// increaseSize increases the size of the Palette to the next palette size.
func (palette *Palette) increaseSize() {
palette.size = sizes[offsets[palette.size]+1]
}
// padded returns true if the Palette size is 3, 5 or 6.
func (p paletteSize) padded() bool {
return p == 3 || p == 5 || p == 6
}
// paletteSizeFor finds a suitable paletteSize for the amount of values passed n.
func paletteSizeFor(n int) paletteSize {
for _, size := range sizes {
if n <= (1 << size) {
return size
}
}
// Should never happen.
return 0
}
// uint32s returns the amount of uint32s needed to represent a storage with this palette size.
func (p paletteSize) uint32s() (n int) {
uint32Count := 0
if p != 0 {
// indicesPerUint32 is the amount of indices that may be stored in a single uint32.
indicesPerUint32 := 32 / int(p)
// uint32Count is the amount of uint32s required to store all indices: 4096 indices need to be stored in
// total.
uint32Count = 4096 / indicesPerUint32
}
if p.padded() {
// We've got one of the padded sizes, so the storage has another uint32 to be able to store
// every index.
uint32Count++
}
return uint32Count
}