jl-ecs/world.go
2024-01-23 10:43:40 +08:00

298 lines
6.0 KiB
Go
Raw Permalink 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 (
"context"
"fmt"
"log/slog"
"math"
"runtime/debug"
"time"
"github.com/yohamta/donburi"
"github.com/yohamta/donburi/features/events"
)
type WorldState int
type WorldId = donburi.WorldId
const (
WorldInit WorldState = 0
WorldRunning WorldState = 1
WorldPause WorldState = 2
WorldError WorldState = 3
WorldClosed WorldState = 4
)
type (
World interface {
donburi.World
// 世界运行间隔时间
Tick() int
// 获取世界状态
State() WorldState
// 启动世界
StartUp()
// 暂停世界
Pause()
// 恢复世界
Resume()
// 关闭世界
Close()
// 设置运行倍速
SetSpeed(speed float64) error
// 添加系统
AddSystem(sys ...ISystem)
// 在世界中执行处理逻辑(在世界运行线程中)
Execute(fn HandleFunc) error
}
// 处理函数
HandleFunc func()
// 世界状态变更消息
WorldStateChange struct {
OldState WorldState
NewState WorldState
}
)
// 世界状态变更事件
var WorldStateChangeEvent = NewEventType[WorldStateChange]()
type world struct {
donburi.World
systems []ISystem
state WorldState
tick int
ticker *time.Ticker
// 世界运行倍速
speed float64
// 下一帧系统需要执行的次数
times float64
// 待执行函数
toBeExecuteds chan HandleFunc
cancel context.CancelFunc
done chan struct{} // 服务协程退出信号
}
// 新建一个组件类型
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(fmt.Errorf("创建World错误: tick必须大于0"))
}
return &world{
World: donburi.NewWorld(),
systems: make([]ISystem, 0),
state: WorldInit,
tick: tick,
ticker: time.NewTicker(time.Duration(tick) * time.Millisecond),
speed: 1,
times: 1,
toBeExecuteds: make(chan HandleFunc, 32),
done: make(chan struct{}),
}
}
func (w *world) Tick() int {
return w.tick
}
func (w *world) State() WorldState {
return w.state
}
// 添加系统
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 == WorldRunning {
w.updateState(WorldPause)
}
}
// 恢复世界运行
func (w *world) Resume() {
if w.state == WorldPause {
w.updateState(WorldRunning)
}
}
func (w *world) updateState(state WorldState) {
if w.state != state {
old := w.state
slog.Debug("世界状态变更", "oldstate", old, "state", state)
w.state = state
WorldStateChangeEvent.Publish(w, &WorldStateChange{
OldState: old,
NewState: state,
})
WorldStateChangeEvent.ProcessEvents(w)
}
}
const (
speedMin = 0.1
speedMax = 10
)
func WorldSpeedMax() float64 {
return speedMax
}
func WorldSpeedMin() float64 {
return speedMin
}
// 设置世界运行倍速
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 == WorldInit { // 避免重复运行
slog.Debug("启动世界", "id", w.Id())
ctx, cancle := context.WithCancel(context.Background())
go w.run(ctx)
w.cancel = cancle
w.updateState(WorldRunning)
}
}
// 在世界线程执行逻辑
func (w *world) Execute(fn HandleFunc) error {
if w.state == WorldError {
return fmt.Errorf("世界运行异常,无法执行请求")
} else if w.state == WorldClosed {
return fmt.Errorf("世界已经关闭,无法执行请求")
}
w.toBeExecuteds <- fn
return nil
}
// 关闭世界
func (w *world) Close() {
if w.state == WorldInit {
slog.Debug("关闭世界", "id", w.Id())
w.updateState(WorldClosed)
return
}
if w.cancel != nil {
w.cancel()
<-w.done
}
}
// 执行待处理方法
func (w *world) executeTodos() {
funcs := w.toBeExecuteds
for {
select {
case fn := <-funcs:
{
fn()
}
default:
return
}
}
}
// 世界循环
func (w *world) run(ctx context.Context) {
defer func() {
if err := recover(); err != nil {
w.exception(err)
slog.Error("世界运行异常", "error", err, "stack", string(debug.Stack()))
debug.PrintStack()
}
}()
defer close(w.done)
for range w.ticker.C {
select {
case <-ctx.Done():
w.close()
return
default:
}
// start := time.Now()
if w.state != WorldRunning { // 世界非运行状态
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")
}
}
// 世界运行异常处理
func (w *world) exception(err any) {
// slog.Error("世界运行异常", "error", err, "stack", string(debug.Stack()))
w.updateState(WorldError)
// 关闭定时器
w.ticker.Stop()
// w.handleRequestAndEvent()
}
// 世界正常关闭逻辑
func (w *world) close() {
slog.Debug("关闭世界", "id", w.Id())
// 世界正常关闭
w.updateState(WorldClosed)
// 关闭定时器
w.ticker.Stop()
slog.Debug("世界关闭finish")
}