Files
ngzz-mc/internal/redstone/redstone.go

133 lines
3.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Package redstone 方块更新队列与红石信号传播(场景/红石与方块更新.md §2–§3)。
//
// 设计要点:
// - 延迟更新队列(0/1/2/4 tick)+ 每 tick 迭代上限(防无限递归);
// - 红石信号 0–15,仅沿线缆传播(每格衰减 1),与方块光完全分离。
package redstone
import (
"sync"
)
// 更新类型(红石与方块更新.md §2)。
const (
KindNeighbor = 0 // 邻居变化
KindScheduled = 1 // 定时刻(延迟更新)
KindBlockEntity = 2 // 方块实体 tick
)
// 迭代上限(红石与方块更新.md §2:每 tick 20000,防级联风暴)。
const DefaultIterLimit = 20000
// Update 一个方块更新事件。
type Update struct {
X, Y, Z int32
Kind uint8
Delay int // 剩余延迟 tick
}
// Queue 延迟更新队列(有序消费)。
type Queue struct {
mu sync.Mutex
events []Update
iterCap int
dropped int // 超限丢弃计数(告警用)
}
// NewQueue 创建队列。
func NewQueue() *Queue { return &Queue{iterCap: DefaultIterLimit} }
// Push 入队。
func (q *Queue) Push(u Update) {
q.mu.Lock()
q.events = append(q.events, u)
q.mu.Unlock()
}
// Pending 待处理事件数。
func (q *Queue) Pending() int {
q.mu.Lock()
defer q.mu.Unlock()
return len(q.events)
}
// Dropped 返回被丢弃的事件数(迭代上限触发次数)。
func (q *Queue) Dropped() int {
q.mu.Lock()
defer q.mu.Unlock()
return q.dropped
}
// Tick 每 tick 消费队列:延迟递减,到期的事件交给 handle 处理。
// 处理数量受 iterCap 限制;超限事件丢弃并计数(红石与方块更新.md §6 踩坑)。
func (q *Queue) Tick(handle func(u Update)) {
q.mu.Lock()
kept := q.events[:0]
var due []Update
for _, u := range q.events {
if u.Delay > 0 {
u.Delay--
kept = append(kept, u)
continue
}
due = append(due, u)
}
q.events = kept
q.mu.Unlock()
processed := 0
for _, u := range due {
if processed >= q.iterCap {
q.mu.Lock()
q.dropped++
q.mu.Unlock()
continue
}
handle(u)
processed++
}
}
// 红石信号(红石与方块更新.md §3:0–15,与方块光分离)。
// Src 信号源。
type Src struct {
X, Y, Z int32
Level uint8 // 0–15
}
// SpreadWire 红石信号沿线缆传播:每格衰减 1,仅经过 isWire 判定的线缆格。
// set 写信号值;返回值无(传播结果由调用方通过 set 收集)。
// 与光照 Propagate 的关键区别:红石只走线缆、不穿过所有方块(红石与方块更新.md §6 踩坑)。
func SpreadWire(sources []Src, isWire func(x, y, z int32) bool, set func(x, y, z int32, v uint8)) {
type pos struct{ x, y, z int32 }
q := make([]pos, 0, 64)
power := make(map[pos]uint8, 64)
for _, s := range sources {
p := pos{s.X, s.Y, s.Z}
power[p] = s.Level
set(p.x, p.y, p.z, s.Level)
q = append(q, p)
}
dirs := [6][3]int32{{1, 0, 0}, {-1, 0, 0}, {0, 1, 0}, {0, -1, 0}, {0, 0, 1}, {0, 0, -1}}
for len(q) > 0 {
p := q[0]
q = q[1:]
lv := power[p]
if lv <= 1 {
continue
}
for _, d := range dirs {
n := pos{p.x + d[0], p.y + d[1], p.z + d[2]}
if !isWire(n.x, n.y, n.z) {
continue
}
if old, ok := power[n]; !ok || old < lv-1 {
power[n] = lv - 1
set(n.x, n.y, n.z, lv-1)
q = append(q, n)
}
}
}
}