175 lines
4.6 KiB
Go
175 lines
4.6 KiB
Go
|
|
package mpsync
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"context"
|
|||
|
|
"net"
|
|||
|
|
"path/filepath"
|
|||
|
|
"testing"
|
|||
|
|
"time"
|
|||
|
|
|
|||
|
|
"mc/internal/block"
|
|||
|
|
"mc/internal/logx"
|
|||
|
|
"mc/internal/netproto"
|
|||
|
|
"mc/internal/netserver"
|
|||
|
|
"mc/internal/world"
|
|||
|
|
"mc/internal/worldgen"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// syncHandler 测试用服务器处理器:登录 → hub.AddPlayer。
|
|||
|
|
type syncHandler struct {
|
|||
|
|
hub *Hub
|
|||
|
|
recv chan *netserver.Conn
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (h *syncHandler) OnConnect(c *netserver.Conn) {}
|
|||
|
|
func (h *syncHandler) OnHandshake(c *netserver.Conn, hs netproto.Handshake) uint8 {
|
|||
|
|
if h.recv != nil {
|
|||
|
|
h.recv <- c
|
|||
|
|
}
|
|||
|
|
h.hub.AddPlayer(c, 8, 8)
|
|||
|
|
return 0
|
|||
|
|
}
|
|||
|
|
func (h *syncHandler) OnMove(c *netserver.Conn, m netproto.PlayerMove) {}
|
|||
|
|
func (h *syncHandler) OnDisconnect(c *netserver.Conn) { h.hub.RemovePlayer(c) }
|
|||
|
|
|
|||
|
|
// TestLoginReceivesChunk 登录后客户端收到区块数据帧(多人同步.md §4)。
|
|||
|
|
func TestLoginReceivesChunk(t *testing.T) {
|
|||
|
|
reg, err := block.Load(filepath.Join("..", "..", "assets", "config", "blocks.json"))
|
|||
|
|
if err != nil {
|
|||
|
|
t.Fatalf("加载注册表失败: %v", err)
|
|||
|
|
}
|
|||
|
|
gen, err := worldgen.New(reg, 7, 0.005, 0.01, 0.1)
|
|||
|
|
if err != nil {
|
|||
|
|
t.Fatalf("创建生成器失败: %v", err)
|
|||
|
|
}
|
|||
|
|
w, err := world.New(reg, gen, 4)
|
|||
|
|
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)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
hub := NewHub(w, 4)
|
|||
|
|
log, _ := logx.New("", logx.LevelDebug)
|
|||
|
|
h := &syncHandler{hub: hub, recv: make(chan *netserver.Conn, 1)}
|
|||
|
|
srv := netserver.New("127.0.0.1:0", log, h)
|
|||
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|||
|
|
defer cancel()
|
|||
|
|
if err := srv.Listen(ctx); err != nil {
|
|||
|
|
t.Fatalf("监听失败: %v", err)
|
|||
|
|
}
|
|||
|
|
nc, err := net.DialTimeout("tcp", srv.Addr(), 3*time.Second)
|
|||
|
|
if err != nil {
|
|||
|
|
t.Fatalf("拨号失败: %v", err)
|
|||
|
|
}
|
|||
|
|
defer nc.Close()
|
|||
|
|
|
|||
|
|
hs := netproto.Handshake{ProtocolVersion: 1, ClientID: "同步测试"}
|
|||
|
|
_, _ = nc.Write(netproto.EncodeFrame(netproto.Frame{MsgID: netproto.MsgHandshake, Payload: hs.Encode()}))
|
|||
|
|
|
|||
|
|
gotChunk := false
|
|||
|
|
buf := make([]byte, 64*1024)
|
|||
|
|
_ = nc.SetReadDeadline(time.Now().Add(5 * time.Second))
|
|||
|
|
acc := []byte{}
|
|||
|
|
for !gotChunk {
|
|||
|
|
n, err := nc.Read(buf)
|
|||
|
|
if err != nil {
|
|||
|
|
break
|
|||
|
|
}
|
|||
|
|
acc = append(acc, buf[:n]...)
|
|||
|
|
for len(acc) >= 4 {
|
|||
|
|
f, rest, err := netproto.DecodeFrame(acc)
|
|||
|
|
if err != nil {
|
|||
|
|
break
|
|||
|
|
}
|
|||
|
|
acc = rest
|
|||
|
|
if f.MsgID == netproto.MsgChunkData {
|
|||
|
|
gotChunk = true
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if !gotChunk {
|
|||
|
|
t.Fatal("登录后未收到区块数据")
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// TestBlockChangeBroadcast 服务器权威写方块 → 客户端收到广播(多人同步.md §5)。
|
|||
|
|
func TestBlockChangeBroadcast(t *testing.T) {
|
|||
|
|
reg, err := block.Load(filepath.Join("..", "..", "assets", "config", "blocks.json"))
|
|||
|
|
if err != nil {
|
|||
|
|
t.Fatalf("加载注册表失败: %v", err)
|
|||
|
|
}
|
|||
|
|
gen, err := worldgen.New(reg, 8, 0.005, 0.01, 0.1)
|
|||
|
|
if err != nil {
|
|||
|
|
t.Fatalf("创建生成器失败: %v", err)
|
|||
|
|
}
|
|||
|
|
w, err := world.New(reg, gen, 2)
|
|||
|
|
if err != nil {
|
|||
|
|
t.Fatalf("创建世界失败: %v", err)
|
|||
|
|
}
|
|||
|
|
defer w.Close()
|
|||
|
|
|
|||
|
|
hub := NewHub(w, 4)
|
|||
|
|
log, _ := logx.New("", logx.LevelDebug)
|
|||
|
|
h := &syncHandler{hub: hub, recv: make(chan *netserver.Conn, 1)}
|
|||
|
|
srv := netserver.New("127.0.0.1:0", log, h)
|
|||
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|||
|
|
defer cancel()
|
|||
|
|
if err := srv.Listen(ctx); err != nil {
|
|||
|
|
t.Fatalf("监听失败: %v", err)
|
|||
|
|
}
|
|||
|
|
nc, err := net.DialTimeout("tcp", srv.Addr(), 3*time.Second)
|
|||
|
|
if err != nil {
|
|||
|
|
t.Fatalf("拨号失败: %v", err)
|
|||
|
|
}
|
|||
|
|
defer nc.Close()
|
|||
|
|
_, _ = nc.Write(netproto.EncodeFrame(netproto.Frame{MsgID: netproto.MsgHandshake, Payload: netproto.Handshake{ProtocolVersion: 1}.Encode()}))
|
|||
|
|
|
|||
|
|
// 等待服务器完成登录注册(避免广播早于 AddPlayer 执行,多人同步.md §2 时序)
|
|||
|
|
select {
|
|||
|
|
case <-h.recv:
|
|||
|
|
case <-time.After(3 * time.Second):
|
|||
|
|
t.Fatal("未完成登录注册")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
torch, _ := reg.ID("torch")
|
|||
|
|
go hub.SetBlockWorld(8, 70, 8, block.NewState(torch, 0))
|
|||
|
|
|
|||
|
|
buf := make([]byte, 64*1024)
|
|||
|
|
_ = nc.SetReadDeadline(time.Now().Add(5 * time.Second))
|
|||
|
|
acc := []byte{}
|
|||
|
|
deadline := time.Now().Add(5 * time.Second)
|
|||
|
|
for time.Now().Before(deadline) {
|
|||
|
|
n, err := nc.Read(buf)
|
|||
|
|
if err != nil {
|
|||
|
|
break
|
|||
|
|
}
|
|||
|
|
acc = append(acc, buf[:n]...)
|
|||
|
|
for len(acc) >= 4 {
|
|||
|
|
f, rest, err := netproto.DecodeFrame(acc)
|
|||
|
|
if err != nil {
|
|||
|
|
break
|
|||
|
|
}
|
|||
|
|
acc = rest
|
|||
|
|
if f.MsgID != netproto.MsgBlockChange {
|
|||
|
|
continue
|
|||
|
|
}
|
|||
|
|
m, err := netproto.DecodeBlockChange(f.Payload)
|
|||
|
|
if err != nil {
|
|||
|
|
t.Fatalf("解码失败: %v", err)
|
|||
|
|
}
|
|||
|
|
if m.X == 8 && m.Y == 70 && m.Z == 8 && m.BlockID == torch {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
t.Fatal("未收到方块变更广播")
|
|||
|
|
}
|