// Package world 世界与区块管理:异步生成流水线 + 优先级队列 + 时间预算(场景/区块管理.md §3–§4)。 // // 线程模型(架构.md §4): // - 主线程:Update 每帧调度(预算内); // - worker pool:噪声生成 + 天空光灌入; // - 本包同时实现 light.World 接口(跨区块光照访问)。 package world import ( "fmt" "runtime" "sort" "sync" "time" "mc/internal/block" "mc/internal/chunk" "mc/internal/coord" "mc/internal/light" "mc/internal/worldgen" ) // chunkState 区块生命周期状态(区块管理.md §3 状态机子集)。 type chunkState uint8 const ( stateUnloaded chunkState = iota // 不在内存 stateQueued // 已入队等待生成 stateGenerating // worker 生成中 stateActive // 已挂回世界,可读可渲染 ) // World 世界实例。 type World struct { mu sync.Mutex chunks map[uint64]*chunk.Chunk states map[uint64]chunkState reg *block.Registry gen *worldgen.Generator viewDistance int playerX float64 playerZ float64 // 异步生成流水线 workers chan chunkCoord // 待生成坐标 results chan *chunk.Chunk wg sync.WaitGroup } // chunkCoord 区块坐标。 type chunkCoord struct{ cx, cz int32 } // coordKey 区块坐标 → map key(禁止浮点 key,区块管理.md §2.2)。 func coordKey(cx, cz int32) uint64 { return uint64(uint32(cx))<<32 | uint64(uint32(cz)) } // New 创建世界并启动 worker pool(数量 NumCPU-1,最少 1,区块管理.md §4.1)。 func New(reg *block.Registry, gen *worldgen.Generator, viewDistance int) (*World, error) { if reg == nil || gen == nil { return nil, fmt.Errorf("world.New: 注册表与生成器必填") } n := runtime.NumCPU() - 1 if n < 1 { n = 1 } w := &World{ chunks: make(map[uint64]*chunk.Chunk), states: make(map[uint64]chunkState), reg: reg, gen: gen, viewDistance: viewDistance, workers: make(chan chunkCoord, n*2), results: make(chan *chunk.Chunk, n*2), } for i := 0; i < n; i++ { w.wg.Add(1) go w.generateWorker() } return w, nil } // generateWorker 生成 goroutine:纯 CPU 工作,不碰 GL、不持主线程锁。 func (w *World) generateWorker() { defer w.wg.Done() for c := range w.workers { ch := w.gen.Generate(c.cx, c.cz) // 生成完立即做天空光灌入(光照.md §3,单区块:邻居视为阻隔无影响) light.SkyFill(chunkLightAdapter{ch, w.reg}, 0, 15, 0, 15) w.results <- ch } } // Update 每帧调度(主线程调用,区块管理.md §4.3 时间预算): // 1) 收集结果挂回世界;2) 按距离重建优先级队列;3) 预算内派发生成任务。 func (w *World) Update(playerX, playerY, playerZ float64, budget time.Duration) { w.mu.Lock() w.playerX, w.playerZ = playerX, playerZ // 收集生成结果 → active for { select { case ch := <-w.results: k := coordKey(ch.CX, ch.CZ) w.chunks[k] = ch w.states[k] = stateActive ch.Dirty.Store(true) // 首次挂载需建 mesh(渲染.md §3.3) default: goto drained } } drained: // 重建优先级队列:视距内缺失区块按距离排序(区块管理.md §4.2) pcx, pcz := coord.ChunkCoord(int32(playerX)), coord.ChunkCoord(int32(playerZ)) vd := int32(w.viewDistance) var queue []chunkCoord for cx := pcx - vd; cx <= pcx+vd; cx++ { for cz := pcz - vd; cz <= pcz+vd; cz++ { k := coordKey(cx, cz) if w.states[k] == stateActive || w.states[k] == stateGenerating { continue } if w.states[k] == stateUnloaded { w.states[k] = stateQueued } queue = append(queue, chunkCoord{cx, cz}) } } sort.Slice(queue, func(i, j int) bool { return w.dist(queue[i]) < w.dist(queue[j]) }) // 预算内派发(主线程每帧最多做 8ms 调度工作,区块管理.md §4.3.2) start := time.Now() for _, c := range queue { if time.Since(start) > budget { break } select { case w.workers <- c: w.states[coordKey(c.cx, c.cz)] = stateGenerating default: // worker 繁忙:保持 queued,下帧重建队列时重试 } } w.mu.Unlock() } // dist 区块中心到玩家的欧氏距离(区块单位)。 func (w *World) dist(c chunkCoord) float64 { dx := float64(c.cx) - w.playerX/16 dz := float64(c.cz) - w.playerZ/16 return dx*dx + dz*dz } // Block 世界级读方块(未加载区块视为空气)。 func (w *World) Block(x, y, z int32) block.State { w.mu.Lock() ch := w.chunks[coordKey(coord.ChunkCoord(x), coord.ChunkCoord(z))] w.mu.Unlock() if ch == nil { return block.Air } return ch.Block(int(coord.LocalCoord(x)), int(y), int(coord.LocalCoord(z))) } // SetBlock 世界级写方块(含火把光源联动,方块交互与动画.md §9): // 放置光源 → 初始化亮度 + BFS;移除光源 → relight 重算。 func (w *World) SetBlock(x, y, z int32, s block.State) { w.mu.Lock() ch := w.chunks[coordKey(coord.ChunkCoord(x), coord.ChunkCoord(z))] w.mu.Unlock() if ch == nil { return } lx, lz := int(coord.LocalCoord(x)), int(coord.LocalCoord(z)) prev := ch.Block(lx, int(y), lz) ch.SetBlock(lx, int(y), lz, s) prevLight := w.reg.Light(prev) newLight := w.reg.Light(s) switch { case prevLight == 0 && newLight > 0: // 放置光源:初始化自身亮度 + BFS(光照.md §4) w.SetBlockLight(x, y, z, newLight) light.Propagate(w, []light.Pos{{X: x, Y: y, Z: z}}) case prevLight > 0 && newLight == 0: // 移除光源:反向清除 + 重新灌入(光照.md §5) light.RemoveLight(w, []light.Pos{{X: x, Y: y, Z: z}}, w.sourcesIn) } } // sourcesIn 扫描包围盒内现存光源(relight 阶段 2 用)。 func (w *World) sourcesIn(minX, minY, minZ, maxX, maxY, maxZ int32) []light.Pos { var seeds []light.Pos for x := minX; x <= maxX; x++ { for y := minY; y <= maxY; y++ { for z := minZ; z <= maxZ; z++ { if w.reg.Light(w.Block(x, y, z)) > 0 { seeds = append(seeds, light.Pos{X: x, Y: y, Z: z}) } } } } return seeds } // ActiveChunks 返回已激活区块列表(渲染遍历用;返回副本避免并发写)。 func (w *World) ActiveChunks() []*chunk.Chunk { w.mu.Lock() defer w.mu.Unlock() out := make([]*chunk.Chunk, 0, len(w.chunks)) for _, c := range w.chunks { out = append(out, c) } return out } // Count 返回已加载区块数(测试/统计)。 func (w *World) Count() int { w.mu.Lock() defer w.mu.Unlock() return len(w.chunks) } // Close 停止 worker pool(goroutine 可退出,性能与内存.md §8)。 func (w *World) Close() { close(w.workers) w.wg.Wait() } // ---- light.World 接口实现(跨区块光照访问,光照.md §6)---- // Opaque 遮光判定:未加载区块与世界边界视为阻隔(延迟更新语义)。 func (w *World) Opaque(x, y, z int32) bool { if y < 0 || y >= chunk.Height { return true } s := w.Block(x, y, z) return w.reg.IsOpaque(s) } // BlockLight 读方块光。 func (w *World) BlockLight(x, y, z int32) uint8 { if c := w.chunkOf(x, z); c != nil { return c.BlockLight(int(coord.LocalCoord(x)), int(y), int(coord.LocalCoord(z))) } return 0 } // SetBlockLight 写方块光。 func (w *World) SetBlockLight(x, y, z int32, v uint8) { if c := w.chunkOf(x, z); c != nil { c.SetBlockLight(int(coord.LocalCoord(x)), int(y), int(coord.LocalCoord(z)), v) } } // SourceLight 方块自身发光等级。 func (w *World) SourceLight(x, y, z int32) uint8 { return w.reg.Light(w.Block(x, y, z)) } // SkyLight 读天空光。 func (w *World) SkyLight(x, y, z int32) uint8 { if c := w.chunkOf(x, z); c != nil { return c.SkyLight(int(coord.LocalCoord(x)), int(y), int(coord.LocalCoord(z))) } return 15 } // SetSkyLight 写天空光。 func (w *World) SetSkyLight(x, y, z int32, v uint8) { if c := w.chunkOf(x, z); c != nil { c.SetSkyLight(int(coord.LocalCoord(x)), int(y), int(coord.LocalCoord(z)), v) } } // chunkOf 定位区块(短锁)。 func (w *World) chunkOf(x, z int32) *chunk.Chunk { w.mu.Lock() c := w.chunks[coordKey(coord.ChunkCoord(x), coord.ChunkCoord(z))] w.mu.Unlock() return c } // chunkLightAdapter 单区块光照适配器(生成阶段天空光灌入用,邻居视为阻隔)。 type chunkLightAdapter struct { c *chunk.Chunk reg *block.Registry } func (a chunkLightAdapter) Opaque(x, y, z int32) bool { if y < 0 || y >= chunk.Height { return true } return a.reg.IsOpaque(a.c.Block(int(coord.LocalCoord(x)), int(y), int(coord.LocalCoord(z)))) } func (a chunkLightAdapter) BlockLight(x, y, z int32) uint8 { return a.c.BlockLight(int(coord.LocalCoord(x)), int(y), int(coord.LocalCoord(z))) } func (a chunkLightAdapter) SetBlockLight(x, y, z int32, v uint8) { a.c.SetBlockLight(int(coord.LocalCoord(x)), int(y), int(coord.LocalCoord(z)), v) } func (a chunkLightAdapter) SourceLight(x, y, z int32) uint8 { return a.reg.Light(a.c.Block(int(coord.LocalCoord(x)), int(y), int(coord.LocalCoord(z)))) } func (a chunkLightAdapter) SkyLight(x, y, z int32) uint8 { return a.c.SkyLight(int(coord.LocalCoord(x)), int(y), int(coord.LocalCoord(z))) } func (a chunkLightAdapter) SetSkyLight(x, y, z int32, v uint8) { a.c.SetSkyLight(int(coord.LocalCoord(x)), int(y), int(coord.LocalCoord(z)), v) }