write mca region file (test and optimization)
This commit is contained in:
@ -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
|
||||||
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user