feat(world): 方块注册表、坐标工具、区块数据结构、确定性世界生成(含测试)
This commit is contained in:
116
internal/chunk/chunk.go
Normal file
116
internal/chunk/chunk.go
Normal file
@@ -0,0 +1,116 @@
|
||||
// Package chunk 区块数据结构:一维数组 + 每区块独立锁(场景/区块管理.md §2)。
|
||||
//
|
||||
// 布局:16×16×256 一维 []block.State(索引 (y*16+z)*16+x),
|
||||
// 天空光/方块光各 4-bit 打包存储(每字节 2 个方块)。
|
||||
package chunk
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"mc/internal/block"
|
||||
)
|
||||
|
||||
// 区块尺寸常量(设计.md §4.1)。
|
||||
const (
|
||||
SizeX = 16 // 水平 X 大小
|
||||
SizeZ = 16 // 水平 Z 大小
|
||||
Height = 256 // 垂直高度
|
||||
SectionY = Height / 16 // section 数量(16×16×16)
|
||||
)
|
||||
|
||||
// Chunk 一个区块。锁粒度:每 Chunk 一个 RWMutex(禁止全局锁,性能与内存.md §3)。
|
||||
type Chunk struct {
|
||||
CX, CZ int32
|
||||
|
||||
mu sync.RWMutex
|
||||
blocks []block.State // 方块状态(一维数组,cache 友好)
|
||||
sky []uint8 // 天空光 4-bit 打包
|
||||
blockLt []uint8 // 方块光 4-bit 打包
|
||||
|
||||
Dirty atomic.Bool // mesh 需重建(渲染.md §3.3)
|
||||
LightDirty atomic.Bool // 光照需重算(光照.md)
|
||||
SaveDirty atomic.Bool // 需写盘(存档与持久化.md §2)
|
||||
}
|
||||
|
||||
// New 创建全空气区块。
|
||||
func New(cx, cz int32) *Chunk {
|
||||
return &Chunk{
|
||||
CX: cx,
|
||||
CZ: cz,
|
||||
blocks: make([]block.State, SizeX*SizeZ*Height),
|
||||
sky: make([]uint8, SizeX*SizeZ*Height/2),
|
||||
blockLt: make([]uint8, SizeX*SizeZ*Height/2),
|
||||
}
|
||||
}
|
||||
|
||||
// Index 计算方块数组索引:(y*16+z)*16+x。
|
||||
func Index(x, y, z int) int { return (y*SizeZ+z)*SizeX + x }
|
||||
|
||||
// Block 读取方块(读锁)。
|
||||
func (c *Chunk) Block(x, y, z int) block.State {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
return c.blocks[Index(x, y, z)]
|
||||
}
|
||||
|
||||
// SetBlock 写入方块并标脏(mesh + 光照 + 存档)。
|
||||
func (c *Chunk) SetBlock(x, y, z int, s block.State) {
|
||||
c.mu.Lock()
|
||||
c.blocks[Index(x, y, z)] = s
|
||||
c.mu.Unlock()
|
||||
c.Dirty.Store(true)
|
||||
c.LightDirty.Store(true)
|
||||
c.SaveDirty.Store(true)
|
||||
}
|
||||
|
||||
// Fill 初始化填充(生成器专用,不标存档脏:生成结果属纯函数可重算)。
|
||||
func (c *Chunk) Fill(x, y, z int, s block.State) {
|
||||
c.blocks[Index(x, y, z)] = s
|
||||
}
|
||||
|
||||
// FillAll 整块填充(生成器初始化)。
|
||||
func (c *Chunk) FillAll(s block.State) {
|
||||
for i := range c.blocks {
|
||||
c.blocks[i] = s
|
||||
}
|
||||
}
|
||||
|
||||
// SkyLight 读取天空光(4-bit,0–15)。
|
||||
func (c *Chunk) SkyLight(x, y, z int) uint8 { return nibble(c.sky, Index(x, y, z)) }
|
||||
|
||||
// SetSkyLight 写入天空光。
|
||||
func (c *Chunk) SetSkyLight(x, y, z int, v uint8) { setNibble(c.sky, Index(x, y, z), v) }
|
||||
|
||||
// BlockLight 读取方块光(4-bit,0–15)。
|
||||
func (c *Chunk) BlockLight(x, y, z int) uint8 { return nibble(c.blockLt, Index(x, y, z)) }
|
||||
|
||||
// SetBlockLight 写入方块光。
|
||||
func (c *Chunk) SetBlockLight(x, y, z int, v uint8) { setNibble(c.blockLt, Index(x, y, z), v) }
|
||||
|
||||
// nibble 读取半字节(偶数索引 → 低 4 位,奇数索引 → 高 4 位)。
|
||||
func nibble(arr []uint8, idx int) uint8 {
|
||||
if idx&1 == 0 {
|
||||
return arr[idx/2] & 0xF
|
||||
}
|
||||
return arr[idx/2] >> 4
|
||||
}
|
||||
|
||||
// setNibble 写入半字节。
|
||||
func setNibble(arr []uint8, idx int, v uint8) {
|
||||
v &= 0xF
|
||||
if idx&1 == 0 {
|
||||
arr[idx/2] = (arr[idx/2] & 0xF0) | v
|
||||
} else {
|
||||
arr[idx/2] = (arr[idx/2] & 0x0F) | (v << 4)
|
||||
}
|
||||
}
|
||||
|
||||
// Snapshot 无锁快照(mesh 构建 goroutine 读取用;调用方需保证期间无写入)。
|
||||
func (c *Chunk) Snapshot() []block.State {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
out := make([]block.State, len(c.blocks))
|
||||
copy(out, c.blocks)
|
||||
return out
|
||||
}
|
||||
56
internal/chunk/chunk_test.go
Normal file
56
internal/chunk/chunk_test.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package chunk
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"mc/internal/block"
|
||||
)
|
||||
|
||||
// TestIndex 验证一维索引换算。
|
||||
func TestIndex(t *testing.T) {
|
||||
c := New(0, 0)
|
||||
if got := Index(0, 0, 0); got != 0 {
|
||||
t.Fatalf("Index(0,0,0) = %d", got)
|
||||
}
|
||||
if got := Index(1, 0, 0); got != 1 {
|
||||
t.Fatalf("Index(1,0,0) = %d", got)
|
||||
}
|
||||
if got := Index(0, 0, 1); got != 16 {
|
||||
t.Fatalf("Index(0,0,1) = %d", got)
|
||||
}
|
||||
if got := Index(0, 1, 0); got != 256 {
|
||||
t.Fatalf("Index(0,1,0) = %d", got)
|
||||
}
|
||||
_ = c
|
||||
}
|
||||
|
||||
// TestSetGet 验证读写与脏标记。
|
||||
func TestSetGet(t *testing.T) {
|
||||
c := New(3, -7)
|
||||
s := block.NewState(5, 0)
|
||||
c.SetBlock(2, 64, 3, s)
|
||||
if got := c.Block(2, 64, 3); got != s {
|
||||
t.Fatalf("读回 %v,期望 %v", got, s)
|
||||
}
|
||||
if !c.Dirty.Load() || !c.SaveDirty.Load() {
|
||||
t.Fatal("SetBlock 应标记 dirty/saveDirty")
|
||||
}
|
||||
}
|
||||
|
||||
// TestNibble 验证 4-bit 光照打包(每字节 2 方块)。
|
||||
func TestNibble(t *testing.T) {
|
||||
c := New(0, 0)
|
||||
// 相邻两个方块:一个偶数索引一个奇数索引,共用一个字节
|
||||
c.SetSkyLight(0, 10, 0, 15)
|
||||
c.SetSkyLight(1, 10, 0, 7)
|
||||
if got := c.SkyLight(0, 10, 0); got != 15 {
|
||||
t.Fatalf("sky(0) = %d,期望 15", got)
|
||||
}
|
||||
if got := c.SkyLight(1, 10, 0); got != 7 {
|
||||
t.Fatalf("sky(1) = %d,期望 7", got)
|
||||
}
|
||||
// 未设置的保持 0
|
||||
if got := c.BlockLight(0, 10, 0); got != 0 {
|
||||
t.Fatalf("blockLight(0) = %d,期望 0", got)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user