func (chunk *Chunk) Compact() {
	for i, sub := range chunk.sub {
		if sub == nil {
			continue
		}
		sub.compact()
		if len(sub.storages) == 0 {
			chunk.sub[i] = nil
		}
	}
}
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.
package chunk
import (
	"bytes"
	"github.com/df-mc/dragonfly/server/block/cube"
	"sync"
)
const (
	MaxSubChunkIndex = (cube.MaxY >> 4) - minSubChunkY
	minSubChunkY  = cube.MinY >> 4
	subChunkCount = MaxSubChunkIndex + 1
)
type Chunk struct {
	sync.Mutex
	air uint32
	sub [subChunkCount]*SubChunk
	biomes [256]uint8
	blockEntities map[cube.Pos]map[string]interface{}
}
func New(airRuntimeID uint32) *Chunk {
	return &Chunk{air: airRuntimeID, blockEntities: make(map[cube.Pos]map[string]interface{})}
}
func (chunk *Chunk) Sub() []*SubChunk {
	return chunk.sub[:]
}
func (chunk *Chunk) BiomeID(x, z uint8) uint8 {
	return chunk.biomes[columnOffset(x, z)]
}
func (chunk *Chunk) SetBiomeID(x, z, biomeID uint8) {
	chunk.biomes[columnOffset(x, z)] = biomeID
}
func (chunk *Chunk) Light(x uint8, y int16, z uint8) uint8 {
	sub := chunk.subChunk(y)
	if sub == nil {
		return 15
	}
	return sub.Light(x&15, uint8(y&15), z&15)
}
func (chunk *Chunk) SkyLight(x uint8, y int16, z uint8) uint8 {
	sub := chunk.subChunk(y)
	if sub == nil {
		return 15
	}
	return sub.SkyLightAt(x&15, uint8(y&15), z&15)
}
func (chunk *Chunk) RuntimeID(x uint8, y int16, z uint8, layer uint8) uint32 {
	sub := chunk.subChunk(y)
	if sub == nil {
		return chunk.air
	}
	if uint8(len(sub.storages)) <= layer {
		return sub.air
	}
	return sub.storages[layer].RuntimeID(x, uint8(y), z)
}
var fullSkyLight [2048]byte
func init() {
	b := bytes.Repeat([]byte{0xff}, 2048)
	copy(fullSkyLight[:], b)
}
func (chunk *Chunk) SetRuntimeID(x uint8, y int16, z uint8, layer uint8, runtimeID uint32) {
	i := subIndex(y)
	sub := chunk.sub[i]
	if sub == nil {
		sub = NewSubChunk(chunk.air)
		sub.skyLight = fullSkyLight
		chunk.sub[i] = sub
	}
	if len(sub.storages) < int(layer+1) && runtimeID == chunk.air {
		return
	}
	sub.Layer(layer).SetRuntimeID(x, uint8(y), z, runtimeID)
}
func (chunk *Chunk) HighestLightBlocker(x, z uint8) int16 {
	for index := int16(MaxSubChunkIndex); index >= 0; index-- {
		sub := chunk.sub[index]
		if sub == nil || len(sub.storages) == 0 {
			continue
		}
		for y := 15; y >= 0; y-- {
			totalY := int16(y) | subY(index)
			if FilteringBlocks[sub.storages[0].RuntimeID(x, uint8(totalY), z)] == 15 {
				return totalY
			}
		}
	}
	return cube.MinY
}
func (chunk *Chunk) HighestBlock(x, z uint8) int16 {
	for index := int16(MaxSubChunkIndex); index >= 0; index-- {
		sub := chunk.sub[index]
		if sub == nil || len(sub.storages) == 0 {
			continue
		}
		for y := 15; y >= 0; y-- {
			totalY := int16(y) | subY(index)
			rid := sub.storages[0].RuntimeID(x, uint8(totalY), z)
			if rid != chunk.air {
				return totalY
			}
		}
	}
	return cube.MinY
}
func (chunk *Chunk) SetBlockNBT(pos cube.Pos, data map[string]interface{}) {
	if data == nil {
		delete(chunk.blockEntities, pos)
		return
	}
	chunk.blockEntities[pos] = data
}
func (chunk *Chunk) BlockNBT() map[cube.Pos]map[string]interface{} {
	return chunk.blockEntities
}