Enhance BitStorage

This commit is contained in:
Tnze
2021-12-14 14:12:26 +08:00
parent f2c0c9e4a8
commit fe6fc3dc7b
2 changed files with 85 additions and 27 deletions

View File

@ -5,47 +5,64 @@ import (
"math" "math"
) )
// BitStorage implement the compacted data array used in chunk storage. const indexOutOfBounds = "index out of bounds"
// https://wiki.vg/Chunk_Format const valueOutOfBounds = "value out of bounds"
// This implement the format since Minecraft 1.16
// BitStorage implement the compacted data array used in chunk storage and heightmaps.
// You can think of this as a []intN whose N is called "bits" in NewBitStorage.
// For more info, see: https://wiki.vg/Chunk_Format
// This is implementation of the format since Minecraft 1.16
type BitStorage struct { type BitStorage struct {
data []uint64 data []uint64
mask uint64 mask uint64
bits, size int bits, length int
valuesPerLong int valuesPerLong int
} }
// NewBitStorage create a new BitStorage. // NewBitStorage create a new BitStorage. Return nil if bits == 0.
// bits is the number of bits per value. //
// size is the number of values. // The "bits" is the number of bits per value, which can be calculated by math/bits.Len()
// arrl is optional data for initializing. // The "length" is the number of values.
// It's length must match the bits and size if it's not nil. // The "data" is optional for initializing. Panic if data != nil && len(data) != BitStorageSize(bits, length).
func NewBitStorage(bits, size int, arrl []uint64) (b *BitStorage) { func NewBitStorage(bits, length int, data []uint64) (b *BitStorage) {
if bits == 0 {
return nil
}
b = &BitStorage{ b = &BitStorage{
mask: 1<<bits - 1, mask: 1<<bits - 1,
bits: bits, bits: bits,
size: size, length: length,
valuesPerLong: 64 / bits, valuesPerLong: 64 / bits,
} }
dataLen := (size + b.valuesPerLong - 1) / b.valuesPerLong dataLen := BitStorageSize(bits, length)
if arrl != nil { if data != nil {
if len(arrl) != dataLen { if len(data) != dataLen {
panic(initBitStorageErr{ArrlLen: len(arrl), WantLen: dataLen}) panic(newBitStorageErr{ArrlLen: len(data), WantLen: dataLen})
} }
b.data = arrl b.data = data
} else { } else {
b.data = make([]uint64, dataLen) b.data = make([]uint64, dataLen)
} }
return return
} }
type initBitStorageErr struct { // BitStorageSize calculate how many uint64 is needed for given bits and length.
func BitStorageSize(bits, length int) (size int) {
if bits == 0 {
return 0
}
valuesPerLong := 64 / bits
return (length + valuesPerLong - 1) / valuesPerLong
}
type newBitStorageErr struct {
ArrlLen int ArrlLen int
WantLen int WantLen int
} }
func (i initBitStorageErr) Error() string { func (i newBitStorageErr) Error() string {
return fmt.Sprintf("invalid length given for storage, got: %d but expected: %d", i.ArrlLen, i.WantLen) return fmt.Sprintf("invalid length given for storage, got: %d but expected: %d", i.ArrlLen, i.WantLen)
} }
@ -57,10 +74,13 @@ func (b *BitStorage) calcIndex(n int) (c, o int) {
// Swap sets v into [i], and return the previous [i] value. // Swap sets v into [i], and return the previous [i] value.
func (b *BitStorage) Swap(i, v int) (old int) { func (b *BitStorage) Swap(i, v int) (old int) {
if i < 0 || i > b.size-1 || if b == nil || i < 0 || i > b.length-1 {
v < 0 || uint64(v) > b.mask { panic(indexOutOfBounds)
panic("out of bounds")
} }
if v < 0 || uint64(v) > b.mask {
panic(valueOutOfBounds)
}
c, offset := b.calcIndex(i) c, offset := b.calcIndex(i)
l := b.data[c] l := b.data[c]
old = int(l >> offset & b.mask) old = int(l >> offset & b.mask)
@ -68,12 +88,15 @@ func (b *BitStorage) Swap(i, v int) (old int) {
return return
} }
// Set sets v into [i] // Set sets v into [i].
func (b *BitStorage) Set(i, v int) { func (b *BitStorage) Set(i, v int) {
if i < 0 || i > b.size-1 || if b == nil || i < 0 || i > b.length-1 {
v < 0 || uint64(v) > b.mask { panic(indexOutOfBounds)
panic("out of bounds")
} }
if v < 0 || uint64(v) > b.mask {
panic(valueOutOfBounds)
}
c, offset := b.calcIndex(i) c, offset := b.calcIndex(i)
l := b.data[c] l := b.data[c]
b.data[c] = l&(b.mask<<offset^math.MaxUint64) | (uint64(v)&b.mask)<<offset b.data[c] = l&(b.mask<<offset^math.MaxUint64) | (uint64(v)&b.mask)<<offset
@ -81,10 +104,27 @@ func (b *BitStorage) Set(i, v int) {
// Get gets [i] value. // Get gets [i] value.
func (b *BitStorage) Get(i int) int { func (b *BitStorage) Get(i int) int {
if i < 0 || i > b.size-1 { if b == nil || i < 0 || i > b.length-1 {
panic("out of bounds") panic(indexOutOfBounds)
} }
c, offset := b.calcIndex(i) c, offset := b.calcIndex(i)
l := b.data[c] l := b.data[c]
return int(l >> offset & b.mask) return int(l >> offset & b.mask)
} }
// Len is the number of stored values.
func (b *BitStorage) Len() int {
if b == nil {
return 0
}
return b.length
}
// Longs return the underling array of uint64 for encoding/decoding.
func (b *BitStorage) Longs() []uint64 {
if b == nil {
return []uint64{}
}
return b.data
}

View File

@ -1,6 +1,8 @@
package save package save
import ( import (
pk "github.com/Tnze/go-mc/net/packet"
"math/bits"
"reflect" "reflect"
"testing" "testing"
) )
@ -26,3 +28,19 @@ func TestBitStorage_Set(t *testing.T) {
t.Errorf("Encode error, got %v but expected: %v", bs.data, data) t.Errorf("Encode error, got %v but expected: %v", bs.data, data)
} }
} }
func ExampleNewBitStorage_heightmaps() {
// Create a BitStorage
bs := NewBitStorage(bits.Len(256), 16*16, nil)
// Fill your data
for i := 0; i < 16; i++ {
for j := 0; j < 16; j++ {
bs.Set(i*16+j, 0)
}
}
// Encode as NBT, and this is ready for packet.Marshal
type HeightMaps struct {
MotionBlocking []uint64 `nbt:"MOTION_BLOCKING"`
}
_ = pk.NBT(HeightMaps{bs.Longs()})
}