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 }