2026-08-15 22:24:38 +08:00
|
|
|
|
// Package engine 主循环:tick 固定 20Hz、render 吃满帧率、两者解耦(架构.md §3.2)。
|
|
|
|
|
|
package engine
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"context"
|
|
|
|
|
|
"time"
|
2026-08-16 06:46:25 +08:00
|
|
|
|
|
|
|
|
|
|
"mc/internal/crash"
|
2026-08-15 22:24:38 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// Ticker 世界逻辑接口:每 tick 调用,dt 为固定 tick 间隔秒数。
|
|
|
|
|
|
type Ticker interface {
|
|
|
|
|
|
Tick(dt float64)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Renderer 渲染接口:每帧调用;ShouldClose 返回 true 时退出循环。
|
|
|
|
|
|
type Renderer interface {
|
|
|
|
|
|
Render()
|
|
|
|
|
|
ShouldClose() bool
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// NoopRenderer 无渲染占位(服务器),以约 60Hz 空转。
|
|
|
|
|
|
type NoopRenderer struct{}
|
|
|
|
|
|
|
|
|
|
|
|
// Render 空实现。
|
|
|
|
|
|
func (NoopRenderer) Render() { time.Sleep(time.Second / 60) }
|
|
|
|
|
|
|
|
|
|
|
|
// ShouldClose 永不主动关闭。
|
|
|
|
|
|
func (NoopRenderer) ShouldClose() bool { return false }
|
|
|
|
|
|
|
|
|
|
|
|
// Run 主循环:tick goroutine 固定 20Hz;主 goroutine 渲染;ctx 取消或窗口关闭时退出。
|
|
|
|
|
|
func Run(ctx context.Context, tick Ticker, render Renderer) {
|
|
|
|
|
|
const tickInterval = time.Second / 20
|
|
|
|
|
|
stop := make(chan struct{})
|
|
|
|
|
|
go func() {
|
2026-08-16 06:46:25 +08:00
|
|
|
|
defer crash.RecoverGo("engine-tick") // 非主 goroutine 兜底:panic 写崩溃日志(防静默闪退)
|
2026-08-15 22:24:38 +08:00
|
|
|
|
t := time.NewTicker(tickInterval)
|
|
|
|
|
|
defer t.Stop()
|
|
|
|
|
|
for {
|
|
|
|
|
|
select {
|
|
|
|
|
|
case <-ctx.Done():
|
|
|
|
|
|
return
|
|
|
|
|
|
case <-stop:
|
|
|
|
|
|
return
|
|
|
|
|
|
case <-t.C:
|
|
|
|
|
|
tick.Tick(tickInterval.Seconds())
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}()
|
|
|
|
|
|
defer close(stop)
|
|
|
|
|
|
|
|
|
|
|
|
for {
|
|
|
|
|
|
if ctx.Err() != nil || render.ShouldClose() {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
render.Render()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|