initial commit

This commit is contained in:
AkmalFairuz
2024-12-31 20:57:21 +07:00
commit 0de5415f05
72 changed files with 20340 additions and 0 deletions

119
internal/chunk/chunk.go Normal file
View File

@@ -0,0 +1,119 @@
package chunk
import (
"github.com/df-mc/dragonfly/server/block/cube"
"sync"
)
// Chunk is a segment in the world with a size of 16x16x256 blocks. A chunk contains multiple sub chunks
// and stores other information such as biomes.
// It is not safe to call methods on Chunk simultaneously from multiple goroutines.
type Chunk struct {
sync.Mutex
// r holds the (vertical) range of the Chunk. It includes both the minimum and maximum coordinates.
r cube.Range
// air is the runtime ID of air.
air uint32
// sub holds all sub chunks part of the chunk. The pointers held by the array are nil if no sub chunk is
// allocated at the indices.
sub []*SubChunk
// biomes is an array of biome IDs. There is one biome ID for every column in the chunk.
biomes []*PalettedStorage
}
// New initialises a new chunk and returns it, so that it may be used.
func New(air uint32, r cube.Range) *Chunk {
n := (r.Height() >> 4) + 1
sub, biomes := make([]*SubChunk, n), make([]*PalettedStorage, n)
for i := 0; i < n; i++ {
sub[i] = NewSubChunk(air)
biomes[i] = emptyStorage(0)
}
return &Chunk{r: r, air: air, sub: sub, biomes: biomes}
}
// Range returns the cube.Range of the Chunk as passed to New.
func (chunk *Chunk) Range() cube.Range {
return chunk.r
}
// Sub returns a list of all sub chunks present in the chunk.
func (chunk *Chunk) Sub() []*SubChunk {
return chunk.sub
}
// Block returns the runtime ID of the block at a given x, y and z in a chunk at the given layer. If no
// sub chunk exists at the given y, the block is assumed to be air.
func (chunk *Chunk) Block(x uint8, y int16, z uint8, layer uint8) uint32 {
sub := chunk.subChunk(y)
if sub.Empty() || uint8(len(sub.storages)) <= layer {
return chunk.air
}
return sub.storages[layer].At(x, uint8(y), z)
}
// SetBlock sets the runtime ID of a block at a given x, y and z in a chunk at the given layer. If no
// SubChunk exists at the given y, a new SubChunk is created and the block is set.
func (chunk *Chunk) SetBlock(x uint8, y int16, z uint8, layer uint8, block uint32) {
sub := chunk.sub[chunk.subIndex(y)]
if uint8(len(sub.storages)) <= layer && block == chunk.air {
// Air was set at n layer, but there were less than n layers, so there already was air there.
// Don't do anything with this, just return.
return
}
sub.Layer(layer).Set(x, uint8(y), z, block)
}
// BiomeSub returns a list of all biome sub chunks present in the chunk.
func (chunk *Chunk) BiomeSub() []*PalettedStorage {
return chunk.biomes
}
// Biome returns the biome ID at a specific column in the chunk.
func (chunk *Chunk) Biome(x uint8, y int16, z uint8) uint32 {
return chunk.biomes[chunk.subIndex(y)].At(x, uint8(y), z)
}
// SetBiome sets the biome ID at a specific column in the chunk.
func (chunk *Chunk) SetBiome(x uint8, y int16, z uint8, biome uint32) {
chunk.biomes[chunk.subIndex(y)].Set(x, uint8(y), z, biome)
}
// HighestBlock iterates from the highest non-empty sub chunk downwards to find the Y value of the highest
// non-air block at an x and z. If no blocks are present in the column, 0 is returned.
func (chunk *Chunk) HighestBlock(x, z uint8) int16 {
for index := int16(len(chunk.sub) - 1); index >= 0; index-- {
if sub := chunk.sub[index]; !sub.Empty() {
for y := 15; y >= 0; y-- {
if rid := sub.storages[0].At(x, uint8(y), z); rid != chunk.air {
return int16(y) | chunk.subY(index)
}
}
}
}
return int16(chunk.r[0])
}
// Compact compacts the chunk as much as possible, getting rid of any sub chunks that are empty, and compacts
// all storages in the sub chunks to occupy as little space as possible.
// Compact should be called right before the chunk is saved in order to optimise the storage space.
func (chunk *Chunk) Compact() {
for i := range chunk.sub {
chunk.sub[i].compact()
}
}
// subChunk finds the correct SubChunk in the Chunk by a Y value.
func (chunk *Chunk) subChunk(y int16) *SubChunk {
return chunk.sub[chunk.subIndex(y)]
}
// subIndex returns the sub chunk Y index matching the y value passed.
func (chunk *Chunk) subIndex(y int16) int16 {
return (y - int16(chunk.r[0])) >> 4
}
// subY returns the sub chunk Y value matching the index passed.
func (chunk *Chunk) subY(index int16) int16 {
return (index << 4) + int16(chunk.r[0])
}

145
internal/chunk/decode.go Normal file
View File

@@ -0,0 +1,145 @@
package chunk
import (
"bytes"
"fmt"
"github.com/df-mc/dragonfly/server/block/cube"
)
// NetworkDecode decodes the network serialised data passed into a Chunk if successful. If not, the chunk
// returned is nil and the error non-nil.
// The sub chunk count passed must be that found in the LevelChunk packet.
// noinspection GoUnusedExportedFunction
func NetworkDecode(air uint32, buf *bytes.Buffer, count int, oldFormat bool, r cube.Range, pse Encoding, pe PaletteEncoding) (*Chunk, error) {
var (
c = New(air, r)
err error
)
for i := 0; i < count; i++ {
index := uint8(i)
if oldFormat {
index += 4
}
c.sub[index], err = DecodeSubChunk(air, r, buf, &index, NetworkEncoding, pse, pe)
if err != nil {
return nil, err
}
}
if oldFormat {
// Read the old biomes.
biomes := make([]byte, 256)
if _, err := buf.Read(biomes); err != nil {
return nil, fmt.Errorf("error reading biomes: %w", err)
}
// Make our 2D biomes 3D.
for x := 0; x < 16; x++ {
for z := 0; z < 16; z++ {
id := biomes[(x&15)|(z&15)<<4]
for y := r.Min(); y <= r.Max(); y++ {
c.SetBiome(uint8(x), int16(y), uint8(z), uint32(id))
}
}
}
} else {
var last *PalettedStorage
for i := 0; i < len(c.sub); i++ {
b, err := decodePalettedStorage(buf, NetworkEncoding, pse, BiomePaletteEncoding)
if err != nil {
return nil, err
}
if b == nil {
// b == nil means this paletted storage had the flag pointing to the previous one. It basically means we should
// inherit whatever palette we decoded last.
if i == 0 {
// This should never happen and there is no way to handle this.
return nil, fmt.Errorf("first biome storage pointed to previous one")
}
b = last
} else {
last = b
}
c.biomes[i] = b
}
}
return c, nil
}
// DecodeSubChunk decodes a SubChunk from a bytes.Buffer. The Encoding passed defines how the block storages of the
// SubChunk are decoded.
func DecodeSubChunk(air uint32, r cube.Range, buf *bytes.Buffer, index *byte, e Encoding, pse Encoding, pe PaletteEncoding) (*SubChunk, error) {
ver, err := buf.ReadByte()
if err != nil {
return nil, fmt.Errorf("error reading version: %w", err)
}
sub := NewSubChunk(air)
switch ver {
default:
return nil, fmt.Errorf("unknown sub chunk version %v: can't decode", ver)
case 1:
// Version 1 only has one layer for each sub chunk, but uses the format with palettes.
storage, err := decodePalettedStorage(buf, e, pse, pe)
if err != nil {
return nil, err
}
sub.storages = append(sub.storages, storage)
case 8, 9:
// Version 8 allows up to 256 layers for one sub chunk.
storageCount, err := buf.ReadByte()
if err != nil {
return nil, fmt.Errorf("error reading storage count: %w", err)
}
if ver == 9 {
uIndex, err := buf.ReadByte()
if err != nil {
return nil, fmt.Errorf("error reading subchunk index: %w", err)
}
// The index as written here isn't the actual index of the subchunk within the chunk. Rather, it is the Y
// value of the subchunk. This means that we need to translate it to an index.
*index = uint8(int8(uIndex) - int8(r[0]>>4))
}
sub.storages = make([]*PalettedStorage, storageCount)
for i := byte(0); i < storageCount; i++ {
sub.storages[i], err = decodePalettedStorage(buf, e, pse, pe)
if err != nil {
return nil, err
}
}
}
return sub, nil
}
// decodePalettedStorage decodes a PalettedStorage from a bytes.Buffer. The Encoding passed is used to read either a
// network or disk block storage.
func decodePalettedStorage(buf *bytes.Buffer, e Encoding, pse Encoding, pe PaletteEncoding) (*PalettedStorage, error) {
blockSize, err := buf.ReadByte()
if err != nil {
return nil, fmt.Errorf("error reading block size: %w", err)
}
if e == NetworkEncoding && blockSize&1 != 1 {
e = pse
}
blockSize >>= 1
if blockSize == 0x7f {
return nil, nil
}
size := paletteSize(blockSize)
uint32Count := size.uint32s()
uint32s := make([]uint32, uint32Count)
byteCount := uint32Count * 4
data := buf.Next(byteCount)
if len(data) != byteCount {
return nil, fmt.Errorf("cannot read paletted storage (size=%v) %T: not enough block data present: expected %v bytes, got %v", blockSize, pe, byteCount, len(data))
}
for i := 0; i < uint32Count; i++ {
// Explicitly don't use the binary package to greatly improve performance of reading the uint32s.
uint32s[i] = uint32(data[i*4]) | uint32(data[i*4+1])<<8 | uint32(data[i*4+2])<<16 | uint32(data[i*4+3])<<24
}
p, err := e.DecodePalette(buf, paletteSize(blockSize), pe)
return newPalettedStorage(uint32s, p), err
}

92
internal/chunk/encode.go Normal file
View File

@@ -0,0 +1,92 @@
package chunk
import (
"bytes"
"github.com/df-mc/dragonfly/server/block/cube"
"sync"
)
// pool is used to pool byte buffers used for encoding chunks.
var pool = sync.Pool{
New: func() any {
return bytes.NewBuffer(make([]byte, 0, 1024))
},
}
func NetworkEncode(air uint32, c *Chunk, oldFormat bool, pe PaletteEncoding) ([]byte, error) {
buf := pool.Get().(*bytes.Buffer)
for i := 0; i < len(c.sub); i++ {
_, _ = buf.Write(EncodeSubChunk(c.sub[i], NetworkEncoding, pe, SubChunkVersion8, c.r, i))
}
if oldFormat {
biomes := make([]byte, 256)
// Make our 3D biomes 2D.
for x := uint8(0); x < 16; x++ {
for z := uint8(0); z < 16; z++ {
biomes[(x&15)|(z&15)<<4] = byte(c.Biome(x, c.HighestBlock(x, z), z))
}
}
_, _ = buf.Write(biomes)
} else {
_, _ = buf.Write(EncodeBiomes(c, NetworkEncoding))
}
return buf.Bytes(), nil
}
// EncodeSubChunk encodes a sub-chunk from a chunk into bytes. An Encoding may be passed to encode either for network or
// disk purposed, the most notable difference being that the network encoding generally uses varints and no NBT.
func EncodeSubChunk(s *SubChunk, e Encoding, pe PaletteEncoding, subChunkVer subChunkVersion, r cube.Range, ind int) []byte {
buf := pool.Get().(*bytes.Buffer)
defer func() {
buf.Reset()
pool.Put(buf)
}()
subChunkVer.EncodeHeader(buf, s, r, ind)
for _, storage := range s.storages {
encodePalettedStorage(buf, storage, nil, e, pe)
}
sub := make([]byte, buf.Len())
_, _ = buf.Read(sub)
return sub
}
// EncodeBiomes encodes the biomes of a chunk into bytes. An Encoding may be passed to encode either for network or
// disk purposed, the most notable difference being that the network encoding generally uses varints and no NBT.
func EncodeBiomes(c *Chunk, e Encoding) []byte {
buf := pool.Get().(*bytes.Buffer)
defer func() {
buf.Reset()
pool.Put(buf)
}()
var previous *PalettedStorage
for _, b := range c.biomes {
encodePalettedStorage(buf, b, previous, e, BiomePaletteEncoding)
previous = b
}
biomes := make([]byte, buf.Len())
_, _ = buf.Read(biomes)
return biomes
}
// encodePalettedStorage encodes a PalettedStorage into a bytes.Buffer. The Encoding passed is used to write the Palette
// of the PalettedStorage.
func encodePalettedStorage(buf *bytes.Buffer, storage, previous *PalettedStorage, e Encoding, pe PaletteEncoding) {
if storage.Equal(previous) {
_, _ = buf.Write([]byte{0x7f<<1 | e.Network()})
return
}
b := make([]byte, len(storage.indices)*4+1)
b[0] = byte(storage.bitsPerIndex<<1) | e.Network()
for i, v := range storage.indices {
// Explicitly don't use the binary package to greatly improve performance of writing the uint32s.
b[i*4+1], b[i*4+2], b[i*4+3], b[i*4+4] = byte(v), byte(v>>8), byte(v>>16), byte(v>>24)
}
_, _ = buf.Write(b)
e.EncodePalette(buf, storage.palette, pe)
}

193
internal/chunk/encoding.go Normal file
View File

@@ -0,0 +1,193 @@
package chunk
import (
"bytes"
"encoding/binary"
"fmt"
"github.com/akmalfairuz/legacy-version/mapping"
"strings"
"github.com/df-mc/dragonfly/server/block/cube"
"github.com/df-mc/worldupgrader/blockupgrader"
"github.com/sandertv/gophertunnel/minecraft/nbt"
"github.com/sandertv/gophertunnel/minecraft/protocol"
)
const (
// SubChunkVersion is the current version of the written sub chunks, specifying the format they are
// written on disk and over network.
SubChunkVersion = 9
)
type (
// Encoding is an encoding type used for Chunk encoding. Implementations of this interface are DiskEncoding and
// NetworkEncoding, which can be used to encode a Chunk to an intermediate disk or network representation respectively.
Encoding interface {
EncodePalette(buf *bytes.Buffer, p *Palette, e PaletteEncoding)
DecodePalette(buf *bytes.Buffer, blockSize paletteSize, e PaletteEncoding) (*Palette, error)
Network() byte
}
// PaletteEncoding is an encoding type used for Chunk encoding. It is used to encode different types of palettes
// (for example, blocks or biomes) differently.
PaletteEncoding interface {
Encode(buf *bytes.Buffer, v uint32)
Decode(buf *bytes.Buffer) (uint32, error)
}
// Encoding is an encoding type used for Chunk encoding. Implementations of this interface are DiskEncoding and
// NetworkEncoding, which can be used to encode a Chunk to an intermediate disk or network representation respectively.
subChunkVersion interface {
EncodeHeader(buf *bytes.Buffer, s *SubChunk, r cube.Range, ind int)
}
)
var (
// SubChunkVersion9 subChunkVersion9
SubChunkVersion9 subChunkVersion9
// SubChunkVersion8 subChunkVersion8
SubChunkVersion8 subChunkVersion8
// NetworkEncoding is the Encoding used for sending a Chunk over network. It does not use NBT and writes varints.
NetworkEncoding networkEncoding
// BiomePaletteEncoding is the paletteEncoding used for encoding a palette of biomes.
BiomePaletteEncoding biomePaletteEncoding
)
// networkEncoding implements the Chunk encoding for sending over network.
type networkEncoding struct{}
func (networkEncoding) Network() byte { return 1 }
func (networkEncoding) EncodePalette(buf *bytes.Buffer, p *Palette, _ PaletteEncoding) {
if p.size != 0 {
_ = protocol.WriteVarint32(buf, int32(p.Len()))
}
for _, val := range p.values {
_ = protocol.WriteVarint32(buf, int32(val))
}
}
func (networkEncoding) DecodePalette(buf *bytes.Buffer, blockSize paletteSize, _ PaletteEncoding) (*Palette, error) {
var paletteCount int32 = 1
if blockSize != 0 {
if err := protocol.Varint32(buf, &paletteCount); err != nil {
return nil, fmt.Errorf("error reading palette entry count: %w", err)
}
if paletteCount <= 0 {
return nil, fmt.Errorf("invalid palette entry count %v", paletteCount)
}
}
blocks, temp := make([]uint32, paletteCount), int32(0)
for i := int32(0); i < paletteCount; i++ {
if err := protocol.Varint32(buf, &temp); err != nil {
return nil, fmt.Errorf("error decoding palette entry: %w", err)
}
blocks[i] = uint32(temp)
}
return &Palette{values: blocks, size: blockSize}, nil
}
// biomePaletteEncoding implements the encoding of biome palettes to disk.
type biomePaletteEncoding struct{}
func (biomePaletteEncoding) Encode(buf *bytes.Buffer, v uint32) {
_ = binary.Write(buf, binary.LittleEndian, v)
}
func (biomePaletteEncoding) Decode(buf *bytes.Buffer) (uint32, error) {
var v uint32
return v, binary.Read(buf, binary.LittleEndian, &v)
}
// BlockPaletteEncoding implements the encoding of block palettes to disk.
type BlockPaletteEncoding struct {
block mapping.Block
version int32
}
// NewBlockPaletteEncoding returns a new BlockPaletteEncoding using the block and version passed.
func NewBlockPaletteEncoding(block mapping.Block, version int32) BlockPaletteEncoding {
return BlockPaletteEncoding{block: block, version: version}
}
func (b BlockPaletteEncoding) Encode(buf *bytes.Buffer, v uint32) {
// Get the block state registered with the runtime IDs we have in the palette of the block storage
// as we need the name and data value to store.
state, _ := b.block.RuntimeIDToState(v)
_ = nbt.NewEncoderWithEncoding(buf, nbt.LittleEndian).Encode(blockupgrader.BlockState{Name: state.Name, Properties: state.Properties, Version: b.version})
}
func (b BlockPaletteEncoding) Decode(buf *bytes.Buffer) (uint32, error) {
var e blockupgrader.BlockState
if err := nbt.NewDecoderWithEncoding(buf, nbt.LittleEndian).Decode(&e); err != nil {
return 0, fmt.Errorf("error decoding block palette entry: %w", err)
}
v, ok := b.block.StateToRuntimeID(e)
if !ok {
return 0, fmt.Errorf("cannot get runtime ID of block state %v{%+v}", e.Name, e.Properties)
}
return v, nil
}
// NewNetworkPersistentEncoding returns a new NetworkPersistentEncoding using the block and version passed.
func NewNetworkPersistentEncoding(block mapping.Block, version int32) NetworkPersistentEncoding {
return NetworkPersistentEncoding{block: block, version: version}
}
// NetworkPersistentEncoding implements the Chunk encoding for sending over network with a persistent palette.
type NetworkPersistentEncoding struct {
block mapping.Block
version int32
}
func (n NetworkPersistentEncoding) Network() byte { return 1 }
func (n NetworkPersistentEncoding) EncodePalette(buf *bytes.Buffer, p *Palette, _ PaletteEncoding) {
if p.size != 0 {
_ = protocol.WriteVarint32(buf, int32(p.Len()))
}
enc := nbt.NewEncoderWithEncoding(buf, nbt.NetworkLittleEndian)
for _, val := range p.values {
state, _ := n.block.RuntimeIDToState(val)
_ = enc.Encode(blockupgrader.BlockState{Name: strings.TrimPrefix("minecraft:", state.Name), Properties: state.Properties, Version: n.version})
}
}
func (n NetworkPersistentEncoding) DecodePalette(buf *bytes.Buffer, blockSize paletteSize, _ PaletteEncoding) (*Palette, error) {
var paletteCount int32 = 1
if blockSize != 0 {
err := protocol.Varint32(buf, &paletteCount)
if err != nil {
panic(err)
}
if paletteCount <= 0 {
return nil, fmt.Errorf("invalid palette entry count %v", paletteCount)
}
}
blocks := make([]blockupgrader.BlockState, paletteCount)
dec := nbt.NewDecoderWithEncoding(buf, nbt.NetworkLittleEndian)
for i := int32(0); i < paletteCount; i++ {
if err := dec.Decode(&blocks[i]); err != nil {
return nil, fmt.Errorf("error decoding block state: %w", err)
}
}
var ok bool
palette, temp := newPalette(blockSize, make([]uint32, paletteCount)), uint32(0)
for i, b := range blocks {
temp, ok = n.block.StateToRuntimeID(blockupgrader.BlockState{Name: "minecraft:" + b.Name, Properties: b.Properties, Version: n.version})
if !ok {
return nil, fmt.Errorf("cannot get runtime ID of block state %v{%+v}", b.Name, b.Properties)
}
palette.values[i] = temp
}
return palette, nil
}
type subChunkVersion8 struct{}
func (subChunkVersion8) EncodeHeader(buf *bytes.Buffer, s *SubChunk, _ cube.Range, _ int) {
_, _ = buf.Write([]byte{8, byte(len(s.storages))})
}
type subChunkVersion9 struct{}
func (subChunkVersion9) EncodeHeader(buf *bytes.Buffer, s *SubChunk, r cube.Range, ind int) {
_, _ = buf.Write([]byte{SubChunkVersion, byte(len(s.storages)), uint8(ind + (r[0] >> 4))})
}

131
internal/chunk/palette.go Normal file
View File

@@ -0,0 +1,131 @@
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
}

View File

@@ -0,0 +1,200 @@
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
}

View File

@@ -0,0 +1,66 @@
package chunk
// SubChunk is a cube of blocks located in a chunk. It has a size of 16x16x16 blocks and forms part of a stack
// that forms a Chunk.
type SubChunk struct {
air uint32
storages []*PalettedStorage
blockLight []uint8
skyLight []uint8
}
// NewSubChunk creates a new sub chunk. All sub chunks should be created through this function
func NewSubChunk(air uint32) *SubChunk {
return &SubChunk{air: air}
}
// Empty checks if the SubChunk is considered empty. This is the case if the SubChunk has 0 block storages or if it has
// a single one that is completely filled with air.
func (sub *SubChunk) Empty() bool {
return len(sub.storages) == 0 || (len(sub.storages) == 1 && len(sub.storages[0].palette.values) == 1 && sub.storages[0].palette.values[0] == sub.air)
}
// Layer returns a certain block storage/layer from a sub chunk. If no storage at the layer exists, the layer
// is created, as well as all layers between the current highest layer and the new highest layer.
func (sub *SubChunk) Layer(layer uint8) *PalettedStorage {
for uint8(len(sub.storages)) <= layer {
// Keep appending to storages until the requested layer is achieved. Makes working with new layers
// much easier.
sub.storages = append(sub.storages, emptyStorage(sub.air))
}
return sub.storages[layer]
}
// Layers returns all layers in the sub chunk. This method may also return an empty slice.
func (sub *SubChunk) Layers() []*PalettedStorage {
return sub.storages
}
// Block returns the runtime ID of the block located at the given X, Y and Z. X, Y and Z must be in a
// range of 0-15.
func (sub *SubChunk) Block(x, y, z byte, layer uint8) uint32 {
if uint8(len(sub.storages)) <= layer {
return sub.air
}
return sub.storages[layer].At(x, y, z)
}
// SetBlock sets the given block runtime ID at the given X, Y and Z. X, Y and Z must be in a range of 0-15.
func (sub *SubChunk) SetBlock(x, y, z byte, layer uint8, block uint32) {
sub.Layer(layer).Set(x, y, z, block)
}
// Compact cleans the garbage from all block storages that sub chunk contains, so that they may be
// cleanly written to a database.
func (sub *SubChunk) compact() {
newStorages := make([]*PalettedStorage, 0, len(sub.storages))
for _, storage := range sub.storages {
storage.compact()
if len(storage.palette.values) == 1 && storage.palette.values[0] == sub.air {
// If the palette has only air in it, it means the storage is empty, so we can ignore it.
continue
}
newStorages = append(newStorages, storage)
}
sub.storages = newStorages
}