commit 3b105865902bec559b56ee6222e9061a5ebabf19 Author: walker Date: Fri Aug 4 11:02:08 2023 +0800 初始版本 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..553e134 --- /dev/null +++ b/.gitignore @@ -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 diff --git a/common_component.go b/common_component.go new file mode 100644 index 0000000..9fa0d4b --- /dev/null +++ b/common_component.go @@ -0,0 +1,9 @@ +package ecs + +type Id string + +var IdComp = NewComponentType[Id]() + +// func IdComp() *ComponentType[Id] { +// return NewComponentType[Id]() +// } diff --git a/component.go b/component.go new file mode 100644 index 0000000..fe302a4 --- /dev/null +++ b/component.go @@ -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) +} diff --git a/ecs_test.go b/ecs_test.go new file mode 100644 index 0000000..3d83358 --- /dev/null +++ b/ecs_test.go @@ -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) +} diff --git a/entity.go b/entity.go new file mode 100644 index 0000000..352feca --- /dev/null +++ b/entity.go @@ -0,0 +1,9 @@ +package ecs + +import "github.com/yohamta/donburi" + +type ( + Entity struct { + donburi.Entity + } +) diff --git a/entry.go b/entry.go new file mode 100644 index 0000000..d66c848 --- /dev/null +++ b/entry.go @@ -0,0 +1,7 @@ +package ecs + +import "github.com/yohamta/donburi" + +type Entry struct { + *donburi.Entry +} diff --git a/events.go b/events.go new file mode 100644 index 0000000..e70b54e --- /dev/null +++ b/events.go @@ -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) +) diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..b58e068 --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module joylink.club/ecs + +go 1.20 + +require github.com/yohamta/donburi v1.3.8 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..9ac4ec0 --- /dev/null +++ b/go.sum @@ -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= diff --git a/query.go b/query.go new file mode 100644 index 0000000..3ec778a --- /dev/null +++ b/query.go @@ -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 +} diff --git a/system.go b/system.go new file mode 100644 index 0000000..0890e2f --- /dev/null +++ b/system.go @@ -0,0 +1,5 @@ +package ecs + +type ISystem interface { + Update(w World) +} diff --git a/world.go b/world.go new file mode 100644 index 0000000..4110b07 --- /dev/null +++ b/world.go @@ -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, + } +}