201 lines
8.5 KiB
Go
201 lines
8.5 KiB
Go
package chunk
|
|
|
|
import (
|
|
"bytes"
|
|
"reflect"
|
|
"unsafe"
|
|
)
|
|
|
|
const (
|
|
// uint32ByteSize is the amount of bytes in a uint32.
|
|
uint32ByteSize = 4
|
|
// uint32BitSize is the amount of bits in a uint32.
|
|
uint32BitSize = uint32ByteSize * 8
|
|
)
|
|
|
|
// PalettedStorage is a storage of 4096 blocks encoded in a variable amount of uint32s, storages may have values
|
|
// with a bit size per block of 0, 1, 2, 3, 4, 5, 6, 8 or 16 bits.
|
|
// 3 of these formats have additional padding in every uint32 and an additional uint32 at the end, to cater
|
|
// for the blocks that don't fit. This padding is present when the storage has a block size of 3, 5 or 6
|
|
// bytes.
|
|
// Methods on PalettedStorage must not be called simultaneously from multiple goroutines.
|
|
type PalettedStorage struct {
|
|
// bitsPerIndex is the amount of bits required to store one block. The number increases as the block
|
|
// storage holds more unique block states.
|
|
bitsPerIndex uint16
|
|
// filledBitsPerIndex returns the amount of blocks that are actually filled per uint32.
|
|
filledBitsPerIndex uint16
|
|
// indexMask is the equivalent of 1 << bitsPerIndex - 1.
|
|
indexMask uint32
|
|
|
|
// indicesStart holds an unsafe.Pointer to the first byte in the indices slice below.
|
|
indicesStart unsafe.Pointer
|
|
|
|
// Palette holds all block runtime IDs that the indices in the indices slice point to. These runtime IDs
|
|
// point to block states.
|
|
palette *Palette
|
|
|
|
// indices contains all indices in the PalettedStorage. This slice has a variable size, but may not be changed
|
|
// unless the whole PalettedStorage is resized, including the Palette.
|
|
indices []uint32
|
|
}
|
|
|
|
// newPalettedStorage creates a new block storage using the uint32 slice as the indices and the palette passed.
|
|
// The bits per block are calculated using the length of the uint32 slice.
|
|
func newPalettedStorage(indices []uint32, palette *Palette) *PalettedStorage {
|
|
var (
|
|
bitsPerIndex = uint16(len(indices) / uint32BitSize / uint32ByteSize)
|
|
indexMask = (uint32(1) << bitsPerIndex) - 1
|
|
indicesStart = (unsafe.Pointer)((*reflect.SliceHeader)(unsafe.Pointer(&indices)).Data)
|
|
filledBitsPerIndex uint16
|
|
)
|
|
if bitsPerIndex != 0 {
|
|
filledBitsPerIndex = uint32BitSize / bitsPerIndex * bitsPerIndex
|
|
}
|
|
return &PalettedStorage{filledBitsPerIndex: filledBitsPerIndex, indexMask: indexMask, indicesStart: indicesStart, bitsPerIndex: bitsPerIndex, indices: indices, palette: palette}
|
|
}
|
|
|
|
// emptyStorage creates a PalettedStorage filled completely with a value v.
|
|
func emptyStorage(v uint32) *PalettedStorage {
|
|
return newPalettedStorage([]uint32{}, newPalette(0, []uint32{v}))
|
|
}
|
|
|
|
// Palette returns the Palette of the PalettedStorage.
|
|
func (storage *PalettedStorage) Palette() *Palette {
|
|
return storage.palette
|
|
}
|
|
|
|
// At returns the value of the PalettedStorage at a given x, y and z.
|
|
func (storage *PalettedStorage) At(x, y, z byte) uint32 {
|
|
return storage.palette.Value(storage.paletteIndex(x&15, y&15, z&15))
|
|
}
|
|
|
|
// Set sets a value at a specific x, y and z. The Palette and PalettedStorage are expanded
|
|
// automatically to make space for the value, should that be needed.
|
|
func (storage *PalettedStorage) Set(x, y, z byte, v uint32) {
|
|
index := storage.palette.Index(v)
|
|
if index == -1 {
|
|
// The runtime ID was not yet available in the palette. We add it, then check if the block storage
|
|
// needs to be resized for the palette pointers to fit.
|
|
index = storage.addNew(v)
|
|
}
|
|
storage.setPaletteIndex(x&15, y&15, z&15, uint16(index))
|
|
}
|
|
|
|
// Equal checks if two PalettedStorages are equal value wise. False is returned
|
|
// if either of the storages are nil.
|
|
func (storage *PalettedStorage) Equal(other *PalettedStorage) bool {
|
|
if storage == nil || other == nil {
|
|
return false
|
|
}
|
|
if len(storage.indices) == 0 || len(other.indices) == 0 || storage.palette.values[0] == 0 || other.palette.values[0] == 0 {
|
|
return false
|
|
}
|
|
indicesA := unsafe.Slice((*byte)(unsafe.Pointer(&storage.indices[0])), len(storage.indices)*4)
|
|
indicesB := unsafe.Slice((*byte)(unsafe.Pointer(&other.indices[0])), len(other.indices)*4)
|
|
if !bytes.Equal(indicesA, indicesB) {
|
|
return false
|
|
}
|
|
paletteA := unsafe.Slice((*byte)(unsafe.Pointer(&storage.palette.values[0])), len(storage.palette.values)*4)
|
|
paletteB := unsafe.Slice((*byte)(unsafe.Pointer(&other.palette.values[0])), len(other.palette.values)*4)
|
|
return bytes.Equal(paletteA, paletteB)
|
|
}
|
|
|
|
// addNew adds a new value to the PalettedStorage's Palette and returns its index. If needed, the storage is resized.
|
|
func (storage *PalettedStorage) addNew(v uint32) int16 {
|
|
index, resize := storage.palette.Add(v)
|
|
if resize {
|
|
storage.resize(storage.palette.size)
|
|
}
|
|
return index
|
|
}
|
|
|
|
// paletteIndex looks up the Palette index at a given x, y and z value in the PalettedStorage. This palette
|
|
// index is not the value at this offset, but merely an index in the Palette pointing to a value.
|
|
func (storage *PalettedStorage) paletteIndex(x, y, z byte) uint16 {
|
|
if storage.bitsPerIndex == 0 {
|
|
// Unfortunately our default logic cannot deal with 0 bits per index, meaning we'll have to special case
|
|
// this. This comes with a little performance hit, but it seems to be the only way to go. An alternative would
|
|
// be not to have 0 bits per block storages in memory, but that would cause a strongly increased memory usage
|
|
// by biomes.
|
|
return 0
|
|
}
|
|
offset := ((uint16(x) << 8) | (uint16(z) << 4) | uint16(y)) * storage.bitsPerIndex
|
|
uint32Offset, bitOffset := offset/storage.filledBitsPerIndex, offset%storage.filledBitsPerIndex
|
|
|
|
w := *(*uint32)(unsafe.Pointer(uintptr(storage.indicesStart) + uintptr(uint32Offset<<2)))
|
|
return uint16((w >> bitOffset) & storage.indexMask)
|
|
}
|
|
|
|
// setPaletteIndex sets the palette index at a given x, y and z to paletteIndex. This index should point
|
|
// to a value in the PalettedStorage's Palette.
|
|
func (storage *PalettedStorage) setPaletteIndex(x, y, z byte, i uint16) {
|
|
if storage.bitsPerIndex == 0 {
|
|
return
|
|
}
|
|
offset := ((uint16(x) << 8) | (uint16(z) << 4) | uint16(y)) * storage.bitsPerIndex
|
|
uint32Offset, bitOffset := offset/storage.filledBitsPerIndex, offset%storage.filledBitsPerIndex
|
|
|
|
ptr := (*uint32)(unsafe.Pointer(uintptr(storage.indicesStart) + uintptr(uint32Offset<<2)))
|
|
*ptr = (*ptr &^ (storage.indexMask << bitOffset)) | (uint32(i) << bitOffset)
|
|
}
|
|
|
|
// resize changes the size of a PalettedStorage to newPaletteSize. A new PalettedStorage is constructed,
|
|
// and all values available in the current storage are set in their appropriate locations in the
|
|
// new storage.
|
|
func (storage *PalettedStorage) resize(newPaletteSize paletteSize) {
|
|
if newPaletteSize == paletteSize(storage.bitsPerIndex) {
|
|
return // Don't resize if the size is already equal.
|
|
}
|
|
// Construct a new storage and set all values in there manually. We can't easily do this in a better
|
|
// way, because all values will be at a different index with a different length.
|
|
newStorage := newPalettedStorage(make([]uint32, newPaletteSize.uint32s()), storage.palette)
|
|
for x := byte(0); x < 16; x++ {
|
|
for y := byte(0); y < 16; y++ {
|
|
for z := byte(0); z < 16; z++ {
|
|
newStorage.setPaletteIndex(x, y, z, storage.paletteIndex(x, y, z))
|
|
}
|
|
}
|
|
}
|
|
// Set the new storage.
|
|
*storage = *newStorage
|
|
}
|
|
|
|
// compact clears unused indexes in the palette by scanning for usages in the PalettedStorage. This is a
|
|
// relatively heavy task which should only happen right before the sub chunk holding this PalettedStorage is
|
|
// saved to disk. compact also shrinks the palette size if possible.
|
|
func (storage *PalettedStorage) compact() {
|
|
usedIndices := make([]bool, storage.palette.Len())
|
|
for x := byte(0); x < 16; x++ {
|
|
for y := byte(0); y < 16; y++ {
|
|
for z := byte(0); z < 16; z++ {
|
|
usedIndices[storage.paletteIndex(x, y, z)] = true
|
|
}
|
|
}
|
|
}
|
|
newRuntimeIDs := make([]uint32, 0, len(usedIndices))
|
|
conversion := make([]uint16, len(usedIndices))
|
|
|
|
for index, set := range usedIndices {
|
|
if set {
|
|
conversion[index] = uint16(len(newRuntimeIDs))
|
|
newRuntimeIDs = append(newRuntimeIDs, storage.palette.values[index])
|
|
}
|
|
}
|
|
// Construct a new storage and set all values in there manually. We can't easily do this in a better
|
|
// way, because all values will be at a different index with a different length.
|
|
size := paletteSizeFor(len(newRuntimeIDs))
|
|
newStorage := newPalettedStorage(make([]uint32, size.uint32s()), newPalette(size, newRuntimeIDs))
|
|
|
|
for x := byte(0); x < 16; x++ {
|
|
for y := byte(0); y < 16; y++ {
|
|
for z := byte(0); z < 16; z++ {
|
|
// Replace all usages of the old palette indexes with the new indexes using the map we
|
|
// produced earlier.
|
|
newStorage.setPaletteIndex(x, y, z, conversion[storage.paletteIndex(x, y, z)])
|
|
}
|
|
}
|
|
}
|
|
*storage = *newStorage
|
|
}
|