jl-ecs/world.go

217 lines
4.0 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 ecs
import (
"fmt"
"log/slog"
"math"
"time"
"github.com/yohamta/donburi"
"github.com/yohamta/donburi/features/events"
)
type WorldState int
type WorldId = donburi.WorldId
const (
Init WorldState = iota
Running
Pause
Error
Close
Closed
)
type (
World interface {
donburi.World
StartUp()
Pause()
Resume()
SetSpeed(speed float64) error
AddSystem(sys ...ISystem)
// 在世界中执行处理逻辑(在世界运行线程中)
Execute(fn HandleFunc)
Close()
// 世界运行间隔
Tick() int
Running() bool
}
// 处理函数
HandleFunc func()
)
type world struct {
donburi.World
systems []ISystem
state WorldState
tick int
ticker *time.Ticker
// 世界运行倍速
speed float64
// 下一帧系统需要执行的次数
times float64
// 待执行函数
toBeExecuteds chan HandleFunc
}
// 新建一个组件类型
func NewComponentType[T any](opts ...interface{}) *ComponentType[T] {
ct := donburi.NewComponentType[T](opts...)
return &ComponentType[T]{ct}
}
// 新建一个标签
func NewTag() *ComponentType[struct{}] {
return NewComponentType[struct{}]()
}
// 将entity列表转换为entry列表
func Entries(w World, entities []donburi.Entity) []*Entry {
entries := make([]*Entry, len(entities))
for i, entity := range entities {
entries[i] = w.Entry(entity)
}
return entries
}
// 初始化一个新World
// tick 单位为ms且必须大于0,(小于15ms的值在Windows系统中会达不到Windows系统中系统中断好像默认是15.6ms也就是一秒最多64次)
func NewWorld(tick int) World {
if tick <= 0 {
panic("tick必须大于0")
}
return &world{
World: donburi.NewWorld(),
systems: make([]ISystem, 0),
state: Init,
tick: tick,
ticker: time.NewTicker(time.Duration(tick) * time.Millisecond),
speed: 1,
times: 1,
toBeExecuteds: make(chan HandleFunc, 32),
}
}
func (w *world) Running() bool {
return w.state == Running
}
func (w *world) Tick() int {
return w.tick
}
// 添加系统
func (w *world) AddSystem(sys ...ISystem) {
w.systems = append(w.systems, sys...)
}
// 执行所有事件处理
func (w *world) ProcessAllEvents() {
events.ProcessAllEvents(w.World)
}
// 暂停世界
func (w *world) Pause() {
if w.state == Running {
w.state = Pause
}
}
// 恢复世界运行
func (w *world) Resume() {
if w.state == Pause {
w.state = Running
}
}
const (
SpeedMin = 0.1
SpeedMax = 10
)
// 设置世界运行倍速
func (w *world) SetSpeed(speed float64) error {
if speed < SpeedMin || speed > SpeedMax {
return fmt.Errorf("速度必须在[%f, %d]之间", SpeedMin, SpeedMax)
}
w.speed = speed
return nil
}
// 启动世界,世界逻辑开始执行且世界为运行状态
func (w *world) StartUp() {
if w.state == Init { // 避免重复运行
w.state = Running
go w.run()
}
}
// 在世界线程执行逻辑
func (w *world) Execute(fn HandleFunc) {
w.toBeExecuteds <- fn
}
// 关闭世界
func (w *world) Close() {
w.state = Close
}
// 执行待处理方法
func (w *world) executeTodos() {
funcs := w.toBeExecuteds
for {
select {
case fn := <-funcs:
{
fn()
}
default:
return
}
}
}
func (w *world) run() {
defer func() {
if err := recover(); err != nil {
slog.Error("世界运行异常:", "stacks", err)
w.state = Error
}
}()
for {
if w.state == Error {
// 世界错误,关闭世界
return
}
if w.state == Close {
// 世界正常关闭
w.state = Closed
return
}
<-w.ticker.C
// start := time.Now()
if w.state == Pause { // 暂停不更新
continue
}
if w.times > 1 {
times := int(math.Floor(w.times))
for i := 0; i < times; i++ {
for _, sys := range w.systems {
sys.Update(w)
}
// 处理事件管理相关
w.executeTodos()
// 处理所有事件
processAllEvents(w)
}
w.times = w.times - float64(times) + w.speed
} else {
w.times += w.speed
}
// dt := time.Since(start)
// slog.Info("仿真系统执行耗时:" + dt.Milliseconds() + "ms")
}
}