simple conversion between save.Chunk and level.Chunk

This commit is contained in:
Tnze
2021-12-20 15:32:44 +08:00
parent 5bc8513039
commit 5acd7f73c5
7 changed files with 376 additions and 189 deletions

View File

@ -4,12 +4,12 @@
package block
import (
"math"
"math/bits"
)
// BitsPerBlock indicates how many bits are needed to represent all possible
// block states. This value is used to determine the size of the global palette.
var BitsPerBlock = int(math.Ceil(math.Log2(float64(len(StateID)))))
var BitsPerBlock = bits.Len(uint(len(StateID)))
// ID describes the numeric ID of a block.
type ID uint32

View File

@ -24,12 +24,12 @@ const (
package block
import (
"math"
"math/bits"
)
// BitsPerBlock indicates how many bits are needed to represent all possible
// block states. This value is used to determine the size of the global palette.
var BitsPerBlock = int(math.Ceil(math.Log2(float64(len(StateID)))))
var BitsPerBlock = bits.Len(uint(len(StateID)))
// ID describes the numeric ID of a block.
type ID uint32

View File

@ -3,6 +3,9 @@ package main
import (
_ "embed"
"github.com/Tnze/go-mc/chat"
"github.com/Tnze/go-mc/level"
"github.com/Tnze/go-mc/save"
"github.com/Tnze/go-mc/save/region"
"github.com/Tnze/go-mc/server"
"image"
_ "image/png"
@ -21,14 +24,9 @@ func main() {
if err != nil {
log.Fatalf("Set server info error: %v", err)
}
defaultDimension := server.NewSimpleDim(16)
chunk00 := server.EmptyChunk(16)
for s := 0; s < 16; s++ {
for i := 0; i < 16*16; i++ {
chunk00.Sections[s].SetBlock(i, 1)
}
}
defaultDimension.LoadChunk(server.ChunkPos{X: 0, Z: 0}, chunk00)
defaultDimension := server.NewSimpleDim(256)
chunk00 := level.ChunkFromSave(readChunk00(), 256)
defaultDimension.LoadChunk(level.ChunkPos{X: 0, Z: 0}, chunk00)
s := server.Server{
ListPingHandler: serverInfo,
LoginHandler: &server.MojangLoginHandler{
@ -61,3 +59,22 @@ func readIcon() image.Image {
}
return icon
}
func readChunk00() *save.Chunk {
r, err := region.Open("./save/testdata/region/r.0.0.mca")
if err != nil {
panic(err)
}
defer r.Close()
var c save.Chunk
data, err := r.ReadSector(0, 0)
if err != nil {
panic(err)
}
err = c.Load(data)
if err != nil {
panic(err)
}
return &c
}

View File

@ -2,9 +2,10 @@ package level
import (
"fmt"
pk "github.com/Tnze/go-mc/net/packet"
"io"
"math"
pk "github.com/Tnze/go-mc/net/packet"
)
const indexOutOfBounds = "index out of bounds"

264
level/chunk.go Normal file
View File

@ -0,0 +1,264 @@
package level
import (
"bytes"
"io"
"math/bits"
"strings"
"sync"
"unsafe"
"github.com/Tnze/go-mc/data/block"
pk "github.com/Tnze/go-mc/net/packet"
"github.com/Tnze/go-mc/save"
)
type ChunkPos struct{ X, Z int }
type Chunk struct {
sync.Mutex
Sections []Section
HeightMaps HeightMaps
}
type HeightMaps struct {
MotionBlocking *BitStorage
WorldSurface *BitStorage
}
func EmptyChunk(secs int) *Chunk {
sections := make([]Section, secs)
for i := range sections {
sections[i] = Section{
blockCount: 0,
States: NewStatesPaletteContainer(16*16*16, 0),
Biomes: NewBiomesPaletteContainer(4*4*4, 0),
}
}
return &Chunk{
Sections: sections,
HeightMaps: HeightMaps{
MotionBlocking: NewBitStorage(bits.Len(uint(secs)*16), 16*16, nil),
},
}
}
var biomesIDs = map[string]int{"ocean": 0,
"deep_ocean": 24,
"frozen_ocean": 10,
"deep_frozen_ocean": 50,
"cold_ocean": 46,
"deep_cold_ocean": 49,
"lukewarm_ocean": 45,
"deep_lukewarm_ocean": 48,
"warm_ocean": 44,
"river": 7,
"frozen_river": 11,
"beach": 16,
"stony_shore": 25,
"snowy_beach": 26,
"forest": 4,
"flower_forest": 132,
"birch_forest": 27,
"old_growth_birch_forest": 155,
"dark_forest": 29,
"jungle": 21,
"sparse_jungle": 23,
"bamboo_jungle": 168,
"taiga": 5,
"snowy_taiga": 30,
"old_growth_pine_taiga": 32,
"old_growth_spruce_taiga": 160,
"mushroom_fields": 14,
"swamp": 6,
"savanna": 35,
"savanna_plateau": 36,
"windswept_savanna": 163,
"plains": 1,
"sunflower_plains": 129,
"desert": 2,
"snowy_plains": 12,
"ice_spikes": 140,
"windswept_hills": 3,
"windswept_forest": 34,
"windswept_gravelly_hills": 131,
"badlands": 37,
"wooded_badlands": 38,
"eroded_badlands": 165,
"dripstone_caves": 174,
"lush_caves": 175,
"nether_wastes": 8,
"crimson_forest": 171,
"warped_forest": 172,
"soul_sand_valley": 170,
"basalt_deltas": 173,
"the_end": 9,
"small_end_islands": 40,
"end_midlands": 41,
"end_highlands": 42,
"end_barrens": 43,
"the_void": 127,
"meadow": 177,
"grove": 178,
"snowy_slopes": 179,
"frozen_peaks": 180,
"jagged_peaks": 181,
"stony_peaks": 182,
}
func ChunkFromSave(c *save.Chunk, secs int) *Chunk {
sections := make([]Section, secs)
for _, v := range c.Sections {
var blockCount int16
stateData := *(*[]uint64)((unsafe.Pointer)(&v.BlockStates.Data))
statePalette := v.BlockStates.Palette
stateRawPalette := make([]int, len(statePalette))
for i, v := range statePalette {
// TODO: Consider the properties of block, not only index the block name
stateRawPalette[i] = int(stateIDs[strings.TrimPrefix(v.Name, "minecraft:")])
if v.Name != "minecraft:air" {
blockCount++
}
}
biomesData := *(*[]uint64)((unsafe.Pointer)(&v.BlockStates.Data))
biomesPalette := v.Biomes.Palette
biomesRawPalette := make([]int, len(biomesPalette))
for i, v := range biomesPalette {
biomesRawPalette[i] = biomesIDs[strings.TrimPrefix(v, "minecraft:")]
}
i := int32(int8(v.Y)) - c.YPos
sections[i].blockCount = blockCount
sections[i].States = NewStatesPaletteContainerWithData(16*16*16, stateData, stateRawPalette)
sections[i].Biomes = NewBiomesPaletteContainerWithData(16*16*16*2, biomesData, biomesRawPalette)
}
for i := range sections {
if sections[i].States == nil {
sections[i] = Section{
blockCount: 0,
States: NewStatesPaletteContainer(16*16*16, 0),
Biomes: NewBiomesPaletteContainer(4*4*4, 0),
}
}
}
motionBlocking := *(*[]uint64)(unsafe.Pointer(&c.Heightmaps.MotionBlocking))
worldSurface := *(*[]uint64)(unsafe.Pointer(&c.Heightmaps.WorldSurface))
return &Chunk{
Sections: sections,
HeightMaps: HeightMaps{
MotionBlocking: NewBitStorage(bits.Len(uint(secs)), 16*16, motionBlocking),
WorldSurface: NewBitStorage(bits.Len(uint(secs)), 16*16, worldSurface),
},
}
}
// TODO: This map should be moved to data/block.
var stateIDs = make(map[string]uint32)
func init() {
for i, v := range block.StateID {
name := block.ByID[v].Name
if _, ok := stateIDs[name]; !ok {
stateIDs[name] = i
}
}
}
func (c *Chunk) WriteTo(w io.Writer) (int64, error) {
data, err := c.Data()
if err != nil {
return 0, err
}
return pk.Tuple{
// Heightmaps
pk.NBT(struct {
MotionBlocking []uint64 `nbt:"MOTION_BLOCKING"`
WorldSurface []uint64 `nbt:"WORLD_SURFACE"`
}{
MotionBlocking: c.HeightMaps.MotionBlocking.Raw(),
WorldSurface: c.HeightMaps.MotionBlocking.Raw(),
}),
pk.ByteArray(data),
pk.VarInt(0), // TODO: Block Entity
&lightData{
SkyLightMask: make(pk.BitSet, (16*16*16-1)>>6+1),
BlockLightMask: make(pk.BitSet, (16*16*16-1)>>6+1),
SkyLight: []pk.ByteArray{},
BlockLight: []pk.ByteArray{},
},
}.WriteTo(w)
}
func (c *Chunk) Data() ([]byte, error) {
var buff bytes.Buffer
for _, section := range c.Sections {
_, err := section.WriteTo(&buff)
if err != nil {
return nil, err
}
}
return buff.Bytes(), nil
}
type Section struct {
blockCount int16
States *PaletteContainer
Biomes *PaletteContainer
}
func (s *Section) GetBlock(i int) int {
return s.States.Get(i)
}
func (s *Section) SetBlock(i int, v int) {
// TODO: Handle cave air and void air
if s.States.Get(i) != 0 {
s.blockCount--
}
if v != 0 {
s.blockCount++
}
s.States.Set(i, v)
}
func (s *Section) WriteTo(w io.Writer) (int64, error) {
return pk.Tuple{
pk.Short(s.blockCount),
s.States,
s.Biomes,
}.WriteTo(w)
}
func (s *Section) ReadFrom(r io.Reader) (int64, error) {
return pk.Tuple{
pk.Short(s.blockCount),
s.States,
s.Biomes,
}.ReadFrom(r)
}
type lightData struct {
SkyLightMask pk.BitSet
BlockLightMask pk.BitSet
SkyLight []pk.ByteArray
BlockLight []pk.ByteArray
}
func bitSetRev(set pk.BitSet) pk.BitSet {
rev := make(pk.BitSet, len(set))
for i := range rev {
rev[i] = ^set[i]
}
return rev
}
func (l *lightData) WriteTo(w io.Writer) (int64, error) {
return pk.Tuple{
pk.Boolean(true), // Trust Edges
l.SkyLightMask,
l.BlockLightMask,
bitSetRev(l.SkyLightMask),
bitSetRev(l.BlockLightMask),
pk.Array(l.SkyLight),
pk.Array(l.BlockLight),
}.WriteTo(w)
}

View File

@ -12,7 +12,7 @@ type state = int
type PaletteContainer struct {
bits int
config func(bits int) palette
config paletteCfg
palette palette
data *BitStorage
}
@ -20,34 +20,63 @@ type PaletteContainer struct {
func NewStatesPaletteContainer(length int, defaultValue state) *PaletteContainer {
return &PaletteContainer{
bits: 0,
config: createStatesPalette,
config: statesCfg{},
palette: &singleValuePalette{v: defaultValue},
data: NewBitStorage(0, length, nil),
}
}
func NewStatesPaletteContainerWithData(length int, data []uint64, palette []int) *PaletteContainer {
n := bits.Len(uint(len(palette)))
return &PaletteContainer{
bits: n,
config: createStatesPalette,
palette: &linearPalette{
values: palette,
func NewStatesPaletteContainerWithData(length int, data []uint64, pat []int) *PaletteContainer {
var p palette
var n int
if len(pat) == 1 {
p = &singleValuePalette{pat[0]}
n = 0
} else {
n = statesCfg{}.bits(bits.Len(uint(len(pat))))
p = &linearPalette{
values: pat,
bits: n,
},
data: NewBitStorage(n, length, data),
}
}
return &PaletteContainer{
bits: n,
config: statesCfg{},
palette: p,
data: NewBitStorage(n, length, data),
}
}
func NewBiomesPaletteContainer(length int, defaultValue state) *PaletteContainer {
return &PaletteContainer{
bits: 0,
config: createBiomesPalette,
config: biomesCfg{},
palette: &singleValuePalette{v: defaultValue},
data: NewBitStorage(0, length, nil),
}
}
func NewBiomesPaletteContainerWithData(length int, data []uint64, pat []int) *PaletteContainer {
var p palette
var n int
if len(pat) == 1 {
p = &singleValuePalette{pat[0]}
n = 0
} else {
n = biomesCfg{}.bits(bits.Len(uint(len(pat))))
p = &linearPalette{
values: pat,
bits: n,
}
}
return &PaletteContainer{
bits: n,
config: biomesCfg{},
palette: p,
data: NewBitStorage(n, length, data),
}
}
func (p *PaletteContainer) Get(i int) state {
return p.palette.value(p.data.Get(i))
}
@ -61,7 +90,7 @@ func (p *PaletteContainer) Set(i int, v state) {
newPalette := PaletteContainer{
bits: vv,
config: p.config,
palette: p.config(vv),
palette: p.config.create(vv),
data: NewBitStorage(vv, oldLen+1, nil),
}
// copy
@ -89,7 +118,7 @@ func (p *PaletteContainer) ReadFrom(r io.Reader) (n int64, err error) {
if err != nil {
return
}
p.palette = p.config(int(bits))
p.palette = p.config.create(int(bits))
nn, err := p.palette.ReadFrom(r)
n += nn
@ -105,7 +134,27 @@ func (p *PaletteContainer) ReadFrom(r io.Reader) (n int64, err error) {
return n, nil
}
func createStatesPalette(bits int) palette {
type paletteCfg interface {
bits(int) int
create(bits int) palette
}
type statesCfg struct{}
func (s statesCfg) bits(bits int) int {
switch bits {
case 0:
return 0
case 1, 2, 3, 4:
return 4
case 5, 6, 7, 8:
return bits
default:
return bits
}
}
func (s statesCfg) create(bits int) palette {
switch bits {
case 0:
return &singleValuePalette{v: -1}
@ -119,7 +168,19 @@ func createStatesPalette(bits int) palette {
}
}
func createBiomesPalette(bits int) palette {
type biomesCfg struct{}
func (b biomesCfg) bits(bits int) int {
switch bits {
case 0:
return 0
case 1, 2, 3:
return bits
default:
return bits
}
}
func (b biomesCfg) create(bits int) palette {
switch bits {
case 0:
return &singleValuePalette{v: -1}

View File

@ -1,18 +1,9 @@
package server
import (
"bytes"
"io"
"math/bits"
"strings"
"sync"
"unsafe"
"github.com/Tnze/go-mc/data/block"
"github.com/Tnze/go-mc/data/packetid"
"github.com/Tnze/go-mc/level"
pk "github.com/Tnze/go-mc/net/packet"
"github.com/Tnze/go-mc/save"
)
type Level interface {
@ -26,160 +17,19 @@ type LevelInfo struct {
HashedSeed uint64
}
type ChunkPos struct{ X, Z int }
type Chunk struct {
sync.Mutex
Sections []Section
HeightMaps *level.BitStorage
}
func EmptyChunk(secs int) *Chunk {
sections := make([]Section, secs)
for i := range sections {
sections[i] = Section{
blockCount: 0,
States: level.NewStatesPaletteContainer(16*16*16, 0),
Biomes: level.NewBiomesPaletteContainer(4*4*4, 0),
}
}
return &Chunk{
Sections: sections,
HeightMaps: level.NewBitStorage(bits.Len(uint(secs)*16), 16*16, nil),
}
}
func ChunkFromSave(c *save.Chunk) *Chunk {
sections := make([]Section, len(c.Sections))
for i := range sections {
data := *(*[]uint64)((unsafe.Pointer)(&c.Sections[i].BlockStates.Data))
palette := c.Sections[i].BlockStates.Palette
rawPalette := make([]int, len(palette))
for i, v := range palette {
// TODO: Consider the properties of block, not only index the block name
rawPalette[i] = int(stateIDs[strings.TrimPrefix(v.Name, "minecraft:")])
}
sections[i].States = level.NewStatesPaletteContainerWithData(16*16*16, data, rawPalette)
}
return &Chunk{
Sections: sections,
HeightMaps: nil,
}
}
// TODO: This map should be moved to data/block.
var stateIDs map[string]uint32
func init() {
for i, v := range block.StateID {
name := block.ByID[v].Name
if _, ok := stateIDs[name]; !ok {
stateIDs[name] = i
}
}
}
func (c *Chunk) WriteTo(w io.Writer) (int64, error) {
data, err := c.Data()
if err != nil {
return 0, err
}
return pk.Tuple{
// Heightmaps
pk.NBT(struct {
MotionBlocking []uint64 `nbt:"MOTION_BLOCKING"`
}{c.HeightMaps.Raw()}),
pk.ByteArray(data),
pk.VarInt(0), // TODO: Block Entity
}.WriteTo(w)
}
func (c *Chunk) Data() ([]byte, error) {
var buff bytes.Buffer
for _, section := range c.Sections {
_, err := section.WriteTo(&buff)
if err != nil {
return nil, err
}
}
return buff.Bytes(), nil
}
type Section struct {
blockCount int16
States *level.PaletteContainer
Biomes *level.PaletteContainer
}
func (s *Section) GetBlock(i int) int {
return s.States.Get(i)
}
func (s *Section) SetBlock(i int, v int) {
// TODO: Handle cave air and void air
if s.States.Get(i) != 0 {
s.blockCount--
}
if v != 0 {
s.blockCount++
}
s.States.Set(i, v)
}
func (s *Section) WriteTo(w io.Writer) (int64, error) {
return pk.Tuple{
pk.Short(s.blockCount),
s.States,
s.Biomes,
}.WriteTo(w)
}
func (s *Section) ReadFrom(r io.Reader) (int64, error) {
return pk.Tuple{
pk.Short(s.blockCount),
s.States,
s.Biomes,
}.ReadFrom(r)
}
type lightData struct {
SkyLightMask pk.BitSet
BlockLightMask pk.BitSet
SkyLight []pk.ByteArray
BlockLight []pk.ByteArray
}
func bitSetRev(set pk.BitSet) pk.BitSet {
rev := make(pk.BitSet, len(set))
for i := range rev {
rev[i] = ^set[i]
}
return rev
}
func (l *lightData) WriteTo(w io.Writer) (int64, error) {
return pk.Tuple{
pk.Boolean(true), // Trust Edges
l.SkyLightMask,
l.BlockLightMask,
bitSetRev(l.SkyLightMask),
bitSetRev(l.BlockLightMask),
pk.Array(l.SkyLight),
pk.Array(l.BlockLight),
}.WriteTo(w)
}
type SimpleDim struct {
numOfSection int
Columns map[ChunkPos]*Chunk
Columns map[level.ChunkPos]*level.Chunk
}
func NewSimpleDim(secs int) *SimpleDim {
return &SimpleDim{
numOfSection: secs,
Columns: make(map[ChunkPos]*Chunk),
Columns: make(map[level.ChunkPos]*level.Chunk),
}
}
func (s *SimpleDim) LoadChunk(pos ChunkPos, c *Chunk) {
func (s *SimpleDim) LoadChunk(pos level.ChunkPos, c *level.Chunk) {
s.Columns[pos] = c
}
@ -197,12 +47,6 @@ func (s *SimpleDim) PlayerJoin(p *Player) {
packetid.ClientboundLevelChunkWithLight,
pk.Int(pos.X), pk.Int(pos.Z),
column,
&lightData{
SkyLightMask: make(pk.BitSet, (16*16*16-1)>>6+1),
BlockLightMask: make(pk.BitSet, (16*16*16-1)>>6+1),
SkyLight: []pk.ByteArray{},
BlockLight: []pk.ByteArray{},
},
)
column.Unlock()
@ -214,7 +58,7 @@ func (s *SimpleDim) PlayerJoin(p *Player) {
err := p.WritePacket(Packet757(pk.Marshal(
packetid.ClientboundPlayerPosition,
pk.Double(0), pk.Double(0), pk.Double(0),
pk.Double(0), pk.Double(143), pk.Double(0),
pk.Float(0), pk.Float(0),
pk.Byte(0),
pk.VarInt(0),