Files
ngzz-mc/internal/engine/loop.go

59 lines
1.4 KiB
Go
Raw Normal View History

// Package engine 主循环tick 固定 20Hz、render 吃满帧率、两者解耦(架构.md §3.2)。
package engine
import (
"context"
"time"
"mc/internal/crash"
)
// 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() {
defer crash.RecoverGo("engine-tick") // 非主 goroutine 兜底panic 写崩溃日志(防静默闪退)
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()
}
}