初始版本
This commit is contained in:
commit
3b10586590
33
.gitignore
vendored
Normal file
33
.gitignore
vendored
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
.DS_Store
|
||||||
|
.thumbs.db
|
||||||
|
node_modules
|
||||||
|
|
||||||
|
# Quasar core related directories
|
||||||
|
.quasar
|
||||||
|
/dist
|
||||||
|
|
||||||
|
# Cordova related directories and files
|
||||||
|
/src-cordova/node_modules
|
||||||
|
/src-cordova/platforms
|
||||||
|
/src-cordova/plugins
|
||||||
|
/src-cordova/www
|
||||||
|
|
||||||
|
# Capacitor related directories and files
|
||||||
|
/src-capacitor/www
|
||||||
|
/src-capacitor/node_modules
|
||||||
|
|
||||||
|
# BEX related directories and files
|
||||||
|
/src-bex/www
|
||||||
|
/src-bex/js/core
|
||||||
|
|
||||||
|
# Log files
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.idea
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
9
common_component.go
Normal file
9
common_component.go
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
package ecs
|
||||||
|
|
||||||
|
type Id string
|
||||||
|
|
||||||
|
var IdComp = NewComponentType[Id]()
|
||||||
|
|
||||||
|
// func IdComp() *ComponentType[Id] {
|
||||||
|
// return NewComponentType[Id]()
|
||||||
|
// }
|
47
component.go
Normal file
47
component.go
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
package ecs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/yohamta/donburi"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ComponentType[T any] struct {
|
||||||
|
*donburi.ComponentType[T]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns component data from the entry.
|
||||||
|
func (c *ComponentType[T]) Get(entry *Entry) *T {
|
||||||
|
return c.ComponentType.Get(entry.Entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set sets component data to the entry.
|
||||||
|
func (c *ComponentType[T]) Set(entry *Entry, component *T) {
|
||||||
|
c.ComponentType.Set(entry.Entry, component)
|
||||||
|
}
|
||||||
|
|
||||||
|
// // Each iterates over the entities that have the component.
|
||||||
|
// func (c *ComponentType[T]) Each(w World, callback func(*Entry)) {
|
||||||
|
// c.ComponentType.Each(w.(*world).world, func(entry *donburi.Entry) {
|
||||||
|
// callback(&Entry{Entry: entry})
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // First returns the first entity that has the component.
|
||||||
|
// func (c *ComponentType[T]) First(w World) (*Entry, bool) {
|
||||||
|
// entry, ok := c.ComponentType.First(w.(*world).world)
|
||||||
|
// return &Entry{entry}, ok
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // MustFirst returns the first entity that has the component or panics.
|
||||||
|
// func (c *ComponentType[T]) MustFirst(w World) *Entry {
|
||||||
|
// e, ok := c.First(w)
|
||||||
|
// if !ok {
|
||||||
|
// panic(fmt.Sprintf("no entity has the component %s", c.ComponentType.Name()))
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return e
|
||||||
|
// }
|
||||||
|
|
||||||
|
// SetValue sets the value of the component.
|
||||||
|
func (c *ComponentType[T]) SetValue(entry *Entry, value T) {
|
||||||
|
c.ComponentType.SetValue(entry.Entry, value)
|
||||||
|
}
|
44
ecs_test.go
Normal file
44
ecs_test.go
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
package ecs_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"joylink.club/ecs"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Sys1 struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
type Sys2 struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Sys1) Update(w ecs.World) {
|
||||||
|
log.Println("系统1运行")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Sys2) Update(w ecs.World) {
|
||||||
|
log.Println("系统2运行")
|
||||||
|
}
|
||||||
|
|
||||||
|
func BaseTest(t *testing.T) {
|
||||||
|
log.Println("Base Test")
|
||||||
|
w := ecs.NewWorld(500)
|
||||||
|
w.AddSystem(&Sys1{}, &Sys2{})
|
||||||
|
w.StartUp()
|
||||||
|
|
||||||
|
time.Sleep(3 * time.Second)
|
||||||
|
// w.SetSpeed(2)
|
||||||
|
|
||||||
|
// time.Sleep(3 * time.Second)
|
||||||
|
// w.Pause()
|
||||||
|
|
||||||
|
// time.Sleep(2 * time.Second)
|
||||||
|
// w.Resume()
|
||||||
|
|
||||||
|
// time.Sleep(2 * time.Second)
|
||||||
|
// w.Close()
|
||||||
|
|
||||||
|
// time.Sleep(3 * time.Second)
|
||||||
|
}
|
9
entity.go
Normal file
9
entity.go
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
package ecs
|
||||||
|
|
||||||
|
import "github.com/yohamta/donburi"
|
||||||
|
|
||||||
|
type (
|
||||||
|
Entity struct {
|
||||||
|
donburi.Entity
|
||||||
|
}
|
||||||
|
)
|
7
entry.go
Normal file
7
entry.go
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package ecs
|
||||||
|
|
||||||
|
import "github.com/yohamta/donburi"
|
||||||
|
|
||||||
|
type Entry struct {
|
||||||
|
*donburi.Entry
|
||||||
|
}
|
11
events.go
Normal file
11
events.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package ecs
|
||||||
|
|
||||||
|
type (
|
||||||
|
EventType[T any] struct {
|
||||||
|
eventName string
|
||||||
|
componentType *ComponentType[T]
|
||||||
|
w World
|
||||||
|
}
|
||||||
|
|
||||||
|
Subscriber[T any] func(w *World, event T)
|
||||||
|
)
|
5
go.mod
Normal file
5
go.mod
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
module joylink.club/ecs
|
||||||
|
|
||||||
|
go 1.20
|
||||||
|
|
||||||
|
require github.com/yohamta/donburi v1.3.8
|
6
go.sum
Normal file
6
go.sum
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
|
||||||
|
github.com/yohamta/donburi v1.3.8 h1:ca4NuhzJ8Jeb6GAEf6ecksa+l8JWaAnr0WLqG20TimU=
|
||||||
|
github.com/yohamta/donburi v1.3.8/go.mod h1:5QkyraUjkzbMVTD2b8jaPFy1Uwjm/zdFN1c1lZGaezg=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
33
query.go
Normal file
33
query.go
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package ecs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/yohamta/donburi"
|
||||||
|
"github.com/yohamta/donburi/filter"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Query struct {
|
||||||
|
*donburi.Query
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewQuery creates a new query.
|
||||||
|
// It receives arbitrary filters that are used to filter entities.
|
||||||
|
func NewQuery(filter filter.LayoutFilter) *Query {
|
||||||
|
return &Query{
|
||||||
|
donburi.NewQuery(filter),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Query) Each(w World, callback func(*Entry)) {
|
||||||
|
q.Query.Each(w.(*world).world, func(entry *donburi.Entry) {
|
||||||
|
callback(&Entry{entry})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Query) Count(w World) int {
|
||||||
|
return q.Query.Count(w.(*world).world)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Query) First(w World) (entry *Entry, ok bool) {
|
||||||
|
e, ok := q.Query.First(w.(*world).world)
|
||||||
|
return &Entry{e}, ok
|
||||||
|
}
|
5
system.go
Normal file
5
system.go
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
package ecs
|
||||||
|
|
||||||
|
type ISystem interface {
|
||||||
|
Update(w World)
|
||||||
|
}
|
219
world.go
Normal file
219
world.go
Normal file
@ -0,0 +1,219 @@
|
|||||||
|
package ecs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"reflect"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/yohamta/donburi"
|
||||||
|
"github.com/yohamta/donburi/features/events"
|
||||||
|
)
|
||||||
|
|
||||||
|
type WorldState int
|
||||||
|
|
||||||
|
type WorldId int
|
||||||
|
|
||||||
|
const (
|
||||||
|
Init WorldState = iota
|
||||||
|
Running
|
||||||
|
Pause
|
||||||
|
Error
|
||||||
|
Closed
|
||||||
|
)
|
||||||
|
|
||||||
|
type World interface {
|
||||||
|
|
||||||
|
// Id returns the unique identifier for the world.
|
||||||
|
Id() WorldId
|
||||||
|
// Create creates a new entity with the specified components.
|
||||||
|
Create(components ...donburi.IComponentType) Entity
|
||||||
|
// CreateMany creates a new entity with the specified components.
|
||||||
|
CreateMany(n int, components ...donburi.IComponentType) []Entity
|
||||||
|
// Entry returns an entry for the specified entity.
|
||||||
|
Entry(entity Entity) *Entry
|
||||||
|
// Remove removes the specified entity.
|
||||||
|
Remove(entity Entity)
|
||||||
|
// Valid returns true if the specified entity is valid.
|
||||||
|
Valid(e Entity) bool
|
||||||
|
// Len returns the number of entities in the world.
|
||||||
|
Len() int
|
||||||
|
|
||||||
|
StartUp()
|
||||||
|
Pause()
|
||||||
|
Resume()
|
||||||
|
SetSpeed(speed float64) error
|
||||||
|
AddSystem(sys ...ISystem)
|
||||||
|
Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
type world struct {
|
||||||
|
world donburi.World
|
||||||
|
systems []ISystem
|
||||||
|
state WorldState
|
||||||
|
tick int
|
||||||
|
speed float64
|
||||||
|
|
||||||
|
quit chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewComponentType[T any](opts ...interface{}) *ComponentType[T] {
|
||||||
|
ct := donburi.NewComponentType[T](opts...)
|
||||||
|
return &ComponentType[T]{ct}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWorld(tick int) World {
|
||||||
|
return &world{
|
||||||
|
world: donburi.NewWorld(),
|
||||||
|
systems: make([]ISystem, 0),
|
||||||
|
state: Init,
|
||||||
|
tick: tick,
|
||||||
|
speed: 1,
|
||||||
|
quit: make(chan struct{}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *world) Id() WorldId {
|
||||||
|
return WorldId(w.world.Id())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *world) Create(components ...donburi.IComponentType) Entity {
|
||||||
|
len := len(components)
|
||||||
|
var icts []donburi.IComponentType = make([]donburi.IComponentType, len)
|
||||||
|
for i := 0; i < len; i++ {
|
||||||
|
icts[i] = components[i].(ComponentType[any]).ComponentType
|
||||||
|
}
|
||||||
|
entity := w.world.Create(icts...)
|
||||||
|
return Entity{entity}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *world) CreateMany(n int, components ...donburi.IComponentType) []Entity {
|
||||||
|
entitys := w.world.CreateMany(n, components...)
|
||||||
|
ets := make([]Entity, len(entitys))
|
||||||
|
for i, e := range entitys {
|
||||||
|
ets[i] = Entity{e}
|
||||||
|
}
|
||||||
|
return ets
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *world) Entry(entity Entity) *Entry {
|
||||||
|
return &Entry{w.world.Entry(entity.Entity)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *world) Remove(entity Entity) {
|
||||||
|
w.world.Remove(entity.Entity)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *world) Valid(e Entity) bool {
|
||||||
|
return w.world.Valid(e.Entity)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *world) Len() int {
|
||||||
|
return w.world.Len()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加系统
|
||||||
|
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) Close() {
|
||||||
|
w.quit <- struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *world) run() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-w.quit: // 退出信号
|
||||||
|
log.Println("仿真退出,id:", w.world.Id())
|
||||||
|
w.state = Closed
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
if w.state == Error {
|
||||||
|
log.Println("世界错误,关闭世界,id:", w.world.Id())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if w.state == Closed {
|
||||||
|
log.Println("世界正常关闭,id:", w.world.Id())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if w.state == Pause { // 暂停不更新
|
||||||
|
log.Println("仿真暂停中,id:", w.world.Id())
|
||||||
|
sleep := int64(float64(w.tick) / (w.speed))
|
||||||
|
time.Sleep(time.Duration(sleep) * time.Millisecond)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
start := time.Now()
|
||||||
|
// fmt.Println("仿真更新,id:", info.id)
|
||||||
|
for _, sys := range w.systems {
|
||||||
|
sys.Update(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理所有事件
|
||||||
|
// w.ProcessAllEvents()
|
||||||
|
|
||||||
|
// 执行逻辑花费时间,单位ms
|
||||||
|
ot := time.Duration(time.Now().Nanosecond() - start.Nanosecond()).Milliseconds()
|
||||||
|
// 根据间隔和速度计算休眠时间
|
||||||
|
sleep := int64(float64(w.tick)/(w.speed)) - ot
|
||||||
|
if sleep > 0 {
|
||||||
|
time.Sleep(time.Duration(sleep) * time.Millisecond)
|
||||||
|
} else {
|
||||||
|
log.Println("仿真无休眠,id:", w.world.Id())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewEventType[T any](w World) *EventType[T] {
|
||||||
|
// events.NewEventType()
|
||||||
|
ct := NewComponentType[T]()
|
||||||
|
var et T
|
||||||
|
name := reflect.TypeOf(et).Name()
|
||||||
|
return &EventType[T]{
|
||||||
|
name,
|
||||||
|
ct,
|
||||||
|
w,
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user