write mca region file (test and optimization)

This commit is contained in:
Tnze
2019-08-03 14:34:51 +08:00
parent 0a670a2824
commit 19fd388cd9
2 changed files with 81 additions and 73 deletions

View File

@ -5,6 +5,7 @@ import (
"errors" "errors"
"io" "io"
"os" "os"
"time"
) )
type Region struct { type Region struct {
@ -67,26 +68,6 @@ func OpenRegion(name string) (r *Region, err error) {
} }
func (r *Region) Close() error { func (r *Region) Close() error {
_, err := r.f.Seek(0, 0)
if err != nil {
return err
}
// read the offsets
err = binary.Write(r.f, binary.BigEndian, &r.offsets)
if err != nil {
_ = r.f.Close()
return err
}
r.sectors[0] = true
// read the timestamps
err = binary.Write(r.f, binary.BigEndian, &r.timestamps)
if err != nil {
_ = r.f.Close()
return err
}
return r.f.Close() return r.f.Close()
} }
@ -119,42 +100,43 @@ func (r *Region) ReadSector(x, y int) (data []byte, err error) {
} }
func (r *Region) WriteSector(x, y int, data []byte) error { func (r *Region) WriteSector(x, y int, data []byte) error {
sectorsNeeded := int32(len(data)+4)/4096 + 1 need := int32(len(data)+4)/4096 + 1
sectorNumber, sectorsAllocated := sectorLoc(r.offsets[x][y]) n, now := sectorLoc(r.offsets[x][y])
// maximum chunk size is 1MB // maximum chunk size is 1MB
if sectorsNeeded >= 256 { if need >= 256 {
return errors.New("data too large") return errors.New("data too large")
} }
if sectorNumber != 0 && sectorsAllocated == sectorsNeeded { if n != 0 && now == need {
// we can simply overwrite the old sectors // we can simply overwrite the old sectors
} else { } else {
// we need to allocate new sectors // we need to allocate new sectors
// mark the sectors previously used for this chunk as free // mark the sectors previously used for this chunk as free
for i := int32(0); i < sectorsAllocated; i++ { for i := int32(0); i < now; i++ {
r.sectors[sectorNumber+i] = false r.sectors[n+i] = false
} }
// scan for a free space large enough to store this chunk // scan for a free space large enough to store this chunk
sectorNumber = 0 n = r.findSpace(need)
for i := int32(0); i < sectorsNeeded; i++ {
if r.sectors[sectorNumber+i] {
sectorNumber += i + 1
i = -1
}
}
// mark the sectors previously used for this chunk as used // mark the sectors previously used for this chunk as used
sectorsAllocated = sectorsNeeded now = need
for i := int32(0); i < sectorsNeeded; i++ { for i := int32(0); i < need; i++ {
r.sectors[sectorNumber+i] = true r.sectors[n+i] = true
}
r.offsets[x][y] = (n << 8) | (need & 0xFF)
// update file head
err := r.setHead(x, y, uint32(r.offsets[x][y]), uint32(time.Now().Unix()))
if err != nil {
return err
} }
r.offsets[x][y] = (sectorNumber << 8) | (sectorsNeeded & 0xFF)
} }
_, err := r.f.Seek(4096*int64(sectorNumber), 0) _, err := r.f.Seek(4096*int64(n), 0)
if err != nil { if err != nil {
return err return err
} }
@ -163,6 +145,7 @@ func (r *Region) WriteSector(x, y int, data []byte) error {
if err != nil { if err != nil {
return err return err
} }
//data //data
_, err = r.f.Write(data) _, err = r.f.Write(data)
if err != nil { if err != nil {
@ -171,3 +154,31 @@ func (r *Region) WriteSector(x, y int, data []byte) error {
return nil return nil
} }
func (r *Region) findSpace(need int32) (n int32) {
for i := int32(0); i < need; i++ {
if r.sectors[n+i] {
n += i + 1
i = -1
}
}
return
}
func (r *Region) setHead(x, y int, offset, timestamp uint32) (err error) {
var buf [4]byte
binary.BigEndian.PutUint32(buf[:], offset)
_, err = r.f.WriteAt(buf[:], 4*(int64(x)*32+int64(y)))
if err != nil {
return
}
binary.BigEndian.PutUint32(buf[:], timestamp)
_, err = r.f.WriteAt(buf[:], 4096+4*(int64(x)*32+int64(y)))
if err != nil {
return
}
return
}

View File

@ -3,7 +3,7 @@ package region
import ( import (
"bytes" "bytes"
"compress/zlib" "compress/zlib"
"io/ioutil" "github.com/Tnze/go-mc/nbt"
"testing" "testing"
) )
@ -26,46 +26,43 @@ func TestReadRegion(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer r.Close()
s, err := r.ReadSector(In(31, 0)) for i := 0; i < 32; i++ {
if err != nil { for j := 0; j < 32; j++ {
t.Fatal(err) s, err := r.ReadSector(In(i, j))
if err != nil {
continue
}
r, err := zlib.NewReader(bytes.NewReader(s[1:]))
if err != nil {
t.Error(err)
}
var b interface{}
err = nbt.NewDecoder(r).Decode(&b)
if err != nil {
t.Error(err)
}
//t.Log(b)
}
} }
reader, err := zlib.NewReader(bytes.NewReader(s[1:]))
if err != nil {
t.Fatal(err)
}
s, err = ioutil.ReadAll(reader)
if err != nil {
t.Fatal(err)
}
t.Log(s)
} }
func TestSectorsFinder(t *testing.T) { func TestFindSpace(t *testing.T) {
sectors := []byte{ r := &Region{
1, 1, sectors: map[int32]bool{
0, 1, //2 0: true, 1: true,
0, 0, 1, //4 2: false, 3: true,
0, 0, 0, 1, 1, //7 4: false, 5: false, 6: true,
0, 0, 0, 0, 0, 1, //12 7: false, 8: false, 9: false, 10: true, 11: true,
0, 0, 0, 0, 0, 0, 0, //18 12: false, 13: false, 14: false, 15: false, 16: false, 17: true,
18: false, 19: false, 20: false, 21: false, 22: false, 23: false, 24: false,
},
} }
scan := func(need int) (index int) { for _, test := range []struct{ need, index int32 }{
for i := 0; i < need; i++ {
if sectors[index+i] != 0 {
index += i + 1
i = -1 // 0 for next loop
}
}
return
}
for _, test := range []struct{ need, index int }{
{0, 0}, {0, 0},
{1, 2}, {1, 2},
{2, 4}, {2, 4},
@ -74,7 +71,7 @@ func TestSectorsFinder(t *testing.T) {
{5, 12}, {5, 12},
{6, 18}, {6, 18},
} { } {
i := scan(test.need) i := r.findSpace(test.need)
if i != test.index { if i != test.index {
t.Errorf("scan sctors fail: get %d, want %d", i, test.index) t.Errorf("scan sctors fail: get %d, want %d", i, test.index)
} }