feat(save): 存档系统——region 文件(zstd+CRC32+原子替换)、level.dat、区块序列化、世界存取(含测试)
This commit is contained in:
145
internal/save/save_test.go
Normal file
145
internal/save/save_test.go
Normal file
@@ -0,0 +1,145 @@
|
||||
package save
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"mc/internal/block"
|
||||
"mc/internal/world"
|
||||
"mc/internal/worldgen"
|
||||
)
|
||||
|
||||
// loadSaveReg 加载注册表。
|
||||
func loadSaveReg(t *testing.T) *block.Registry {
|
||||
t.Helper()
|
||||
r, err := block.Load(filepath.Join("..", "..", "assets", "config", "blocks.json"))
|
||||
if err != nil {
|
||||
t.Fatalf("加载注册表失败: %v", err)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// newGen 创建生成器。
|
||||
func newGen(t *testing.T, reg *block.Registry, seed int64) *worldgen.Generator {
|
||||
t.Helper()
|
||||
g, err := worldgen.New(reg, seed, 0.005, 0.01, 0.1)
|
||||
if err != nil {
|
||||
t.Fatalf("创建生成器失败: %v", err)
|
||||
}
|
||||
return g
|
||||
}
|
||||
|
||||
// TestChunkRoundtrip 区块序列化往返(存档格式.md §4)。
|
||||
func TestChunkRoundtrip(t *testing.T) {
|
||||
reg := loadSaveReg(t)
|
||||
g := newGen(t, reg, 777)
|
||||
c := g.Generate(3, -5)
|
||||
c.SetBlockLight(8, 70, 8, 13)
|
||||
|
||||
dec, err := DecodeChunk(EncodeChunk(c))
|
||||
if err != nil {
|
||||
t.Fatalf("反序列化失败: %v", err)
|
||||
}
|
||||
if dec.CX != 3 || dec.CZ != -5 {
|
||||
t.Fatalf("坐标不一致: %d,%d", dec.CX, dec.CZ)
|
||||
}
|
||||
if dec.Block(8, 70, 8) != c.Block(8, 70, 8) {
|
||||
t.Fatal("方块数据不一致")
|
||||
}
|
||||
if dec.BlockLight(8, 70, 8) != 13 {
|
||||
t.Fatalf("方块光不一致: %d", dec.BlockLight(8, 70, 8))
|
||||
}
|
||||
if dec.SkyLight(8, 100, 8) != c.SkyLight(8, 100, 8) {
|
||||
t.Fatal("天空光不一致")
|
||||
}
|
||||
}
|
||||
|
||||
// TestRegionWriteRead 区域文件写入/读取(含 CRC 与跨重启重开)。
|
||||
func TestRegionWriteRead(t *testing.T) {
|
||||
reg := loadSaveReg(t)
|
||||
g := newGen(t, reg, 99)
|
||||
dir := t.TempDir()
|
||||
|
||||
data := EncodeChunk(g.Generate(1, 1))
|
||||
region, err := OpenRegion(dir, 0, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("打开区域失败: %v", err)
|
||||
}
|
||||
if err := region.WriteChunk(1, 1, data); err != nil {
|
||||
t.Fatalf("写区块失败: %v", err)
|
||||
}
|
||||
// 模拟重启:重新打开区域
|
||||
region2, err := OpenRegion(dir, 0, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("重开区域失败: %v", err)
|
||||
}
|
||||
got, ok, err := region2.ReadChunk(1, 1)
|
||||
if err != nil || !ok {
|
||||
t.Fatalf("读区块失败: ok=%v err=%v", ok, err)
|
||||
}
|
||||
if string(got) != string(data) {
|
||||
t.Fatal("读取数据不一致")
|
||||
}
|
||||
// 未保存区块应返回 ok=false
|
||||
if _, ok, _ := region2.ReadChunk(2, 2); ok {
|
||||
t.Fatal("未保存区块应返回 false")
|
||||
}
|
||||
}
|
||||
|
||||
// TestSaveLoadWorld 世界级存取:放火把 → 保存 → 载入 → 火把与光仍在。
|
||||
func TestSaveLoadWorld(t *testing.T) {
|
||||
reg := loadSaveReg(t)
|
||||
seed := int64(2024)
|
||||
g := newGen(t, reg, seed)
|
||||
w, err := world.New(reg, g, 3)
|
||||
if err != nil {
|
||||
t.Fatalf("创建世界失败: %v", err)
|
||||
}
|
||||
defer w.Close()
|
||||
|
||||
// 生成并等待区块
|
||||
deadline := time.Now().Add(15 * time.Second)
|
||||
for time.Now().Before(deadline) {
|
||||
w.Update(8, 64, 8, 8*time.Millisecond)
|
||||
if w.Block(8, 0, 8) != block.Air {
|
||||
break
|
||||
}
|
||||
time.Sleep(2 * time.Millisecond)
|
||||
}
|
||||
// 放一个火把
|
||||
torch, _ := reg.ID("torch")
|
||||
var surf int32
|
||||
for y := int32(255); y >= 0; y-- {
|
||||
if w.Block(8, y, 8) != block.Air {
|
||||
surf = y
|
||||
break
|
||||
}
|
||||
}
|
||||
w.SetBlock(8, surf+1, 8, block.NewState(torch, 0))
|
||||
|
||||
dir := t.TempDir()
|
||||
if err := SaveWorld(w, dir, "TestWorld", seed, 1000); err != nil {
|
||||
t.Fatalf("保存失败: %v", err)
|
||||
}
|
||||
|
||||
// 载入到新世界
|
||||
g2 := newGen(t, reg, seed)
|
||||
w2, err := world.New(reg, g2, 3)
|
||||
if err != nil {
|
||||
t.Fatalf("创建世界失败: %v", err)
|
||||
}
|
||||
defer w2.Close()
|
||||
if gotSeed, err := LoadWorld(w2, dir, "TestWorld"); err != nil {
|
||||
t.Fatalf("载入失败: %v", err)
|
||||
} else if gotSeed != seed {
|
||||
t.Fatalf("种子不一致: %d", gotSeed)
|
||||
}
|
||||
// 火把及其光
|
||||
if s := w2.Block(8, surf+1, 8); s.ID() != torch {
|
||||
t.Fatalf("火把丢失: %v", s)
|
||||
}
|
||||
if l := w2.BlockLight(7, surf+1, 8); l != 13 {
|
||||
t.Fatalf("火把光丢失: %d", l)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user