├── .gitignore ├── LICENSE ├── README.md ├── chunk.go ├── collection_iter.go ├── component.go ├── component_collection.go ├── component_getter.go ├── component_meta.go ├── component_meta_test.go ├── component_operate_task.go ├── component_set.go ├── component_set_test.go ├── component_test.go ├── component_utils.go ├── compound.go ├── compound_test.go ├── compound_utils.go ├── concurrent_map.go ├── concurrent_map_test.go ├── direct_api.go ├── entity.go ├── entity_info.go ├── entity_set.go ├── entity_test.go ├── example ├── benchmark-0 │ ├── components.go │ ├── damage_system.go │ ├── game_common.go │ ├── game_ecs.go │ ├── game_normal.go │ ├── go.mod │ ├── main_benchmark_test.go │ ├── main_test.go │ ├── move_system.go │ └── simu_load_system.go └── fake-simple-game-server │ ├── client │ └── fake_client.go │ ├── game │ ├── chat.go │ ├── empty_system.go │ ├── fake_game.go │ ├── move_component.go │ ├── move_system.go │ ├── player_component.go │ ├── position_sync_system.go │ └── session.go │ ├── gm │ └── gm.go │ ├── go.mod │ ├── main.go │ └── network │ └── fake_net.go ├── fixed_string.go ├── fixed_string_test.go ├── fixed_string_utils.go ├── go.mod ├── go.sum ├── goroutine_id.go ├── goroutine_pool.go ├── goroutine_pool_benchmark_test.go ├── internal_type_mock.go ├── logger.go ├── metrics.go ├── optimizer.go ├── optimizer_benchmark_test.go ├── ordered_int_set.go ├── ordered_int_set_test.go ├── res ├── img.png ├── img1.png ├── img2.png ├── img3.png └── img4.png ├── serialize.go ├── shape_getter.go ├── shape_getter_test.go ├── shape_iter.go ├── sparse_array.go ├── system.go ├── system_event.go ├── system_flow.go ├── system_group.go ├── system_group_test.go ├── system_requirement.go ├── unordered_collection.go ├── unordered_collection_test.go ├── unordered_collection_with_id.go ├── unordered_collection_with_id_test.go ├── utility.go ├── utils.go ├── utils_test.go ├── world.go ├── world_async.go ├── world_sync.go └── world_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2023, zllangct 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | 3. Neither the name of the copyright holder nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /chunk.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | const ( 4 | ChunkSize uintptr = 1024 * 16 5 | //ChunkSize int64 = 512 6 | EntitySize uintptr = 8 7 | ) 8 | 9 | const ( 10 | ChunkAddCodeSuccess int = iota 11 | ChunkAddCodeFull 12 | ChunkAddCodeInvalidElement 13 | ) 14 | 15 | type Chunk[T ComponentObject] struct { 16 | data []T 17 | ids map[int64]int64 18 | idx2id map[int64]int64 19 | len int64 20 | max int64 21 | eleSize uintptr 22 | pend uintptr 23 | pre *Chunk[T] 24 | next *Chunk[T] 25 | } 26 | 27 | func NewChunk[T ComponentObject]() *Chunk[T] { 28 | size := TypeOf[T]().Size() 29 | max := ChunkSize / size 30 | c := &Chunk[T]{ 31 | data: make([]T, max, max), 32 | eleSize: size, 33 | max: int64(max), 34 | ids: make(map[int64]int64), 35 | idx2id: make(map[int64]int64), 36 | } 37 | return c 38 | } 39 | 40 | func (c *Chunk[T]) Add(element *T, id int64) (*T, int) { 41 | if uintptr(len(c.data)) >= c.pend+c.eleSize { 42 | c.data[c.pend+1] = *element 43 | } else { 44 | return nil, ChunkAddCodeFull 45 | } 46 | idx := c.len 47 | c.ids[id] = idx 48 | c.idx2id[idx] = id 49 | real := &(c.data[c.pend]) 50 | c.len++ 51 | c.pend += c.eleSize 52 | return real, ChunkAddCodeSuccess 53 | } 54 | 55 | func (c *Chunk[T]) Remove(id int64) { 56 | if id < 0 { 57 | return 58 | } 59 | idx, ok := c.ids[id] 60 | if !ok { 61 | return 62 | } 63 | lastIdx := c.len - 1 64 | lastId := c.idx2id[lastIdx] 65 | 66 | c.ids[lastId] = idx 67 | c.idx2id[idx] = lastId 68 | delete(c.idx2id, lastIdx) 69 | delete(c.ids, id) 70 | 71 | c.data[idx], c.data[lastIdx] = c.data[lastIdx], c.data[idx] 72 | c.len-- 73 | } 74 | 75 | func (c *Chunk[T]) RemoveAndReturn(id int64) *T { 76 | if id < 0 { 77 | return nil 78 | } 79 | idx, ok := c.ids[id] 80 | if !ok { 81 | return nil 82 | } 83 | lastIdx := c.len - 1 84 | lastId := c.idx2id[lastIdx] 85 | c.ids[lastId] = idx 86 | c.idx2id[idx] = lastId 87 | delete(c.idx2id, lastIdx) 88 | delete(c.ids, id) 89 | 90 | c.data[idx], c.data[lastIdx] = c.data[lastIdx], c.data[idx] 91 | r := &(c.data[lastIdx]) 92 | c.len-- 93 | c.pend -= c.eleSize 94 | return r 95 | } 96 | 97 | func (c *Chunk[T]) MoveTo(target *Chunk[T]) []int64 { 98 | moveSize := uintptr(0) 99 | if c.len < c.max { 100 | moveSize = uintptr(c.len) 101 | } else { 102 | moveSize = uintptr(c.max) 103 | } 104 | copy(target.data[target.pend:target.pend+moveSize], c.data[c.pend-moveSize:c.pend]) 105 | 106 | var moved []int64 107 | for i := int64(0); i < int64(moveSize); i++ { 108 | idx := c.len - int64(moveSize) + i 109 | id := c.idx2id[idx] 110 | moved = append(moved, id) 111 | } 112 | 113 | target.pend += moveSize * c.eleSize 114 | c.pend -= moveSize * c.eleSize 115 | target.len += int64(moveSize) 116 | c.len -= int64(moveSize) 117 | 118 | return moved 119 | } 120 | 121 | func (c *Chunk[T]) Get(id int64) *T { 122 | idx, ok := c.ids[id] 123 | if !ok { 124 | return nil 125 | } 126 | return &(c.data[idx]) 127 | } 128 | 129 | func (c *Chunk[T]) GetByIndex(idx int64) *T { 130 | if idx < 0 || idx >= c.len { 131 | return nil 132 | } 133 | return &(c.data[idx]) 134 | } 135 | 136 | func (c *Chunk[T]) Len() int64 { 137 | return c.len 138 | } 139 | -------------------------------------------------------------------------------- /collection_iter.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import "unsafe" 4 | 5 | type Iterator[T any] interface { 6 | Begin() *T 7 | Val() *T 8 | Next() *T 9 | End() bool 10 | } 11 | 12 | type Iter[T any] struct { 13 | head unsafe.Pointer 14 | data []T 15 | len int 16 | offset int 17 | pend uintptr 18 | cur *T 19 | curTemp T 20 | eleSize uintptr 21 | readOnly bool 22 | } 23 | 24 | func EmptyIter[T any]() Iterator[T] { 25 | return &Iter[T]{} 26 | } 27 | 28 | func (i *Iter[T]) End() bool { 29 | if i.offset >= i.len || i.len == 0 { 30 | return true 31 | } 32 | return false 33 | } 34 | 35 | func (i *Iter[T]) Begin() *T { 36 | if i.len != 0 { 37 | i.offset = 0 38 | if i.readOnly { 39 | i.curTemp = i.data[0] 40 | i.cur = &i.curTemp 41 | } else { 42 | i.cur = &(i.data[0]) 43 | } 44 | } 45 | return i.cur 46 | } 47 | 48 | func (i *Iter[T]) Val() *T { 49 | return i.cur 50 | } 51 | 52 | func (i *Iter[T]) Next() *T { 53 | i.offset++ 54 | i.pend += i.eleSize 55 | if !i.End() { 56 | if i.readOnly { 57 | //i.curTemp = i.data[i.offset] 58 | i.curTemp = *(*T)(unsafe.Add(i.head, i.pend)) 59 | i.cur = &i.curTemp 60 | } else { 61 | //i.cur = &(i.data[i.offset]) 62 | i.cur = (*T)(unsafe.Add(i.head, i.pend)) 63 | } 64 | } else { 65 | i.cur = nil 66 | } 67 | return i.cur 68 | } 69 | -------------------------------------------------------------------------------- /component.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "unsafe" 7 | ) 8 | 9 | const ( 10 | h4 = uint8(240) 11 | l4 = uint8(15) 12 | zero = uint8(0) 13 | ) 14 | 15 | type ComponentState uint8 16 | 17 | const ( 18 | ComponentStateInvalid ComponentState = iota 19 | ComponentStateActive 20 | ComponentStateDisable 21 | ) 22 | 23 | type ComponentType uint8 24 | 25 | const ( 26 | ComponentTypeFreeMask ComponentType = 1 << 7 27 | ComponentTypeDisposableMask ComponentType = 1 << 6 28 | ) 29 | 30 | const ( 31 | ComponentTypeNormal ComponentType = 0 32 | ComponentTypeDisposable = 1 | ComponentTypeDisposableMask 33 | ComponentTypeFree = 2 | ComponentTypeFreeMask 34 | ComponentTypeFreeDisposable = 3 | ComponentTypeFreeMask | ComponentTypeDisposableMask 35 | ) 36 | 37 | type EmptyComponent struct { 38 | Component[EmptyComponent] 39 | } 40 | 41 | type IComponent interface { 42 | Owner() Entity 43 | Type() reflect.Type 44 | 45 | setOwner(owner Entity) 46 | setState(state ComponentState) 47 | setIntType(typ uint16) 48 | setSeq(seq uint32) 49 | getState() ComponentState 50 | getIntType() uint16 51 | getComponentType() ComponentType 52 | getPermission() ComponentPermission 53 | check(initializer SystemInitConstraint) 54 | getSeq() uint32 55 | newCollection(meta *ComponentMetaInfo) IComponentSet 56 | addToCollection(ct ComponentType, p unsafe.Pointer) 57 | deleteFromCollection(collection interface{}) 58 | isValidComponentType() bool 59 | 60 | debugAddress() unsafe.Pointer 61 | } 62 | 63 | type ComponentObject interface { 64 | __ComponentIdentification() 65 | } 66 | 67 | type FreeComponentObject interface { 68 | __ComponentIdentification() 69 | } 70 | 71 | type DisposableComponentObject interface { 72 | __ComponentIdentification() 73 | } 74 | 75 | type FreeDisposableComponentObject interface { 76 | __ComponentIdentification() 77 | } 78 | 79 | type ComponentPointer[T ComponentObject] interface { 80 | IComponent 81 | *T 82 | } 83 | 84 | type FreeComponentPointer[T FreeComponentObject] interface { 85 | ComponentPointer[T] 86 | *T 87 | } 88 | 89 | type DisposableComponentPointer[T FreeComponentObject] interface { 90 | ComponentPointer[T] 91 | *T 92 | } 93 | 94 | type FreeDisposableComponentPointer[T FreeComponentObject] interface { 95 | ComponentPointer[T] 96 | *T 97 | } 98 | 99 | type FreeComponent[T ComponentObject] struct { 100 | Component[T] 101 | } 102 | 103 | func (f *FreeComponent[T]) getComponentType() ComponentType { 104 | return ComponentTypeFree 105 | } 106 | 107 | type DisposableComponent[T ComponentObject] struct { 108 | Component[T] 109 | } 110 | 111 | func (f *DisposableComponent[T]) getComponentType() ComponentType { 112 | return ComponentTypeDisposable 113 | } 114 | 115 | type FreeDisposableComponent[T ComponentObject] struct { 116 | Component[T] 117 | } 118 | 119 | func (f *FreeDisposableComponent[T]) getComponentType() ComponentType { 120 | return ComponentTypeFreeDisposable 121 | } 122 | 123 | type componentIdentification struct{} 124 | 125 | func (c componentIdentification) __ComponentIdentification() {} 126 | 127 | type Component[T ComponentObject] struct { 128 | componentIdentification 129 | st uint8 130 | o1 uint8 131 | it uint16 132 | seq uint32 133 | owner Entity 134 | } 135 | 136 | func (c *Component[T]) getComponentType() ComponentType { 137 | return ComponentTypeNormal 138 | } 139 | 140 | func (c *Component[T]) addToCollection(ct ComponentType, p unsafe.Pointer) { 141 | cc := (*ComponentSet[T])(p) 142 | var ins *T 143 | if ct&ComponentTypeFreeMask > 0 { 144 | ins, _ = cc.UnorderedCollection.Add(c.rawInstance()) 145 | } else { 146 | ins = cc.Add(c.rawInstance(), c.owner) 147 | } 148 | 149 | if ins != nil { 150 | (*Component[T])(unsafe.Pointer(ins)).setState(ComponentStateActive) 151 | } 152 | } 153 | 154 | func (c *Component[T]) deleteFromCollection(collection interface{}) { 155 | cc, ok := collection.(*ComponentSet[T]) 156 | if !ok { 157 | Log.Info("add to collection, collecion is nil") 158 | return 159 | } 160 | c.setState(ComponentStateDisable) 161 | cc.Remove(c.owner) 162 | return 163 | } 164 | 165 | func (c *Component[T]) newCollection(meta *ComponentMetaInfo) IComponentSet { 166 | return NewComponentSet[T](meta) 167 | } 168 | 169 | func (c *Component[T]) setOwner(entity Entity) { 170 | c.owner = entity 171 | } 172 | 173 | func (c *Component[T]) rawInstance() *T { 174 | return (*T)(unsafe.Pointer(c)) 175 | } 176 | 177 | func (c *Component[T]) instance() IComponent { 178 | return any((*T)(unsafe.Pointer(c))).(IComponent) 179 | } 180 | 181 | func (c *Component[T]) setState(state ComponentState) { 182 | c.st = (c.st & l4) | (uint8(state) << 4) 183 | } 184 | 185 | func (c *Component[T]) getState() ComponentState { 186 | return ComponentState(c.st & h4 >> 4) 187 | } 188 | 189 | func (c *Component[T]) setType(typ ComponentType) { 190 | c.st = (c.st & h4) | uint8(typ) 191 | } 192 | 193 | func (c *Component[T]) getType() ComponentType { 194 | return ComponentType(c.st & l4) 195 | } 196 | 197 | func (c *Component[T]) setIntType(typ uint16) { 198 | c.it = typ 199 | } 200 | 201 | func (c *Component[T]) getIntType() uint16 { 202 | return c.it 203 | } 204 | 205 | func (c *Component[T]) setSeq(seq uint32) { 206 | c.seq = seq 207 | } 208 | 209 | func (c *Component[T]) getSeq() uint32 { 210 | return c.seq 211 | } 212 | 213 | func (c *Component[T]) invalidate() { 214 | c.setState(ComponentStateInvalid) 215 | } 216 | 217 | func (c *Component[T]) active() { 218 | c.setState(ComponentStateActive) 219 | } 220 | 221 | func (c *Component[T]) Owner() Entity { 222 | return c.owner 223 | } 224 | 225 | func (c *Component[T]) Type() reflect.Type { 226 | return TypeOf[T]() 227 | } 228 | 229 | func (c *Component[T]) getPermission() ComponentPermission { 230 | return ComponentReadWrite 231 | } 232 | 233 | func (c *Component[T]) check(initializer SystemInitConstraint) { 234 | if initializer.isValid() { 235 | panic("out of initialization stage") 236 | } 237 | ins := c.instance() 238 | if !ins.isValidComponentType() { 239 | panic("invalid component type") 240 | } 241 | sys := initializer.getSystem() 242 | sys.World().getOrCreateComponentMetaInfo(ins) 243 | sys.World().getComponentCollection().checkSet(ins) 244 | } 245 | 246 | func (c *Component[T]) isValidComponentType() bool { 247 | typ := c.Type() 248 | fieldNum := typ.NumField() 249 | if fieldNum < 1 { 250 | return false 251 | } 252 | for i := 1; i < fieldNum; i++ { 253 | subType := typ.Field(i).Type 254 | if !IsPureValueType(subType) { 255 | return false 256 | } 257 | } 258 | return true 259 | } 260 | 261 | func (c *Component[T]) debugAddress() unsafe.Pointer { 262 | return unsafe.Pointer(c) 263 | } 264 | 265 | func (c *Component[T]) ToString() string { 266 | return fmt.Sprintf("%+v", c.rawInstance()) 267 | } 268 | -------------------------------------------------------------------------------- /component_collection.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import ( 4 | "reflect" 5 | "sync" 6 | "unsafe" 7 | ) 8 | 9 | type CollectionOperate uint8 10 | 11 | const ( 12 | CollectionOperateNone CollectionOperate = iota 13 | CollectionOperateAdd //add component operation 14 | CollectionOperateDelete //delete component operation 15 | CollectionOperateDeleteAll //delete component by type operation 16 | ) 17 | 18 | type IComponentCollection interface { 19 | operate(op CollectionOperate, entity Entity, component IComponent) 20 | deleteOperate(op CollectionOperate, entity Entity, it uint16) 21 | getTempTasks() []func() 22 | clearDisposable() 23 | getComponentSet(typ reflect.Type) IComponentSet 24 | getComponentSetByIntType(typ uint16) IComponentSet 25 | getCollections() *SparseArray[uint16, IComponentSet] 26 | checkSet(com IComponent) 27 | } 28 | 29 | type ComponentCollection struct { 30 | collections *SparseArray[uint16, IComponentSet] 31 | world *ecsWorld 32 | bucket int64 33 | locks []sync.RWMutex 34 | opLog []map[reflect.Type]*opTaskList 35 | } 36 | 37 | func NewComponentCollection(world *ecsWorld, k int) *ComponentCollection { 38 | cc := &ComponentCollection{ 39 | world: world, 40 | collections: NewSparseArray[uint16, IComponentSet](), 41 | } 42 | 43 | for i := 1; ; i++ { 44 | if c := int64(1 << i); int64(k) < c { 45 | cc.bucket = c - 1 46 | break 47 | } 48 | } 49 | 50 | cc.bucket = 0 51 | 52 | cc.locks = make([]sync.RWMutex, cc.bucket+1) 53 | for i := int64(0); i < cc.bucket+1; i++ { 54 | cc.locks[i] = sync.RWMutex{} 55 | } 56 | cc.opLog = make([]map[reflect.Type]*opTaskList, cc.bucket+1) 57 | cc.initOptTemp() 58 | 59 | return cc 60 | } 61 | 62 | func (c *ComponentCollection) initOptTemp() { 63 | for index := range c.opLog { 64 | c.locks[index].Lock() 65 | c.opLog[index] = make(map[reflect.Type]*opTaskList) 66 | c.locks[index].Unlock() 67 | } 68 | } 69 | 70 | func (c *ComponentCollection) operate(op CollectionOperate, entity Entity, component IComponent) { 71 | var hash int64 72 | switch component.getComponentType() { 73 | case ComponentTypeFree, ComponentTypeFreeDisposable: 74 | hash = int64((uintptr)(unsafe.Pointer(&hash))) & c.bucket 75 | case ComponentTypeNormal, ComponentTypeDisposable: 76 | hash = int64(entity) & c.bucket 77 | } 78 | 79 | typ := component.Type() 80 | newOpt := opTaskPool.Get() 81 | newOpt.target = entity 82 | newOpt.com = component 83 | newOpt.op = op 84 | 85 | b := c.opLog[hash] 86 | 87 | c.locks[hash].Lock() 88 | defer c.locks[hash].Unlock() 89 | 90 | tl, ok := b[typ] 91 | if !ok { 92 | tl = &opTaskList{} 93 | b[typ] = tl 94 | } 95 | 96 | tl.Append(newOpt) 97 | } 98 | 99 | func (c *ComponentCollection) deleteOperate(op CollectionOperate, entity Entity, it uint16) { 100 | var hash int64 101 | meta := c.world.componentMeta.GetComponentMetaInfoByIntType(it) 102 | if meta.componentType&ComponentTypeFreeMask > 0 { 103 | hash = int64((uintptr)(unsafe.Pointer(&hash))) & c.bucket 104 | } else { 105 | hash = int64(entity) & c.bucket 106 | } 107 | 108 | typ := meta.typ 109 | newOpt := opTaskPool.Get() 110 | newOpt.target = entity 111 | newOpt.com = nil 112 | newOpt.op = op 113 | 114 | b := c.opLog[hash] 115 | 116 | c.locks[hash].Lock() 117 | defer c.locks[hash].Unlock() 118 | 119 | tl, ok := b[typ] 120 | if !ok { 121 | tl = &opTaskList{} 122 | b[typ] = tl 123 | } 124 | 125 | tl.Append(newOpt) 126 | } 127 | 128 | func (c *ComponentCollection) clearDisposable() { 129 | disposable := c.world.componentMeta.GetDisposableTypes() 130 | for r, _ := range disposable { 131 | meta := c.world.getComponentMetaInfoByType(r) 132 | if meta.componentType&ComponentTypeFreeMask > 0 { 133 | continue 134 | } 135 | set := c.collections.Get(meta.it) 136 | if set == nil { 137 | return 138 | } 139 | (*set).Range(func(com IComponent) bool { 140 | info, ok := c.world.entities.GetEntityInfo(com.Owner()) 141 | if ok { 142 | info.removeFromCompound(meta.it) 143 | } 144 | return true 145 | }) 146 | 147 | (*set).Clear() 148 | } 149 | } 150 | 151 | func (c *ComponentCollection) clearFree() { 152 | free := c.world.componentMeta.GetFreeTypes() 153 | for r, _ := range free { 154 | meta := c.world.getComponentMetaInfoByType(r) 155 | set := c.collections.Get(meta.it) 156 | if set == nil { 157 | return 158 | } 159 | (*set).Clear() 160 | } 161 | } 162 | 163 | func (c *ComponentCollection) getTempTasks() []func() { 164 | combination := make(map[reflect.Type]*opTaskList) 165 | 166 | for i := 0; i < len(c.opLog); i++ { 167 | c.locks[i].RLock() 168 | for typ, list := range c.opLog[i] { 169 | if list.Len() == 0 { 170 | continue 171 | } 172 | if _, ok := combination[typ]; ok { 173 | combination[typ].Combine(list) 174 | } else { 175 | combination[typ] = list.Clone() 176 | } 177 | list.Reset() 178 | } 179 | 180 | c.locks[i].RUnlock() 181 | } 182 | 183 | var tasks []func() 184 | for typ, list := range combination { 185 | taskList := list 186 | if taskList.Len() == 0 { 187 | continue 188 | } 189 | meta := c.world.getComponentMetaInfoByType(typ) 190 | setp := c.collections.Get(meta.it) 191 | if setp == nil { 192 | c.checkSet(taskList.head.com) 193 | setp = c.collections.Get(meta.it) 194 | } 195 | 196 | fn := func() { 197 | c.opExecute(taskList, *setp) 198 | } 199 | tasks = append(tasks, fn) 200 | } 201 | 202 | fn := func() { 203 | for typ, list := range combination { 204 | meta := c.world.getComponentMetaInfoByType(typ) 205 | for task := list.head; task != nil; task = task.next { 206 | if task.op == CollectionOperateDelete { 207 | continue 208 | } 209 | info, ok := c.world.getEntityInfo(task.target) 210 | if ok { 211 | switch task.op { 212 | case CollectionOperateAdd: 213 | switch task.com.getComponentType() { 214 | case ComponentTypeNormal, ComponentTypeDisposable: 215 | info.addToCompound(meta.it) 216 | } 217 | case CollectionOperateDelete: 218 | switch task.com.getComponentType() { 219 | case ComponentTypeNormal, ComponentTypeDisposable: 220 | info.removeFromCompound(meta.it) 221 | } 222 | } 223 | } 224 | } 225 | } 226 | } 227 | tasks = append(tasks, fn) 228 | return tasks 229 | } 230 | 231 | func (c *ComponentCollection) opExecute(taskList *opTaskList, collection IComponentSet) { 232 | meta := collection.GetElementMeta() 233 | for task := taskList.head; task != nil; task = task.next { 234 | switch task.op { 235 | case CollectionOperateAdd: 236 | task.com.setIntType(meta.it) 237 | task.com.setOwner(task.target) 238 | task.com.addToCollection(task.com.getComponentType(), collection.pointer()) 239 | case CollectionOperateDelete: 240 | if meta.componentType&ComponentTypeFreeMask == 0 { 241 | collection.Remove(task.target) 242 | } 243 | case CollectionOperateDeleteAll: 244 | collection.Clear() 245 | } 246 | } 247 | next := taskList.head 248 | for next != nil { 249 | task := next 250 | next = next.next 251 | opTaskPool.Put(task) 252 | } 253 | taskList.Reset() 254 | } 255 | 256 | func (c *ComponentCollection) getComponentSet(typ reflect.Type) IComponentSet { 257 | meta := c.world.getComponentMetaInfoByType(typ) 258 | return *(c.collections.Get(meta.it)) 259 | } 260 | 261 | func (c *ComponentCollection) getComponentSetByIntType(it uint16) IComponentSet { 262 | return *(c.collections.Get(it)) 263 | } 264 | 265 | func (c *ComponentCollection) getCollections() *SparseArray[uint16, IComponentSet] { 266 | return c.collections 267 | } 268 | 269 | func (c *ComponentCollection) checkSet(com IComponent) { 270 | typ := com.Type() 271 | meta := c.world.getComponentMetaInfoByType(typ) 272 | isExist := c.collections.Exist(meta.it) 273 | if !isExist { 274 | set := com.newCollection(meta) 275 | c.collections.Add(set.GetElementMeta().it, &set) 276 | } 277 | } 278 | -------------------------------------------------------------------------------- /component_getter.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import ( 4 | "reflect" 5 | "unsafe" 6 | ) 7 | 8 | type GetterCache struct { 9 | indices []reflect.Type 10 | values []unsafe.Pointer 11 | } 12 | 13 | func NewGetterCache(initCap ...int) *GetterCache { 14 | cap := 0 15 | if len(initCap) > 0 { 16 | cap = initCap[0] 17 | } 18 | return &GetterCache{ 19 | indices: make([]reflect.Type, 0, cap), 20 | values: make([]unsafe.Pointer, 0, cap), 21 | } 22 | } 23 | 24 | func (g *GetterCache) Add(key reflect.Type, value unsafe.Pointer) { 25 | g.indices = append(g.indices, key) 26 | g.values = append(g.values, value) 27 | } 28 | 29 | func (g *GetterCache) Remove(key reflect.Type) { 30 | idx := -1 31 | for i, t := range g.indices { 32 | if t == key { 33 | idx = i 34 | break 35 | } 36 | } 37 | if idx == -1 { 38 | return 39 | } 40 | // remove from indices 41 | g.indices = append(g.indices[:idx], g.indices[idx+1:]...) 42 | // remove from values 43 | g.values = append(g.values[:idx], g.values[idx+1:]...) 44 | } 45 | 46 | func (g *GetterCache) Get(key reflect.Type) unsafe.Pointer { 47 | for i, t := range g.indices { 48 | if t == key { 49 | return g.values[i] 50 | } 51 | } 52 | return nil 53 | } 54 | 55 | type ComponentGetter[T ComponentObject] struct { 56 | permission ComponentPermission 57 | set *ComponentSet[T] 58 | } 59 | 60 | func NewComponentGetter[T ComponentObject](sys ISystem) *ComponentGetter[T] { 61 | typ := TypeOf[T]() 62 | 63 | r, isRequire := sys.GetRequirements()[typ] 64 | if !isRequire { 65 | return nil 66 | } 67 | getter := &ComponentGetter[T]{} 68 | seti := sys.World().getComponentSet(typ) 69 | if seti == nil { 70 | return nil 71 | } 72 | getter.set = seti.(*ComponentSet[T]) 73 | getter.permission = r.getPermission() 74 | return getter 75 | } 76 | 77 | func (c *ComponentGetter[T]) Get(entity Entity) *T { 78 | if c.permission == ComponentReadOnly { 79 | return &(*c.set.getByEntity(entity)) 80 | } else { 81 | return c.set.getByEntity(entity) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /component_meta.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | func GetComponentMeta[T ComponentObject](world IWorld) *ComponentMetaInfo { 9 | return world.getComponentMetaInfoByType(TypeOf[T]()) 10 | } 11 | 12 | type ComponentMetaInfo struct { 13 | it uint16 14 | componentType ComponentType 15 | o1 uint8 16 | typ reflect.Type 17 | } 18 | 19 | type componentMeta struct { 20 | world *ecsWorld 21 | seq uint16 22 | types map[reflect.Type]uint16 23 | infos *SparseArray[uint16, ComponentMetaInfo] 24 | disposable map[reflect.Type]uint16 25 | free map[reflect.Type]uint16 26 | } 27 | 28 | func NewComponentMeta(world *ecsWorld) *componentMeta { 29 | return &componentMeta{ 30 | world: world, 31 | seq: 0, 32 | types: make(map[reflect.Type]uint16), 33 | infos: NewSparseArray[uint16, ComponentMetaInfo](), 34 | disposable: map[reflect.Type]uint16{}, 35 | free: map[reflect.Type]uint16{}, 36 | } 37 | } 38 | 39 | func (c *componentMeta) CreateComponentMetaInfo(typ reflect.Type, ct ComponentType) *ComponentMetaInfo { 40 | c.world.checkMainThread() 41 | c.seq++ 42 | info := &ComponentMetaInfo{} 43 | info.it = c.seq 44 | info.componentType = ct 45 | info.typ = typ 46 | 47 | c.types[typ] = info.it 48 | info = c.infos.Add(info.it, info) 49 | 50 | if ct&ComponentTypeDisposableMask > 0 { 51 | c.disposable[typ] = info.it 52 | } 53 | if ct&ComponentTypeFreeMask > 0 { 54 | c.free[typ] = info.it 55 | } 56 | 57 | return info 58 | } 59 | 60 | func (c *componentMeta) GetDisposableTypes() map[reflect.Type]uint16 { 61 | return c.disposable 62 | } 63 | 64 | func (c *componentMeta) GetFreeTypes() map[reflect.Type]uint16 { 65 | return c.free 66 | } 67 | 68 | func (c *componentMeta) Exist(typ reflect.Type) bool { 69 | _, ok := c.types[typ] 70 | return ok 71 | } 72 | 73 | func (c *componentMeta) GetOrCreateComponentMetaInfo(com IComponent) *ComponentMetaInfo { 74 | it, ok := c.types[com.Type()] 75 | if !ok { 76 | it = c.CreateComponentMetaInfo(com.Type(), com.getComponentType()).it 77 | } 78 | return c.infos.Get(it) 79 | } 80 | 81 | func (c *componentMeta) GetComponentMetaInfoByIntType(it uint16) *ComponentMetaInfo { 82 | info := c.infos.Get(it) 83 | if info == nil { 84 | panic("must register component first") 85 | } 86 | return info 87 | } 88 | 89 | func (c *componentMeta) GetComponentMetaInfoByType(typ reflect.Type) *ComponentMetaInfo { 90 | it, ok := c.types[typ] 91 | if !ok { 92 | panic(fmt.Sprintf("must register component %s first", typ.String())) 93 | } 94 | return c.infos.Get(it) 95 | } 96 | 97 | func (c *componentMeta) ComponentMetaInfoPrint() { 98 | fn := func(m map[reflect.Type]uint16) { 99 | total := len(m) 100 | count := 0 101 | prefix := "│ ├─" 102 | prefix2 := "│ └─" 103 | str := "" 104 | for typ, _ := range m { 105 | str += " " + typ.Name() 106 | count++ 107 | if count%5 == 0 { 108 | if count == total { 109 | Log.Infof("%s%s", prefix2, str) 110 | } else { 111 | Log.Infof("%s%s", prefix, str) 112 | } 113 | str = "" 114 | } 115 | } 116 | if str != "" { 117 | Log.Infof("%s%s", prefix2, str) 118 | str = "" 119 | } 120 | } 121 | 122 | Log.Infof("┌──────────────── # Component Info # ─────────────────") 123 | Log.Infof("├─ Total: %d", len(c.types)) 124 | fn(c.types) 125 | Log.Infof("├─ Disposable: %d", len(c.disposable)) 126 | fn(c.disposable) 127 | Log.Infof("├─ Free: %d", len(c.free)) 128 | fn(c.free) 129 | Log.Infof("└────────────── # Component Info End # ───────────────") 130 | } 131 | -------------------------------------------------------------------------------- /component_meta_test.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func BenchmarkIntTypeAndReflectType(b *testing.B) { 9 | infos := NewSparseArray[uint16, ComponentMetaInfo]() 10 | infos.Add(0, &ComponentMetaInfo{}) 11 | 12 | m1 := map[uint16]struct{}{1: {}} 13 | m2 := map[reflect.Type]struct{}{reflect.TypeOf(0): {}} 14 | 15 | b.Run("int type", func(b *testing.B) { 16 | for i := 0; i < b.N; i++ { 17 | typ := *infos.Get(0) 18 | _ = m1[typ.it] 19 | } 20 | }) 21 | b.Run("reflect type", func(b *testing.B) { 22 | for i := 0; i < b.N; i++ { 23 | typ := reflect.TypeOf(0) 24 | _ = m2[typ] 25 | } 26 | }) 27 | } 28 | -------------------------------------------------------------------------------- /component_operate_task.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | type opTask struct { 8 | target Entity 9 | com IComponent 10 | op CollectionOperate 11 | next *opTask 12 | } 13 | 14 | func (o *opTask) Reset() { 15 | o.target = 0 16 | o.com = nil 17 | o.op = CollectionOperateNone 18 | o.next = nil 19 | } 20 | 21 | type opTaskList struct { 22 | len int 23 | head *opTask 24 | tail *opTask 25 | } 26 | 27 | func (o *opTaskList) Clone() *opTaskList { 28 | clone := *o 29 | return &clone 30 | } 31 | 32 | func (o *opTaskList) Len() int { 33 | return o.len 34 | } 35 | 36 | func (o *opTaskList) Combine(list *opTaskList) { 37 | if o.head == nil { 38 | o.head = list.head 39 | o.tail = list.tail 40 | } else { 41 | o.tail.next = list.head 42 | o.tail = list.tail 43 | } 44 | o.len += list.len 45 | } 46 | 47 | func (o *opTaskList) Append(task *opTask) { 48 | if o.head == nil { 49 | o.head = task 50 | o.tail = task 51 | } else { 52 | o.tail.next = task 53 | o.tail = task 54 | } 55 | o.len++ 56 | } 57 | 58 | func (o *opTaskList) Reset() { 59 | o.len = 0 60 | o.head = nil 61 | o.tail = nil 62 | } 63 | 64 | var opTaskPool = newTaskPool() 65 | 66 | type taskPool struct { 67 | pool sync.Pool 68 | } 69 | 70 | func newTaskPool() *taskPool { 71 | return &taskPool{ 72 | pool: sync.Pool{ 73 | New: func() interface{} { 74 | return new(opTask) 75 | }, 76 | }, 77 | } 78 | } 79 | 80 | func (p *taskPool) Get() *opTask { 81 | v := p.pool.Get() 82 | if v == nil { 83 | return &opTask{} 84 | } 85 | return v.(*opTask) 86 | } 87 | 88 | func (p *taskPool) Put(t *opTask) { 89 | t.Reset() 90 | p.pool.Put(t) 91 | } 92 | -------------------------------------------------------------------------------- /component_set.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import ( 4 | "sort" 5 | "unsafe" 6 | ) 7 | 8 | type IComponentSet interface { 9 | Len() int 10 | Range(fn func(com IComponent) bool) 11 | Clear() 12 | GetByEntity(entity Entity) any 13 | GetElementMeta() *ComponentMetaInfo 14 | GetComponent(entity Entity) IComponent 15 | GetComponentRaw(entity Entity) unsafe.Pointer 16 | Remove(entity Entity) 17 | Sort() 18 | 19 | getPointerByIndex(index int64) unsafe.Pointer 20 | changeCount() int64 21 | changeReset() 22 | pointer() unsafe.Pointer 23 | getPointerByEntity(entity Entity) unsafe.Pointer 24 | } 25 | 26 | type ComponentSet[T ComponentObject] struct { 27 | SparseArray[int32, T] 28 | change int64 29 | meta *ComponentMetaInfo 30 | } 31 | 32 | func NewComponentSet[T ComponentObject](meta *ComponentMetaInfo, initSize ...int) *ComponentSet[T] { 33 | c := &ComponentSet[T]{ 34 | SparseArray: *NewSparseArray[int32, T](initSize...), 35 | meta: meta, 36 | } 37 | return c 38 | } 39 | 40 | func (c *ComponentSet[T]) Add(element *T, entity Entity) *T { 41 | index := entity.ToRealID().index 42 | data := c.SparseArray.Add(index, element) 43 | if data == nil { 44 | return nil 45 | } 46 | c.change++ 47 | return data 48 | } 49 | 50 | func (c *ComponentSet[T]) remove(entity Entity) *T { 51 | index := entity.ToRealID().index 52 | return c.SparseArray.Remove(index) 53 | } 54 | 55 | func (c *ComponentSet[T]) Remove(entity Entity) { 56 | data := c.remove(entity) 57 | if data == nil { 58 | return 59 | } 60 | c.change++ 61 | } 62 | 63 | func (c *ComponentSet[T]) RemoveAndReturn(entity Entity) *T { 64 | cpy := *c.remove(entity) 65 | return &cpy 66 | } 67 | 68 | func (c *ComponentSet[T]) getByEntity(entity Entity) *T { 69 | return c.SparseArray.Get(entity.ToRealID().index) 70 | } 71 | 72 | func (c *ComponentSet[T]) getPointerByEntity(entity Entity) unsafe.Pointer { 73 | return unsafe.Pointer(c.getByEntity(entity)) 74 | } 75 | 76 | func (c *ComponentSet[T]) GetByEntity(entity Entity) any { 77 | return c.getByEntity(entity) 78 | } 79 | 80 | func (c *ComponentSet[T]) Get(entity Entity) *T { 81 | return c.getByEntity(entity) 82 | } 83 | 84 | func (c *ComponentSet[T]) pointer() unsafe.Pointer { 85 | return unsafe.Pointer(c) 86 | } 87 | 88 | func (c *ComponentSet[T]) changeCount() int64 { 89 | return c.change 90 | } 91 | 92 | func (c *ComponentSet[T]) changeReset() { 93 | c.change = 0 94 | } 95 | 96 | func (c *ComponentSet[T]) Sort() { 97 | if c.changeCount() == 0 { 98 | return 99 | } 100 | var zeroSeq = SeqMax 101 | seq2id := map[uint32]int64{} 102 | var cp *Component[T] 103 | for i := int64(0); i < int64(c.Len()); i++ { 104 | cp = (*Component[T])(unsafe.Pointer(&(c.data[i]))) 105 | if cp.seq == 0 { 106 | zeroSeq-- 107 | cp.seq = zeroSeq 108 | } 109 | seq2id[cp.seq] = cp.owner.ToInt64() 110 | } 111 | sort.Slice(c.data, func(i, j int) bool { 112 | return (*Component[T])(unsafe.Pointer(&(c.data[i]))).seq < (*Component[T])(unsafe.Pointer(&(c.data[j]))).seq 113 | }) 114 | for i := int32(0); i < int32(c.Len()); i++ { 115 | cp = (*Component[T])(unsafe.Pointer(&(c.data[i]))) 116 | c.indices[cp.owner.ToRealID().index] = i + 1 117 | } 118 | c.changeReset() 119 | } 120 | 121 | func (c *ComponentSet[T]) GetComponent(entity Entity) IComponent { 122 | return c.GetByEntity(entity).(IComponent) 123 | } 124 | 125 | func (c *ComponentSet[T]) GetComponentRaw(entity Entity) unsafe.Pointer { 126 | return unsafe.Pointer(c.getByEntity(entity)) 127 | } 128 | 129 | func (c *ComponentSet[T]) getPointerByIndex(index int64) unsafe.Pointer { 130 | return unsafe.Pointer(c.SparseArray.UnorderedCollection.Get(index)) 131 | } 132 | 133 | func (c *ComponentSet[T]) GetElementMeta() *ComponentMetaInfo { 134 | return c.meta 135 | } 136 | 137 | func (c *ComponentSet[T]) Range(fn func(com IComponent) bool) { 138 | c.SparseArray.Range(func(com *T) bool { 139 | return fn(any(com).(IComponent)) 140 | }) 141 | } 142 | 143 | func NewComponentSetIterator[T ComponentObject](collection *ComponentSet[T], readOnly ...bool) Iterator[T] { 144 | iter := &Iter[T]{ 145 | data: collection.data, 146 | len: collection.Len(), 147 | eleSize: collection.eleSize, 148 | offset: 0, 149 | } 150 | if len(readOnly) > 0 { 151 | iter.readOnly = readOnly[0] 152 | } 153 | if iter.len != 0 { 154 | iter.head = unsafe.Pointer(&collection.data[0]) 155 | if iter.readOnly { 156 | iter.curTemp = collection.data[0] 157 | iter.cur = &iter.curTemp 158 | } else { 159 | iter.cur = &(collection.data[0]) 160 | } 161 | } 162 | 163 | return iter 164 | } 165 | -------------------------------------------------------------------------------- /component_set_test.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import ( 4 | "math/rand" 5 | "testing" 6 | ) 7 | 8 | func TestComponentSet_Sort(t *testing.T) { 9 | //准备数据 10 | caseCount := 50 11 | var srcList []__unorderedCollection_Test_item 12 | for i := 0; i < caseCount; i++ { 13 | srcList = append(srcList, __unorderedCollection_Test_item{ 14 | Component: Component[__unorderedCollection_Test_item]{ 15 | seq: uint32(caseCount - i), 16 | owner: Entity(i), 17 | }, 18 | ItemID: int64(i), 19 | Arr: [3]int{1, 2, 3}, 20 | }) 21 | } 22 | 23 | //创建容器(无序数据集) 24 | c := NewComponentSet[__unorderedCollection_Test_item](&ComponentMetaInfo{}) 25 | 26 | //添加数据 27 | for i := 0; i < caseCount; i++ { 28 | _ = c.Add(&srcList[i], srcList[i].Owner()) 29 | } 30 | 31 | i := 0 32 | c.Range(func(item IComponent) bool { 33 | if item.(*__unorderedCollection_Test_item).seq != uint32(caseCount-i) { 34 | t.Errorf("sort error, want %d, got %d", caseCount, item.(*__unorderedCollection_Test_item).seq) 35 | return false 36 | } 37 | i++ 38 | return true 39 | }) 40 | 41 | //排序 42 | c.Sort() 43 | 44 | //验证 45 | i = 1 46 | c.Range(func(item IComponent) bool { 47 | if item.(*__unorderedCollection_Test_item).seq != uint32(i) { 48 | t.Errorf("sort error, want %d, got %d", i, item.(*__unorderedCollection_Test_item).seq) 49 | return false 50 | } 51 | i++ 52 | return true 53 | }) 54 | } 55 | 56 | func TestNewComponentSet(t *testing.T) { 57 | cs := NewComponentSet[__unorderedCollection_Test_item](&ComponentMetaInfo{}) 58 | if cs.GetElementMeta().it != 0 { 59 | t.Error("element meta error") 60 | } 61 | } 62 | 63 | func BenchmarkComponentSet_Read(b *testing.B) { 64 | c := NewComponentSet[__unorderedCollection_Test_item](&ComponentMetaInfo{}) 65 | var ids []int64 66 | total := 1000000 67 | for n := 0; n < total; n++ { 68 | item := &__unorderedCollection_Test_item{ 69 | ItemID: int64(n), 70 | } 71 | _ = c.Add(item, Entity(n)) 72 | ids = append(ids, int64(n+1)) 73 | } 74 | 75 | seq := make([]int, total) 76 | r := make([]int, total) 77 | 78 | for i := 0; i < total; i++ { 79 | seq[i] = i 80 | r[i] = i 81 | } 82 | rand.Shuffle(len(r), func(i, j int) { 83 | r[i], r[j] = r[j], r[i] 84 | }) 85 | 86 | b.ResetTimer() 87 | 88 | b.Run("sequence", func(b *testing.B) { 89 | for n := 0; n < b.N; n++ { 90 | _ = c.Get(Entity(seq[n%total])) 91 | } 92 | }) 93 | b.Run("random", func(b *testing.B) { 94 | for n := 0; n < b.N; n++ { 95 | _ = c.Get(Entity(r[n%total])) 96 | } 97 | }) 98 | } 99 | -------------------------------------------------------------------------------- /component_test.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import "testing" 4 | 5 | func TestComponent_isValidComponentType(t *testing.T) { 6 | type C1 struct { 7 | Component[C1] 8 | Field1 int 9 | Field2 struct { 10 | Field1 int 11 | } 12 | } 13 | 14 | type C2 struct { 15 | Component[C2] 16 | Field1 string 17 | } 18 | 19 | type C3 struct { 20 | Component[C3] 21 | Field1 *int 22 | } 23 | 24 | type C4 struct { 25 | Component[C4] 26 | Field1 int 27 | Field2 struct { 28 | Field1 *int 29 | } 30 | } 31 | 32 | type C5 struct { 33 | Component[C5] 34 | Field1 int 35 | Field2 struct { 36 | Field1 struct { 37 | Field1 int 38 | } 39 | Field2 uint32 40 | } 41 | Field3 FixedString[Fixed5] 42 | } 43 | 44 | tests := []struct { 45 | name string 46 | c IComponent 47 | want bool 48 | }{ 49 | { 50 | name: "Test1", 51 | c: &C1{}, 52 | want: true, 53 | }, 54 | { 55 | name: "Test2", 56 | c: &C2{}, 57 | want: false, 58 | }, 59 | { 60 | name: "Test3", 61 | c: &C3{}, 62 | want: false, 63 | }, 64 | { 65 | name: "Test4", 66 | c: &C4{}, 67 | want: false, 68 | }, 69 | { 70 | name: "Test5", 71 | c: &C5{}, 72 | want: true, 73 | }, 74 | } 75 | for _, tt := range tests { 76 | t.Run(tt.name, func(t *testing.T) { 77 | if got := tt.c.isValidComponentType(); got != tt.want { 78 | t.Errorf("isValidComponentType() = %v, want %v", got, tt.want) 79 | } 80 | }) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /component_utils.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import ( 4 | "encoding/json" 5 | "reflect" 6 | ) 7 | 8 | func GetType[T ComponentObject]() reflect.Type { 9 | return TypeOf[T]() 10 | } 11 | 12 | func ObjectToString(in interface{}) string { 13 | b, err := json.Marshal(in) 14 | if err != nil { 15 | return err.Error() 16 | } 17 | return string(b) 18 | } 19 | -------------------------------------------------------------------------------- /compound.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | type Compound = OrderedIntSet[uint16] 4 | 5 | func NewCompound(initCap ...int) Compound { 6 | cap := 0 7 | if len(initCap) > 0 { 8 | cap = initCap[0] 9 | } 10 | return make(Compound, 0, cap) 11 | } 12 | -------------------------------------------------------------------------------- /compound_test.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "os" 7 | "testing" 8 | ) 9 | 10 | func Test_getCompoundType(t *testing.T) { 11 | return 12 | filePath := "./compound_utils.go" 13 | file, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE, 0666) 14 | if err != nil { 15 | fmt.Println("文件打开失败", err) 16 | } 17 | defer file.Close() 18 | write := bufio.NewWriter(file) 19 | h1 := ` 20 | package ecs 21 | 22 | import ( 23 | "unsafe" 24 | ) 25 | 26 | func getCompoundType(compound Compound) interface{} { 27 | length := len(compound) 28 | if length == 0 || length > 255 { 29 | return nil 30 | } 31 | switch length {` 32 | write.WriteString(h1) 33 | h2 := ` 34 | case %d: 35 | return *(*[%d]uint16)(unsafe.Pointer(&compound[0]))` 36 | for i := 1; i < 256; i++ { 37 | write.WriteString(fmt.Sprintf(h2, i, i)) 38 | } 39 | 40 | h3 := ` 41 | } 42 | 43 | return nil 44 | }` 45 | write.WriteString(h3) 46 | write.Flush() 47 | } 48 | 49 | func TestCompound_find(t *testing.T) { 50 | type args struct { 51 | it uint16 52 | } 53 | tests := []struct { 54 | name string 55 | c Compound 56 | args args 57 | want int 58 | }{ 59 | { 60 | name: "1", 61 | c: Compound{1, 3, 5}, 62 | args: args{it: 3}, 63 | want: 1, 64 | }, 65 | } 66 | for _, tt := range tests { 67 | t.Run(tt.name, func(t *testing.T) { 68 | if got := tt.c.Find(tt.args.it); got != tt.want { 69 | t.Errorf("Find() = %v, want %v", got, tt.want) 70 | } 71 | }) 72 | } 73 | 74 | } 75 | 76 | func TestCompound_insertIndex(t *testing.T) { 77 | type args struct { 78 | it uint16 79 | } 80 | tests := []struct { 81 | name string 82 | c Compound 83 | args args 84 | want int 85 | }{ 86 | { 87 | name: "1", 88 | c: Compound{1, 3, 4, 6, 7}, 89 | args: args{it: 5}, 90 | want: 3, 91 | }, 92 | { 93 | name: "2", 94 | c: Compound{1, 3, 4, 6, 9, 10}, 95 | args: args{it: 5}, 96 | want: 3, 97 | }, 98 | { 99 | name: "3", 100 | c: Compound{2, 3, 5, 5, 6}, 101 | args: args{it: 1}, 102 | want: 0, 103 | }, 104 | { 105 | name: "4", 106 | c: Compound{1, 2, 3, 5, 7, 8}, 107 | args: args{it: 6}, 108 | want: 4, 109 | }, 110 | { 111 | name: "5", 112 | c: Compound{1, 2, 3, 4, 5, 6}, 113 | args: args{it: 7}, 114 | want: 6, 115 | }, 116 | { 117 | name: "6", 118 | c: Compound{1, 2, 3, 4, 5, 6}, 119 | args: args{it: 3}, 120 | want: -1, 121 | }, 122 | } 123 | for _, tt := range tests { 124 | t.Run(tt.name, func(t *testing.T) { 125 | if got := tt.c.InsertIndex(tt.args.it); got != tt.want { 126 | t.Errorf("insertIndex() = %v, want %v", got, tt.want) 127 | } 128 | }) 129 | } 130 | } 131 | 132 | func TestCompound_Add(t *testing.T) { 133 | type args struct { 134 | it uint16 135 | } 136 | tests := []struct { 137 | name string 138 | c Compound 139 | args args 140 | wantErr bool 141 | }{ 142 | { 143 | name: "1", 144 | c: Compound{1, 2, 4, 5, 6}, 145 | args: args{it: 3}, 146 | wantErr: false, 147 | }, 148 | } 149 | for _, tt := range tests { 150 | t.Run(tt.name, func(t *testing.T) { 151 | if ok := tt.c.Add(tt.args.it); (ok != true) != tt.wantErr { 152 | t.Errorf("Add() error = %v, wantErr %v", ok, tt.wantErr) 153 | } 154 | }) 155 | } 156 | } 157 | 158 | func TestCompound_Remove(t *testing.T) { 159 | type args struct { 160 | it uint16 161 | } 162 | tests := []struct { 163 | name string 164 | c Compound 165 | args args 166 | }{ 167 | { 168 | name: "1", 169 | c: Compound{1, 2, 3, 4, 5, 6}, 170 | args: args{it: 3}, 171 | }, 172 | } 173 | for _, tt := range tests { 174 | t.Run(tt.name, func(t *testing.T) { 175 | tt.c.Remove(tt.args.it) 176 | }) 177 | } 178 | } 179 | 180 | func BenchmarkCompound_Add(b *testing.B) { 181 | c := Compound{} 182 | for i := 0; i < b.N; i++ { 183 | c.Add(uint16(i % 65535)) 184 | } 185 | } 186 | 187 | const ( 188 | CompoundSize = 20 189 | ) 190 | 191 | func BenchmarkCompound_Find(b *testing.B) { 192 | c := Compound{} 193 | for i := 0; i < CompoundSize; i++ { 194 | c.Add(uint16(i % CompoundSize)) 195 | } 196 | b.ResetTimer() 197 | for i := 0; i < b.N; i++ { 198 | c.Find(uint16(i % CompoundSize)) 199 | } 200 | } 201 | 202 | func BenchmarkCompound_MapFind(b *testing.B) { 203 | m := map[uint16]struct{}{} 204 | for i := 0; i < CompoundSize; i++ { 205 | m[(uint16(i % CompoundSize))] = struct{}{} 206 | } 207 | b.ResetTimer() 208 | for i := 0; i < b.N; i++ { 209 | _, ok := m[(uint16(i % CompoundSize))] 210 | _ = ok 211 | } 212 | } 213 | 214 | func BenchmarkCompound_BigMapFind(b *testing.B) { 215 | m := map[int]struct{}{} 216 | for i := 0; i < 50000; i++ { 217 | m[i] = struct{}{} 218 | } 219 | b.ResetTimer() 220 | for i := 0; i < b.N; i++ { 221 | _, ok := m[i%50000] 222 | _ = ok 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /concurrent_map.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import ( 4 | "sync" 5 | "sync/atomic" 6 | "unsafe" 7 | ) 8 | 9 | type Map[K comparable, V any] struct { 10 | mu sync.Mutex 11 | read atomic.Value 12 | dirty map[K]*entry[V] 13 | misses int 14 | } 15 | 16 | type readOnly[K comparable, V any] struct { 17 | m map[K]*entry[V] 18 | amended bool 19 | } 20 | 21 | var expunged = unsafe.Pointer(new(interface{})) 22 | 23 | type entry[V any] struct { 24 | p unsafe.Pointer 25 | } 26 | 27 | func newEntry[V any](i V) *entry[V] { 28 | return &entry[V]{p: unsafe.Pointer(&i)} 29 | } 30 | 31 | func (m *Map[K, V]) Load(key K) (value V, ok bool) { 32 | read, _ := m.read.Load().(readOnly[K, V]) 33 | e, ok := read.m[key] 34 | if !ok && read.amended { 35 | m.mu.Lock() 36 | read, _ = m.read.Load().(readOnly[K, V]) 37 | e, ok = read.m[key] 38 | if !ok && read.amended { 39 | e, ok = m.dirty[key] 40 | m.missLocked() 41 | } 42 | m.mu.Unlock() 43 | } 44 | if !ok { 45 | return *new(V), false 46 | } 47 | return e.load() 48 | } 49 | 50 | func (e *entry[V]) load() (value V, ok bool) { 51 | p := atomic.LoadPointer(&e.p) 52 | if p == nil || p == expunged { 53 | return *new(V), false 54 | } 55 | return *(*V)(p), true 56 | } 57 | 58 | func (m *Map[K, V]) Store(key K, value V) { 59 | read, _ := m.read.Load().(readOnly[K, V]) 60 | if e, ok := read.m[key]; ok && e.tryStore(&value) { 61 | return 62 | } 63 | 64 | m.mu.Lock() 65 | read, _ = m.read.Load().(readOnly[K, V]) 66 | if e, ok := read.m[key]; ok { 67 | if e.unexpungeLocked() { 68 | m.dirty[key] = e 69 | } 70 | e.storeLocked(&value) 71 | } else if e, ok := m.dirty[key]; ok { 72 | e.storeLocked(&value) 73 | } else { 74 | if !read.amended { 75 | m.dirtyLocked() 76 | m.read.Store(readOnly[K, V]{m: read.m, amended: true}) 77 | } 78 | m.dirty[key] = newEntry[V](value) 79 | } 80 | m.mu.Unlock() 81 | } 82 | 83 | func (e *entry[V]) tryStore(i *V) bool { 84 | for { 85 | p := atomic.LoadPointer(&e.p) 86 | if p == expunged { 87 | return false 88 | } 89 | if atomic.CompareAndSwapPointer(&e.p, p, unsafe.Pointer(i)) { 90 | return true 91 | } 92 | } 93 | } 94 | 95 | func (e *entry[V]) unexpungeLocked() (wasExpunged bool) { 96 | return atomic.CompareAndSwapPointer(&e.p, expunged, nil) 97 | } 98 | 99 | func (e *entry[V]) storeLocked(i *V) { 100 | atomic.StorePointer(&e.p, unsafe.Pointer(i)) 101 | } 102 | 103 | func (m *Map[K, V]) LoadOrStore(key K, value V) (actual V, loaded bool) { 104 | read, _ := m.read.Load().(readOnly[K, V]) 105 | if e, ok := read.m[key]; ok { 106 | actual, loaded, ok := e.tryLoadOrStore(value) 107 | if ok { 108 | return actual, loaded 109 | } 110 | } 111 | 112 | m.mu.Lock() 113 | read, _ = m.read.Load().(readOnly[K, V]) 114 | if e, ok := read.m[key]; ok { 115 | if e.unexpungeLocked() { 116 | m.dirty[key] = e 117 | } 118 | actual, loaded, _ = e.tryLoadOrStore(value) 119 | } else if e, ok := m.dirty[key]; ok { 120 | actual, loaded, _ = e.tryLoadOrStore(value) 121 | m.missLocked() 122 | } else { 123 | if !read.amended { 124 | m.dirtyLocked() 125 | m.read.Store(readOnly[K, V]{m: read.m, amended: true}) 126 | } 127 | m.dirty[key] = newEntry(value) 128 | actual, loaded = value, false 129 | } 130 | m.mu.Unlock() 131 | 132 | return actual, loaded 133 | } 134 | 135 | func (e *entry[V]) tryLoadOrStore(i V) (actual V, loaded, ok bool) { 136 | p := atomic.LoadPointer(&e.p) 137 | if p == expunged { 138 | return *new(V), false, false 139 | } 140 | if p != nil { 141 | return *(*V)(p), true, true 142 | } 143 | 144 | ic := i 145 | for { 146 | if atomic.CompareAndSwapPointer(&e.p, nil, unsafe.Pointer(&ic)) { 147 | return i, false, true 148 | } 149 | p = atomic.LoadPointer(&e.p) 150 | if p == expunged { 151 | return *new(V), false, false 152 | } 153 | if p != nil { 154 | return *(*V)(p), true, true 155 | } 156 | } 157 | } 158 | 159 | func (m *Map[K, V]) LoadAndDelete(key K) (value V, loaded bool) { 160 | read, _ := m.read.Load().(readOnly[K, V]) 161 | e, ok := read.m[key] 162 | if !ok && read.amended { 163 | m.mu.Lock() 164 | read, _ = m.read.Load().(readOnly[K, V]) 165 | e, ok = read.m[key] 166 | if !ok && read.amended { 167 | e, ok = m.dirty[key] 168 | delete(m.dirty, key) 169 | m.missLocked() 170 | } 171 | m.mu.Unlock() 172 | } 173 | if ok { 174 | return e.delete() 175 | } 176 | return *new(V), false 177 | } 178 | 179 | func (m *Map[K, V]) Delete(key K) { 180 | m.LoadAndDelete(key) 181 | } 182 | 183 | func (e *entry[V]) delete() (value V, ok bool) { 184 | for { 185 | p := atomic.LoadPointer(&e.p) 186 | if p == nil || p == expunged { 187 | return *new(V), false 188 | } 189 | if atomic.CompareAndSwapPointer(&e.p, p, nil) { 190 | return *(*V)(p), true 191 | } 192 | } 193 | } 194 | 195 | func (m *Map[K, V]) Range(f func(key K, value V) bool) bool { 196 | read, _ := m.read.Load().(readOnly[K, V]) 197 | if read.amended { 198 | m.mu.Lock() 199 | read, _ = m.read.Load().(readOnly[K, V]) 200 | if read.amended { 201 | read = readOnly[K, V]{m: m.dirty} 202 | m.read.Store(read) 203 | m.dirty = nil 204 | m.misses = 0 205 | } 206 | m.mu.Unlock() 207 | } 208 | 209 | for k, e := range read.m { 210 | v, ok := e.load() 211 | if !ok { 212 | continue 213 | } 214 | if !f(k, v) { 215 | return false 216 | } 217 | } 218 | return true 219 | } 220 | 221 | func (m *Map[K, V]) missLocked() { 222 | m.misses++ 223 | if m.misses < len(m.dirty) { 224 | return 225 | } 226 | m.read.Store(readOnly[K, V]{m: m.dirty}) 227 | m.dirty = nil 228 | m.misses = 0 229 | } 230 | 231 | func (m *Map[K, V]) dirtyLocked() { 232 | if m.dirty != nil { 233 | return 234 | } 235 | 236 | read, _ := m.read.Load().(readOnly[K, V]) 237 | m.dirty = make(map[K]*entry[V], len(read.m)) 238 | for k, e := range read.m { 239 | if !e.tryExpungeLocked() { 240 | m.dirty[k] = e 241 | } 242 | } 243 | } 244 | 245 | func (e *entry[V]) tryExpungeLocked() (isExpunged bool) { 246 | p := atomic.LoadPointer(&e.p) 247 | for p == nil { 248 | if atomic.CompareAndSwapPointer(&e.p, nil, expunged) { 249 | return true 250 | } 251 | p = atomic.LoadPointer(&e.p) 252 | } 253 | return p == expunged 254 | } 255 | -------------------------------------------------------------------------------- /concurrent_map_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package ecs_test 6 | 7 | import ( 8 | "github.com/zllangct/ecs" 9 | "math/rand" 10 | "runtime" 11 | "sync" 12 | "testing" 13 | ) 14 | 15 | func TestConcurrentRange(t *testing.T) { 16 | const mapSize = 1 << 10 17 | 18 | m := new(ecs.Map[int64, int64]) 19 | for n := int64(1); n <= mapSize; n++ { 20 | m.Store(n, int64(n)) 21 | } 22 | 23 | done := make(chan struct{}) 24 | var wg sync.WaitGroup 25 | defer func() { 26 | close(done) 27 | wg.Wait() 28 | }() 29 | for g := int64(runtime.GOMAXPROCS(0)); g > 0; g-- { 30 | r := rand.New(rand.NewSource(g)) 31 | wg.Add(1) 32 | go func(g int64) { 33 | defer wg.Done() 34 | for i := int64(0); ; i++ { 35 | select { 36 | case <-done: 37 | return 38 | default: 39 | } 40 | for n := int64(1); n < mapSize; n++ { 41 | if r.Int63n(mapSize) == 0 { 42 | m.Store(n, n*i*g) 43 | } else { 44 | m.Load(n) 45 | } 46 | } 47 | } 48 | }(g) 49 | } 50 | 51 | iters := 1 << 10 52 | if testing.Short() { 53 | iters = 16 54 | } 55 | for n := iters; n > 0; n-- { 56 | seen := make(map[int64]bool, mapSize) 57 | 58 | m.Range(func(ki int64, vi int64) bool { 59 | k, v := ki, vi 60 | if v%k != 0 { 61 | t.Fatalf("while Storing multiples of %v, Range saw value %v", k, v) 62 | } 63 | if seen[k] { 64 | t.Fatalf("Range visited key %v twice", k) 65 | } 66 | seen[k] = true 67 | return true 68 | }) 69 | 70 | if len(seen) != mapSize { 71 | t.Fatalf("Range visited %v elements of %v-element Map", len(seen), mapSize) 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /direct_api.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import ( 4 | "reflect" 5 | "unsafe" 6 | ) 7 | 8 | func RegisterSystem[T SystemObject, TP SystemPointer[T]](world IWorld, order ...Order) { 9 | sys := TP(new(T)) 10 | if len(order) > 0 { 11 | sys.setOrder(order[0]) 12 | } 13 | world.registerSystem(sys) 14 | } 15 | 16 | func AddFreeComponent[T FreeComponentObject, TP FreeComponentPointer[T]](world IWorld, component *T) { 17 | world.addFreeComponent(TP(component)) 18 | } 19 | 20 | func GetComponent[T ComponentObject](sys ISystem, entity Entity) *T { 21 | return GetRelated[T](sys, entity) 22 | } 23 | 24 | func GetComponentAll[T ComponentObject](sys ISystem) Iterator[T] { 25 | if sys.getState() == SystemStateInvalid { 26 | return EmptyIter[T]() 27 | } 28 | if !sys.isExecuting() { 29 | return EmptyIter[T]() 30 | } 31 | typ := GetType[T]() 32 | r, ok := sys.GetRequirements()[typ] 33 | if !ok { 34 | return EmptyIter[T]() 35 | } 36 | 37 | c := sys.World().getComponentSet(typ) 38 | if c == nil { 39 | return EmptyIter[T]() 40 | } 41 | return NewComponentSetIterator[T](c.(*ComponentSet[T]), r.getPermission() == ComponentReadOnly) 42 | } 43 | 44 | func GetRelated[T ComponentObject](sys ISystem, entity Entity) *T { 45 | typ := TypeOf[T]() 46 | isRequire := sys.isRequire(typ) 47 | if !isRequire { 48 | return nil 49 | } 50 | var cache *ComponentGetter[T] 51 | cacheMap := sys.getGetterCache() 52 | c := cacheMap.Get(typ) 53 | if c != nil { 54 | cache = (*ComponentGetter[T])(c) 55 | } else { 56 | cache = NewComponentGetter[T](sys) 57 | cacheMap.Add(typ, unsafe.Pointer(cache)) 58 | } 59 | return cache.Get(entity) 60 | } 61 | 62 | func BindUtility[T UtilityObject, TP UtilityPointer[T]](si SystemInitConstraint) { 63 | if si.isValid() { 64 | panic("out of initialization stage") 65 | } 66 | utility := TP(new(T)) 67 | sys := si.getSystem() 68 | utility.setSystem(sys) 69 | utility.setWorld(sys.World()) 70 | sys.setUtility(utility) 71 | sys.World().base().utilities[utility.Type()] = utility 72 | } 73 | 74 | func GetUtility[T UtilityObject, TP UtilityPointer[T]](getter IUtilityGetter) (*T, bool) { 75 | w := getter.getWorld() 76 | if w == nil { 77 | return nil, false 78 | } 79 | u, ok := w.getUtilityForT(TypeOf[T]()) 80 | if !ok { 81 | return nil, false 82 | } 83 | return (*T)(u), true 84 | } 85 | 86 | func TypeOf[T any]() reflect.Type { 87 | ins := (*T)(nil) 88 | return reflect.TypeOf(ins).Elem() 89 | } 90 | -------------------------------------------------------------------------------- /entity.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import ( 4 | "sort" 5 | "unsafe" 6 | ) 7 | 8 | type Entity int64 9 | 10 | func (e Entity) ToInt64() int64 { 11 | return int64(e) 12 | } 13 | 14 | func (e Entity) ToRealID() RealID { 15 | return *(*RealID)(unsafe.Pointer(&e)) 16 | } 17 | 18 | type RealID struct { 19 | index int32 20 | reuse int32 21 | } 22 | 23 | func (r *RealID) ToInt64() int64 { 24 | return *(*int64)(unsafe.Pointer(r)) 25 | } 26 | 27 | func (r *RealID) ToEntity() Entity { 28 | return *(*Entity)(unsafe.Pointer(r)) 29 | } 30 | 31 | type EntityIDGenerator struct { 32 | ids []RealID 33 | free int32 34 | pending int32 35 | len int32 36 | 37 | removeDelay []RealID 38 | delayFree int32 39 | delayCap int32 40 | } 41 | 42 | func NewEntityIDGenerator(initSize int, delayCap int) *EntityIDGenerator { 43 | g := &EntityIDGenerator{} 44 | g.ids = make([]RealID, initSize) 45 | for i := 0; i < len(g.ids); i++ { 46 | g.ids[i].index = int32(i + 1) 47 | } 48 | g.free = 1 49 | g.pending = int32(initSize) 50 | g.len = 0 51 | g.removeDelay = make([]RealID, delayCap) 52 | g.delayCap = int32(delayCap) 53 | g.delayFree = 0 54 | return g 55 | } 56 | 57 | func (e *EntityIDGenerator) NewID() Entity { 58 | id := RealID{} 59 | if e.free == e.pending { 60 | e.ids = append(e.ids, RealID{index: e.free, reuse: 0}) 61 | id = e.ids[e.pending] 62 | e.free++ 63 | e.pending++ 64 | } else { 65 | next := e.ids[e.free].index 66 | e.ids[e.free].index = e.free 67 | id = e.ids[e.free] 68 | e.free = next 69 | } 70 | e.len++ 71 | return id.ToEntity() 72 | } 73 | 74 | func (e *EntityIDGenerator) FreeID(entity Entity) { 75 | e.len-- 76 | 77 | realID := entity.ToRealID() 78 | e.ids[realID.index].index = -1 79 | e.ids[realID.index].reuse++ 80 | 81 | e.removeDelay[e.delayFree] = realID 82 | e.delayFree++ 83 | if e.delayFree >= e.delayCap { 84 | e.delayFlush() 85 | } 86 | if e.pending > 1024 && e.pending < int32(len(e.ids))/2 { 87 | e.ids = e.ids[:e.len*5/8] 88 | } 89 | } 90 | 91 | func (e *EntityIDGenerator) delayFlush() { 92 | sort.Slice(e.removeDelay, func(i, j int) bool { 93 | return e.removeDelay[i].index < e.removeDelay[j].index 94 | }) 95 | lastFree := e.free 96 | nextFree := e.free 97 | if e.free < e.pending { 98 | nextFree = e.ids[e.free].index 99 | } 100 | for i := int32(0); i < e.delayFree; i++ { 101 | tempID := e.removeDelay[i] 102 | if tempID.index < lastFree { 103 | e.ids[tempID.index].index = lastFree 104 | e.free = tempID.index 105 | nextFree = lastFree 106 | lastFree = tempID.index 107 | continue 108 | } else { 109 | for { 110 | if tempID.index < nextFree { 111 | e.ids[lastFree].index = tempID.index 112 | e.ids[tempID.index].index = nextFree 113 | lastFree = tempID.index 114 | break 115 | } else { 116 | lastFree = nextFree 117 | nextFree = e.ids[nextFree].index 118 | } 119 | } 120 | } 121 | } 122 | e.delayFree = 0 123 | } 124 | -------------------------------------------------------------------------------- /entity_info.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | type EntityInfo struct { 4 | entity Entity 5 | compound Compound 6 | } 7 | 8 | func (e *EntityInfo) Destroy(world IWorld) { 9 | for i := 0; i < len(e.compound); i++ { 10 | world.deleteComponentByIntType(e.entity, e.compound[i]) 11 | } 12 | // must be last 13 | world.deleteEntity(e.entity) 14 | } 15 | 16 | func (e *EntityInfo) Entity() Entity { 17 | return e.entity 18 | } 19 | 20 | func (e *EntityInfo) Add(world IWorld, components ...IComponent) { 21 | for _, c := range components { 22 | if !e.compound.Exist(world.getComponentMetaInfoByType(c.Type()).it) { 23 | world.addComponent(e.entity, c) 24 | } 25 | } 26 | } 27 | 28 | func (e *EntityInfo) Has(its ...uint16) bool { 29 | for i := 0; i < len(its); i++ { 30 | if !e.compound.Exist(its[i]) { 31 | return false 32 | } 33 | } 34 | return true 35 | } 36 | 37 | func (e *EntityInfo) HasType(world *ecsWorld, components ...IComponent) bool { 38 | for i := 0; i < len(components); i++ { 39 | if !e.compound.Exist(world.getComponentMetaInfoByType(components[i].Type()).it) { 40 | return false 41 | } 42 | } 43 | return true 44 | } 45 | 46 | func (e *EntityInfo) addToCompound(it uint16) { 47 | e.compound.Add(it) 48 | } 49 | 50 | func (e *EntityInfo) removeFromCompound(it uint16) { 51 | e.compound.Remove(it) 52 | } 53 | 54 | func (e *EntityInfo) Remove(world IWorld, components ...IComponent) { 55 | for _, c := range components { 56 | if e.compound.Exist(world.getComponentMetaInfoByType(c.Type()).it) { 57 | world.deleteComponent(e.entity, c) 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /entity_set.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | type EntitySet struct { 4 | SparseArray[int32, EntityInfo] 5 | } 6 | 7 | func NewEntityCollection() *EntitySet { 8 | return &EntitySet{ 9 | SparseArray: *NewSparseArray[int32, EntityInfo](), 10 | } 11 | } 12 | 13 | func (c *EntitySet) Exist(entity Entity) bool { 14 | index := entity.ToRealID().index 15 | return c.SparseArray.Exist(index) 16 | } 17 | 18 | func (c *EntitySet) GetEntityInfo(entity Entity) (*EntityInfo, bool) { 19 | index := entity.ToRealID().index 20 | info := c.Get(index) 21 | if info == nil { 22 | return nil, false 23 | } 24 | return info, true 25 | } 26 | 27 | func (c *EntitySet) Add(entityInfo EntityInfo) *EntityInfo { 28 | index := entityInfo.entity.ToRealID().index 29 | return c.SparseArray.Add(index, &entityInfo) 30 | } 31 | 32 | func (c *EntitySet) Remove(entity Entity) *EntityInfo { 33 | index := entity.ToRealID().index 34 | return c.SparseArray.Remove(index) 35 | } 36 | -------------------------------------------------------------------------------- /entity_test.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import ( 4 | "math/rand" 5 | "testing" 6 | "time" 7 | "unsafe" 8 | ) 9 | 10 | func TestEntityIDGenerator_NewID(t *testing.T) { 11 | t.Run("test1", func(t *testing.T) { 12 | e := NewEntityIDGenerator(10, 3) 13 | id1 := e.NewID() 14 | id2 := e.NewID() 15 | id3 := e.NewID() 16 | 17 | e.FreeID(id2) 18 | 19 | id4 := e.NewID() 20 | 21 | e.FreeID(id1) 22 | e.FreeID(id4) 23 | e.FreeID(id3) 24 | 25 | var m []Entity 26 | for i := 0; i < 11; i++ { 27 | newID := e.NewID() 28 | m = append(m, newID) 29 | } 30 | 31 | for _, id := range m { 32 | e.FreeID(id) 33 | } 34 | }) 35 | } 36 | 37 | type _EntityTest struct { 38 | seq int32 39 | freeList map[RealID]struct{} 40 | } 41 | 42 | func (e *_EntityTest) NewID() Entity { 43 | id := RealID{} 44 | if len(e.freeList) > 0 { 45 | for i, _ := range e.freeList { 46 | id = i 47 | delete(e.freeList, i) 48 | } 49 | } else { 50 | id = RealID{index: e.seq, reuse: 0} 51 | e.seq++ 52 | } 53 | return id.ToEntity() 54 | } 55 | 56 | func (e *_EntityTest) FreeID(id Entity) { 57 | real := *(*RealID)(unsafe.Pointer(&id)) 58 | e.freeList[real] = struct{}{} 59 | } 60 | 61 | func BenchmarkEntityIDGenerator_New(b *testing.B) { 62 | e := NewEntityIDGenerator(1024, 10) 63 | idmap := map[Entity]struct{}{} 64 | e2 := &_EntityTest{freeList: map[RealID]struct{}{}} 65 | idmap2 := map[Entity]struct{}{} 66 | 67 | for i := 0; i < 10000; i++ { 68 | id := e.NewID() 69 | idmap[id] = struct{}{} 70 | 71 | id = e2.NewID() 72 | idmap2[id] = struct{}{} 73 | } 74 | 75 | b.Run("map", func(b *testing.B) { 76 | for i := 0; i < b.N; i++ { 77 | e.NewID() 78 | } 79 | }) 80 | b.Run("gen", func(b *testing.B) { 81 | for i := 0; i < b.N; i++ { 82 | e2.NewID() 83 | } 84 | }) 85 | } 86 | 87 | func TestEntityIDGenerator_Free(t *testing.T) { 88 | size := 100000 89 | e := NewEntityIDGenerator(1024, 100) 90 | idmap := map[Entity]struct{}{} 91 | for i := 0; i < size; i++ { 92 | id := e.NewID() 93 | idmap[id] = struct{}{} 94 | } 95 | 96 | e2 := &_EntityTest{freeList: map[RealID]struct{}{}} 97 | idmap2 := map[Entity]struct{}{} 98 | for i := 0; i < size; i++ { 99 | id := e2.NewID() 100 | idmap2[id] = struct{}{} 101 | } 102 | 103 | t.Run("gen", func(t *testing.T) { 104 | start := time.Now() 105 | for i, _ := range idmap { 106 | e.FreeID(i) 107 | } 108 | println(time.Since(start).Nanoseconds() / int64(size)) 109 | }) 110 | t.Run("map", func(t *testing.T) { 111 | start := time.Now() 112 | for i, _ := range idmap2 { 113 | e2.FreeID(i) 114 | } 115 | println(time.Since(start).Nanoseconds() / int64(size)) 116 | }) 117 | } 118 | 119 | func BenchmarkEntityIDGenerator_NewFreeRandom(b *testing.B) { 120 | 121 | e := NewEntityIDGenerator(1024, 100) 122 | idmap := map[Entity]struct{}{} 123 | e2 := &_EntityTest{freeList: map[RealID]struct{}{}} 124 | idmap2 := map[Entity]struct{}{} 125 | 126 | for i := 0; i < 1000000; i++ { 127 | id := e.NewID() 128 | idmap[id] = struct{}{} 129 | 130 | id = e2.NewID() 131 | idmap2[id] = struct{}{} 132 | } 133 | 134 | b.Run("map", func(b *testing.B) { 135 | for i := 0; i < b.N; i++ { 136 | r := rand.Intn(100) 137 | if r%2 == 0 { 138 | id := e.NewID() 139 | idmap[id] = struct{}{} 140 | } else { 141 | for i2, _ := range idmap { 142 | e.FreeID(i2) 143 | delete(idmap, i2) 144 | break 145 | } 146 | } 147 | } 148 | }) 149 | b.Run("gen", func(b *testing.B) { 150 | for i := 0; i < b.N; i++ { 151 | r := rand.Intn(100) 152 | if r%2 == 0 { 153 | id := e2.NewID() 154 | idmap[id] = struct{}{} 155 | } else { 156 | for i2, _ := range idmap { 157 | e2.FreeID(i2) 158 | delete(idmap2, i2) 159 | break 160 | } 161 | } 162 | } 163 | }) 164 | } 165 | -------------------------------------------------------------------------------- /example/benchmark-0/components.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/zllangct/ecs" 5 | ) 6 | 7 | type Position struct { 8 | ecs.Component[Position] 9 | X int 10 | Y int 11 | Z int 12 | } 13 | 14 | type Movement struct { 15 | ecs.Component[Movement] 16 | V int 17 | Dir [3]int 18 | } 19 | 20 | type HealthPoint struct { 21 | ecs.Component[HealthPoint] 22 | HP int 23 | } 24 | 25 | type Force struct { 26 | ecs.Component[Force] 27 | AttackRange int 28 | PhysicalBaseAttack int 29 | Strength int 30 | CriticalChange int 31 | CriticalMultiple int 32 | } 33 | 34 | type Action struct { 35 | ecs.DisposableComponent[Action] 36 | ActionType int 37 | } 38 | 39 | type Test1 struct { 40 | ecs.Component[Test1] 41 | Test1 int 42 | } 43 | 44 | type Test2 struct { 45 | ecs.Component[Test2] 46 | Test2 int 47 | } 48 | 49 | type Test3 struct { 50 | ecs.Component[Test3] 51 | Test3 int 52 | } 53 | 54 | type Test4 struct { 55 | ecs.Component[Test4] 56 | Test4 int 57 | } 58 | 59 | type Test5 struct { 60 | ecs.Component[Test5] 61 | Test5 int 62 | } 63 | 64 | type Test6 struct { 65 | ecs.Component[Test6] 66 | Test6 int 67 | } 68 | 69 | type Test7 struct { 70 | ecs.Component[Test7] 71 | Test7 int 72 | } 73 | 74 | type Test8 struct { 75 | ecs.Component[Test8] 76 | Test8 int 77 | } 78 | 79 | type Test9 struct { 80 | ecs.Component[Test9] 81 | Test9 int 82 | } 83 | 84 | type Test10 struct { 85 | ecs.Component[Test10] 86 | Test10 int 87 | } 88 | -------------------------------------------------------------------------------- /example/benchmark-0/damage_system.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "github.com/zllangct/ecs" 6 | "math/rand" 7 | ) 8 | 9 | type Caster struct { 10 | Action *Action 11 | Position *Position 12 | Force *Force 13 | } 14 | 15 | type Target struct { 16 | HealthPoint *HealthPoint 17 | Position *Position 18 | } 19 | 20 | type DamageSystem struct { 21 | ecs.System[DamageSystem] 22 | casterGetter *ecs.Shape[Caster] 23 | targetGetter *ecs.Shape[Target] 24 | } 25 | 26 | func (d *DamageSystem) Init(si ecs.SystemInitConstraint) error { 27 | d.SetRequirements( 28 | si, 29 | //&ecs.ReadOnly[Position]{}, 30 | //&ecs.ReadOnly[Force]{}, 31 | //&ecs.ReadOnly[Action]{}, 32 | &Position{}, 33 | &Force{}, 34 | &Action{}, 35 | &HealthPoint{}) 36 | d.casterGetter = ecs.NewShape[Caster](si).SetGuide(&Action{}) 37 | d.targetGetter = ecs.NewShape[Target](si) 38 | 39 | if !d.casterGetter.IsValid() || !d.targetGetter.IsValid() { 40 | return errors.New("invalid shape getter") 41 | } 42 | return nil 43 | } 44 | 45 | // Update will be called every frame 46 | func (d *DamageSystem) Update(event ecs.Event) { 47 | /* 伤害结算逻辑 48 | - 分析'攻击'行为,我们需要考虑攻击的相关计算公式所需组件,如当前示例中的Force组件,包含基础攻击、力量、 49 | 暴击倍率、暴击率、攻击范围等数据。考虑攻击范围时需要,知道位置相关信息,由Position组件提供数据支持,在全遍历 50 | 所有位置关系时,消耗比较大,可通过AOI优化,减小遍历规模,优化搜索效率,此处示例不做额外处理。 51 | */ 52 | casterIter := d.casterGetter.Get() 53 | targetIter := d.targetGetter.Get() 54 | count := 0 55 | for caster := casterIter.Begin(); !casterIter.End(); caster = casterIter.Next() { 56 | //count++ 57 | for target := targetIter.Begin(); !targetIter.End(); target = targetIter.Next() { 58 | if caster.Action.Owner() == target.HealthPoint.Owner() { 59 | continue 60 | } 61 | 62 | //计算距离 63 | distance := (caster.Position.X-target.Position.X)*(caster.Position.X-target.Position.X) + 64 | (caster.Position.Y-target.Position.Y)*(caster.Position.Y-target.Position.Y) 65 | if distance > caster.Force.AttackRange*caster.Force.AttackRange { 66 | continue 67 | } 68 | 69 | //伤害公式:伤害=(基础攻击+力量)+ 暴击伤害, 暴击伤害=基础攻击 * 2 70 | damage := caster.Force.PhysicalBaseAttack + caster.Force.Strength 71 | critical := 0 72 | if rand.Intn(100) < caster.Force.CriticalChange { 73 | critical = caster.Force.PhysicalBaseAttack * caster.Force.CriticalMultiple 74 | } 75 | damage = damage + critical 76 | //ecs.Log.Infof("Damage:%v", damage) 77 | target.HealthPoint.HP -= damage 78 | if target.HealthPoint.HP < 0 { 79 | target.HealthPoint.HP = 0 80 | } 81 | count++ 82 | } 83 | } 84 | 85 | if count < (PlayerCount)*(PlayerCount-1) { 86 | //panic("count error") 87 | } 88 | 89 | //ecs.Log.Infof("DamageSystem count:%v, frame:%v", count, event.Frame) 90 | } 91 | -------------------------------------------------------------------------------- /example/benchmark-0/game_common.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | const ( 4 | PlayerCount = 200 5 | DummyMaxFor = 3000 6 | ) 7 | -------------------------------------------------------------------------------- /example/benchmark-0/game_ecs.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/zllangct/ecs" 5 | _ "unsafe" 6 | ) 7 | 8 | type GameECS struct { 9 | world *ecs.SyncWorld 10 | entities []ecs.Entity 11 | } 12 | 13 | func (g *GameECS) init(config *ecs.WorldConfig) { 14 | g.world = ecs.NewSyncWorld(config) 15 | 16 | ecs.RegisterSystem[MoveSystem](g.world) 17 | ecs.RegisterSystem[DamageSystem](g.world) 18 | ecs.RegisterSystem[Test1System](g.world) 19 | ecs.RegisterSystem[Test2System](g.world) 20 | ecs.RegisterSystem[Test3System](g.world) 21 | ecs.RegisterSystem[Test4System](g.world) 22 | ecs.RegisterSystem[Test5System](g.world) 23 | ecs.RegisterSystem[Test6System](g.world) 24 | ecs.RegisterSystem[Test7System](g.world) 25 | ecs.RegisterSystem[Test8System](g.world) 26 | ecs.RegisterSystem[Test9System](g.world) 27 | ecs.RegisterSystem[Test10System](g.world) 28 | 29 | DataGenerateECS(g) 30 | } 31 | 32 | func (g *GameECS) attack() { 33 | act := &Action{ 34 | ActionType: 1, 35 | } 36 | for _, entity := range g.entities { 37 | g.world.Add(entity, act) 38 | } 39 | } 40 | 41 | func DataGenerateECS(game *GameECS) { 42 | for i := 0; i < PlayerCount; i++ { 43 | p := &Position{ 44 | X: 0, 45 | Y: 0, 46 | Z: 0, 47 | } 48 | m := &Movement{ 49 | V: 100, 50 | Dir: [3]int{1, 0, 0}, 51 | } 52 | h := &HealthPoint{ 53 | HP: 100, 54 | } 55 | f := &Force{ 56 | AttackRange: 10000, 57 | PhysicalBaseAttack: 10, 58 | } 59 | 60 | t1 := &Test1{} 61 | t2 := &Test2{} 62 | t3 := &Test3{} 63 | t4 := &Test4{} 64 | t5 := &Test5{} 65 | t6 := &Test6{} 66 | t7 := &Test7{} 67 | t8 := &Test8{} 68 | t9 := &Test9{} 69 | t10 := &Test10{} 70 | 71 | e := game.world.NewEntity() 72 | game.world.Add(e, p, m, h, f, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10) 73 | 74 | game.entities = append(game.entities, e) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /example/benchmark-0/game_normal.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "math/rand" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | type Player struct { 10 | rw sync.RWMutex 11 | 12 | ID int64 13 | 14 | X int 15 | Y int 16 | Z int 17 | V int 18 | Dir [3]int 19 | HP int 20 | AttackRange int 21 | PhysicalBaseAttack int 22 | Strength int 23 | CriticalChange int 24 | CriticalMultiple int 25 | ActionType int 26 | Test1 int 27 | Test2 int 28 | Test3 int 29 | Test4 int 30 | Test5 int 31 | Test6 int 32 | Test7 int 33 | Test8 int 34 | Test9 int 35 | Test10 int 36 | } 37 | 38 | type GameNormal struct { 39 | players map[int64]*Player 40 | } 41 | 42 | func (g *GameNormal) init() { 43 | DataGenerateNormal(g) 44 | } 45 | 46 | func DataGenerateNormal(normal *GameNormal) { 47 | players := make(map[int64]*Player) 48 | for i := 0; i < PlayerCount; i++ { 49 | p := &Player{ 50 | ID: int64(i), 51 | X: 0, 52 | Y: 0, 53 | Z: 0, 54 | V: 100, 55 | Dir: [3]int{1, 0, 0}, 56 | HP: 100, 57 | AttackRange: 10000, 58 | PhysicalBaseAttack: 10, 59 | Strength: 0, 60 | CriticalChange: 0, 61 | CriticalMultiple: 0, 62 | ActionType: 1, 63 | } 64 | players[p.ID] = p 65 | } 66 | normal.players = players 67 | } 68 | 69 | func (g *GameNormal) doFrame(parallel bool, frame uint64, delta time.Duration) { 70 | if parallel { 71 | wg := &sync.WaitGroup{} 72 | wg.Add(12) 73 | go func() { 74 | g.DoMoveParallel(delta) 75 | wg.Done() 76 | }() 77 | go func() { 78 | g.DoDamageParallel() 79 | wg.Done() 80 | }() 81 | go func() { 82 | g.SimuLoadParallel1() 83 | wg.Done() 84 | }() 85 | go func() { 86 | g.SimuLoadParallel2() 87 | wg.Done() 88 | }() 89 | go func() { 90 | g.SimuLoadParallel3() 91 | wg.Done() 92 | }() 93 | go func() { 94 | g.SimuLoadParallel4() 95 | wg.Done() 96 | }() 97 | go func() { 98 | g.SimuLoadParallel5() 99 | wg.Done() 100 | }() 101 | go func() { 102 | g.SimuLoadParallel6() 103 | wg.Done() 104 | }() 105 | go func() { 106 | g.SimuLoadParallel7() 107 | wg.Done() 108 | }() 109 | go func() { 110 | g.SimuLoadParallel8() 111 | wg.Done() 112 | }() 113 | go func() { 114 | g.SimuLoadParallel9() 115 | wg.Done() 116 | }() 117 | go func() { 118 | g.SimuLoadParallel10() 119 | wg.Done() 120 | }() 121 | wg.Wait() 122 | } else { 123 | // 移动 124 | g.DoMove(delta) 125 | // 攻击处理 126 | g.DoDamage() 127 | // 模拟其他负载 128 | g.SimuLoad1() 129 | g.SimuLoad2() 130 | g.SimuLoad3() 131 | g.SimuLoad4() 132 | g.SimuLoad5() 133 | g.SimuLoad6() 134 | g.SimuLoad7() 135 | g.SimuLoad8() 136 | g.SimuLoad9() 137 | g.SimuLoad10() 138 | } 139 | } 140 | func (g *GameNormal) SimuLoad1() { 141 | for _, p := range g.players { 142 | for i := 0; i < DummyMaxFor; i++ { 143 | p.Test1 += 1 144 | } 145 | } 146 | } 147 | func (g *GameNormal) SimuLoad2() { 148 | for _, p := range g.players { 149 | for i := 0; i < DummyMaxFor; i++ { 150 | p.Test2 += 1 151 | } 152 | } 153 | } 154 | func (g *GameNormal) SimuLoad3() { 155 | for _, p := range g.players { 156 | for i := 0; i < DummyMaxFor; i++ { 157 | p.Test3 += 1 158 | } 159 | } 160 | } 161 | func (g *GameNormal) SimuLoad4() { 162 | for _, p := range g.players { 163 | for i := 0; i < DummyMaxFor; i++ { 164 | p.Test4 += 1 165 | } 166 | } 167 | } 168 | func (g *GameNormal) SimuLoad5() { 169 | for _, p := range g.players { 170 | for i := 0; i < DummyMaxFor; i++ { 171 | p.Test5 += 1 172 | } 173 | } 174 | } 175 | func (g *GameNormal) SimuLoad6() { 176 | for _, p := range g.players { 177 | for i := 0; i < DummyMaxFor; i++ { 178 | p.Test5 += 1 179 | } 180 | } 181 | } 182 | func (g *GameNormal) SimuLoad7() { 183 | for _, p := range g.players { 184 | for i := 0; i < DummyMaxFor; i++ { 185 | p.Test5 += 1 186 | } 187 | } 188 | } 189 | func (g *GameNormal) SimuLoad8() { 190 | for _, p := range g.players { 191 | for i := 0; i < DummyMaxFor; i++ { 192 | p.Test5 += 1 193 | } 194 | } 195 | } 196 | func (g *GameNormal) SimuLoad9() { 197 | for _, p := range g.players { 198 | for i := 0; i < DummyMaxFor; i++ { 199 | p.Test5 += 1 200 | } 201 | } 202 | } 203 | func (g *GameNormal) SimuLoad10() { 204 | for _, p := range g.players { 205 | for i := 0; i < DummyMaxFor; i++ { 206 | p.Test10 += 1 207 | } 208 | } 209 | } 210 | 211 | func (g *GameNormal) SimuLoadParallel1() { 212 | for _, p := range g.players { 213 | for i := 0; i < DummyMaxFor; i++ { 214 | p.Test1 += 1 215 | } 216 | } 217 | } 218 | func (g *GameNormal) SimuLoadParallel2() { 219 | for _, p := range g.players { 220 | for i := 0; i < DummyMaxFor; i++ { 221 | p.Test2 += 1 222 | } 223 | } 224 | } 225 | func (g *GameNormal) SimuLoadParallel3() { 226 | for _, p := range g.players { 227 | for i := 0; i < DummyMaxFor; i++ { 228 | p.Test3 += 1 229 | } 230 | } 231 | } 232 | func (g *GameNormal) SimuLoadParallel4() { 233 | for _, p := range g.players { 234 | for i := 0; i < DummyMaxFor; i++ { 235 | p.Test4 += 1 236 | } 237 | } 238 | } 239 | func (g *GameNormal) SimuLoadParallel5() { 240 | for _, p := range g.players { 241 | for i := 0; i < DummyMaxFor; i++ { 242 | p.Test5 += 1 243 | } 244 | } 245 | } 246 | func (g *GameNormal) SimuLoadParallel6() { 247 | for _, p := range g.players { 248 | for i := 0; i < DummyMaxFor; i++ { 249 | p.Test6 += 1 250 | } 251 | } 252 | } 253 | func (g *GameNormal) SimuLoadParallel7() { 254 | for _, p := range g.players { 255 | for i := 0; i < DummyMaxFor; i++ { 256 | p.Test7 += 1 257 | } 258 | } 259 | } 260 | func (g *GameNormal) SimuLoadParallel8() { 261 | for _, p := range g.players { 262 | for i := 0; i < DummyMaxFor; i++ { 263 | p.Test8 += 1 264 | } 265 | } 266 | } 267 | func (g *GameNormal) SimuLoadParallel9() { 268 | for _, p := range g.players { 269 | for i := 0; i < DummyMaxFor; i++ { 270 | p.Test9 += 1 271 | } 272 | } 273 | } 274 | func (g *GameNormal) SimuLoadParallel10() { 275 | for _, p := range g.players { 276 | for i := 0; i < DummyMaxFor; i++ { 277 | p.Test10 += 1 278 | } 279 | } 280 | } 281 | 282 | func (g *GameNormal) DoMove(delta time.Duration) { 283 | for _, p := range g.players { 284 | p.X = p.X + int(float64(p.Dir[0]*p.V)*delta.Seconds()) 285 | p.Y = p.Y + int(float64(p.Dir[1]*p.V)*delta.Seconds()) 286 | p.Z = p.Z + int(float64(p.Dir[2]*p.V)*delta.Seconds()) 287 | } 288 | } 289 | 290 | func (g *GameNormal) DoMoveParallel(delta time.Duration) { 291 | for _, p := range g.players { 292 | p.X = p.X + int(float64(p.Dir[0]*p.V)*delta.Seconds()) 293 | p.Y = p.Y + int(float64(p.Dir[1]*p.V)*delta.Seconds()) 294 | p.Z = p.Z + int(float64(p.Dir[2]*p.V)*delta.Seconds()) 295 | } 296 | } 297 | 298 | func (g *GameNormal) DoDamage() { 299 | for _, caster := range g.players { 300 | for _, target := range g.players { 301 | if caster.ID == target.ID { 302 | continue 303 | } 304 | 305 | //计算距离 306 | distance := (caster.X-target.X)*(caster.X-target.X) + (caster.Y-target.Y)*(caster.Y-target.Y) 307 | if distance > caster.AttackRange*caster.AttackRange { 308 | continue 309 | } 310 | 311 | //伤害公式:伤害=(基础攻击+力量)+ 暴击伤害, 暴击伤害=基础攻击 * 2 312 | damage := caster.PhysicalBaseAttack + caster.Strength 313 | critical := 0 314 | if rand.Intn(100) < caster.CriticalChange { 315 | critical = caster.PhysicalBaseAttack * caster.CriticalMultiple 316 | } 317 | damage = damage + critical 318 | target.HP -= damage 319 | if target.HP < 0 { 320 | target.HP = 0 321 | } 322 | } 323 | } 324 | } 325 | func (g *GameNormal) DoDamageParallel() { 326 | for _, caster := range g.players { 327 | caster.rw.RLock() 328 | for _, target := range g.players { 329 | if caster.ID == target.ID { 330 | continue 331 | } 332 | 333 | target.rw.Lock() 334 | //计算距离 335 | distance := (caster.X-target.X)*(caster.X-target.X) + (caster.Y-target.Y)*(caster.Y-target.Y) 336 | if distance > caster.AttackRange*caster.AttackRange { 337 | continue 338 | } 339 | 340 | //伤害公式:伤害=(基础攻击+力量)+ 暴击伤害, 暴击伤害=基础攻击 * 2 341 | damage := caster.PhysicalBaseAttack + caster.Strength 342 | critical := 0 343 | if rand.Intn(100) < caster.CriticalChange { 344 | critical = caster.PhysicalBaseAttack * caster.CriticalMultiple 345 | } 346 | damage = damage + critical 347 | target.HP -= damage 348 | if target.HP < 0 { 349 | target.HP = 0 350 | } 351 | target.rw.Unlock() 352 | } 353 | caster.rw.RUnlock() 354 | } 355 | } 356 | -------------------------------------------------------------------------------- /example/benchmark-0/go.mod: -------------------------------------------------------------------------------- 1 | module test_ecs_m_d 2 | 3 | go 1.18 4 | 5 | replace github.com/zllangct/ecs => ./../.. 6 | 7 | require ( 8 | github.com/zllangct/ecs v0.0.0 9 | ) -------------------------------------------------------------------------------- /example/benchmark-0/main_benchmark_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/zllangct/ecs" 5 | "runtime" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | func BenchmarkNormal(b *testing.B) { 11 | game := &GameNormal{ 12 | players: make(map[int64]*Player), 13 | } 14 | game.init() 15 | b.ResetTimer() 16 | 17 | var delta time.Duration 18 | var ts time.Time 19 | for i := 0; i < b.N; i++ { 20 | ts = time.Now() 21 | game.doFrame(false, uint64(i), delta) 22 | delta = time.Since(ts) 23 | } 24 | } 25 | 26 | func BenchmarkNormalParallel(b *testing.B) { 27 | game := &GameNormal{ 28 | players: make(map[int64]*Player), 29 | } 30 | game.init() 31 | b.ResetTimer() 32 | 33 | var delta time.Duration 34 | var ts time.Time 35 | for i := 0; i < b.N; i++ { 36 | ts = time.Now() 37 | game.doFrame(true, uint64(i), delta) 38 | delta = time.Since(ts) 39 | } 40 | } 41 | 42 | func BenchmarkEcs(b *testing.B) { 43 | //go func() { 44 | // http.ListenAndServe(":6060", nil) 45 | //}() 46 | 47 | game := &GameECS{} 48 | config := ecs.NewDefaultWorldConfig() 49 | config.Debug = false 50 | config.MetaInfoDebugPrint = false 51 | game.init(config) 52 | 53 | game.world.Startup() 54 | 55 | b.ResetTimer() 56 | 57 | var delta time.Duration 58 | _ = delta 59 | var ts time.Time 60 | for i := 0; i < b.N; i++ { 61 | ts = time.Now() 62 | game.attack() 63 | game.world.Update() 64 | delta = time.Since(ts) 65 | } 66 | } 67 | 68 | func BenchmarkEcsSingleCore(b *testing.B) { 69 | //go func() { 70 | // http.ListenAndServe(":6060", nil) 71 | //}() 72 | 73 | runtime.GOMAXPROCS(1) 74 | 75 | game := &GameECS{} 76 | config := ecs.NewDefaultWorldConfig() 77 | config.Debug = false 78 | config.MetaInfoDebugPrint = false 79 | game.init(config) 80 | 81 | game.world.Startup() 82 | 83 | b.ResetTimer() 84 | 85 | var delta time.Duration 86 | _ = delta 87 | var ts time.Time 88 | for i := 0; i < b.N; i++ { 89 | ts = time.Now() 90 | game.attack() 91 | game.world.Update() 92 | delta = time.Since(ts) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /example/benchmark-0/main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/zllangct/ecs" 6 | _ "net/http/pprof" 7 | "reflect" 8 | "testing" 9 | "time" 10 | "unsafe" 11 | _ "unsafe" 12 | ) 13 | 14 | func TestFrame(t *testing.T) { 15 | game := &GameECS{} 16 | game.init(ecs.NewDefaultWorldConfig()) 17 | 18 | game.world.Startup() 19 | 20 | var delta time.Duration 21 | _ = delta 22 | var ts time.Time 23 | for i := 0; i < 10; i++ { 24 | ts = time.Now() 25 | game.world.Update() 26 | game.attack() 27 | delta = time.Since(ts) 28 | //ecs.Log.Info("===== Frame:", i, "=====", delta) 29 | } 30 | } 31 | 32 | func TestEcsOptimizer(t *testing.T) { 33 | game := &GameECS{} 34 | game.init(ecs.NewDefaultWorldConfig()) 35 | 36 | game.world.Startup() 37 | 38 | var frameInterval = time.Millisecond * 33 39 | var delta time.Duration 40 | var ts time.Time 41 | for i := 0; i < 10; i++ { 42 | //ecs.Log.Info("===== Frame:", i) 43 | ts = time.Now() 44 | 45 | game.world.Update() 46 | game.attack() 47 | delta = time.Since(ts) 48 | ecs.Log.Info("===== Frame:", i, "=====", delta) 49 | if frameInterval-delta != 0 { 50 | game.world.Optimize(frameInterval-delta, true) 51 | time.Sleep(frameInterval - delta) 52 | delta = frameInterval 53 | } 54 | } 55 | } 56 | 57 | func TestOthers(t *testing.T) { 58 | var a1 = [...]reflect.Type{ecs.TypeOf[Test1](), ecs.TypeOf[Test2](), ecs.TypeOf[Test3]()} 59 | var a2 = [...]reflect.Type{ecs.TypeOf[Test1](), ecs.TypeOf[Test2](), ecs.TypeOf[Test3]()} 60 | m := map[interface{}]string{} 61 | m[a1] = "this is a1" 62 | m[a2] = "this is a2" 63 | fmt.Printf("%v\n", m) 64 | 65 | println(unsafe.Sizeof(ecs.Component[Test1]{})) 66 | } 67 | -------------------------------------------------------------------------------- /example/benchmark-0/move_system.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/zllangct/ecs" 5 | ) 6 | 7 | type MoveSystem struct { 8 | ecs.System[MoveSystem] 9 | } 10 | 11 | func (m *MoveSystem) Init(si ecs.SystemInitConstraint) error { 12 | m.SetRequirements(si, &Position{}, &ecs.ReadOnly[Movement]{}) 13 | return nil 14 | } 15 | 16 | func (m *MoveSystem) Update(event ecs.Event) { 17 | delta := event.Delta 18 | 19 | count := 0 20 | iter := ecs.GetComponentAll[Movement](m) 21 | for move := iter.Begin(); !iter.End(); move = iter.Next() { 22 | entity := move.Owner() 23 | pos := ecs.GetRelated[Position](m, entity) 24 | if pos == nil { 25 | continue 26 | } 27 | 28 | pos.X = pos.X + int(float64(move.Dir[0]*move.V)*delta.Seconds()) 29 | pos.Y = pos.Y + int(float64(move.Dir[1]*move.V)*delta.Seconds()) 30 | pos.Z = pos.Z + int(float64(move.Dir[2]*move.V)*delta.Seconds()) 31 | 32 | oldMoveV := move.V 33 | _ = oldMoveV 34 | 35 | // test for read only component, changes are not effective 36 | move.V = move.V + 1 37 | 38 | count++ 39 | //ecs.Log.Info("target id:", entity, " delta:", delta, " current position:", pos.X, pos.Y, pos.Z, 40 | // " move speed:", oldMoveV) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /example/benchmark-0/simu_load_system.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "github.com/zllangct/ecs" 4 | 5 | type Test1System struct { 6 | ecs.System[Test1System] 7 | } 8 | 9 | func (t *Test1System) Init(si ecs.SystemInitConstraint) error { 10 | t.SetRequirements(si, &Test1{}) 11 | return nil 12 | } 13 | 14 | func (t *Test1System) Update(event ecs.Event) { 15 | iter := ecs.GetComponentAll[Test1](t) 16 | for c := iter.Begin(); !iter.End(); c = iter.Next() { 17 | for i := 0; i < DummyMaxFor; i++ { 18 | c.Test1 += i 19 | } 20 | } 21 | } 22 | 23 | type Test2System struct { 24 | ecs.System[Test2System] 25 | } 26 | 27 | func (t *Test2System) Init(si ecs.SystemInitConstraint) error { 28 | t.SetRequirements(si, &Test2{}) 29 | return nil 30 | } 31 | 32 | func (t *Test2System) Update(event ecs.Event) { 33 | iter := ecs.GetComponentAll[Test2](t) 34 | for c := iter.Begin(); !iter.End(); c = iter.Next() { 35 | for i := 0; i < DummyMaxFor; i++ { 36 | c.Test2 += i 37 | } 38 | } 39 | } 40 | 41 | type Test3System struct { 42 | ecs.System[Test3System] 43 | } 44 | 45 | func (t *Test3System) Init(si ecs.SystemInitConstraint) error { 46 | t.SetRequirements(si, &Test3{}) 47 | return nil 48 | } 49 | 50 | func (t *Test3System) Update(event ecs.Event) { 51 | iter := ecs.GetComponentAll[Test3](t) 52 | for c := iter.Begin(); !iter.End(); c = iter.Next() { 53 | for i := 0; i < DummyMaxFor; i++ { 54 | c.Test3 += i 55 | } 56 | } 57 | } 58 | 59 | type Test4System struct { 60 | ecs.System[Test4System] 61 | } 62 | 63 | func (t *Test4System) Init(si ecs.SystemInitConstraint) error { 64 | t.SetRequirements(si, &Test4{}) 65 | return nil 66 | } 67 | 68 | func (t *Test4System) Update(event ecs.Event) { 69 | iter := ecs.GetComponentAll[Test4](t) 70 | for c := iter.Begin(); !iter.End(); c = iter.Next() { 71 | for i := 0; i < DummyMaxFor; i++ { 72 | c.Test4 += i 73 | } 74 | } 75 | } 76 | 77 | type Test5System struct { 78 | ecs.System[Test5System] 79 | } 80 | 81 | func (t *Test5System) Init(si ecs.SystemInitConstraint) error { 82 | t.SetRequirements(si, &Test5{}) 83 | return nil 84 | } 85 | 86 | func (t *Test5System) Update(event ecs.Event) { 87 | iter := ecs.GetComponentAll[Test5](t) 88 | for c := iter.Begin(); !iter.End(); c = iter.Next() { 89 | for i := 0; i < DummyMaxFor; i++ { 90 | c.Test5 += i 91 | } 92 | } 93 | } 94 | 95 | type Test6System struct { 96 | ecs.System[Test6System] 97 | } 98 | 99 | func (t *Test6System) Init(si ecs.SystemInitConstraint) error { 100 | t.SetRequirements(si, &Test6{}) 101 | return nil 102 | } 103 | 104 | func (t *Test6System) Update(event ecs.Event) { 105 | iter := ecs.GetComponentAll[Test6](t) 106 | for c := iter.Begin(); !iter.End(); c = iter.Next() { 107 | for i := 0; i < DummyMaxFor; i++ { 108 | c.Test6 += i 109 | } 110 | } 111 | } 112 | 113 | type Test7System struct { 114 | ecs.System[Test7System] 115 | } 116 | 117 | func (t *Test7System) Init(si ecs.SystemInitConstraint) error { 118 | t.SetRequirements(si, &Test7{}) 119 | return nil 120 | } 121 | 122 | func (t *Test7System) Update(event ecs.Event) { 123 | iter := ecs.GetComponentAll[Test7](t) 124 | for c := iter.Begin(); !iter.End(); c = iter.Next() { 125 | for i := 0; i < DummyMaxFor; i++ { 126 | c.Test7 += i 127 | } 128 | } 129 | } 130 | 131 | type Test8System struct { 132 | ecs.System[Test8System] 133 | } 134 | 135 | func (t *Test8System) Init(si ecs.SystemInitConstraint) error { 136 | t.SetRequirements(si, &Test8{}) 137 | return nil 138 | } 139 | 140 | func (t *Test8System) Update(event ecs.Event) { 141 | iter := ecs.GetComponentAll[Test8](t) 142 | for c := iter.Begin(); !iter.End(); c = iter.Next() { 143 | for i := 0; i < DummyMaxFor; i++ { 144 | c.Test8 += i 145 | } 146 | } 147 | } 148 | 149 | type Test9System struct { 150 | ecs.System[Test9System] 151 | } 152 | 153 | func (t *Test9System) Init(si ecs.SystemInitConstraint) error { 154 | t.SetRequirements(si, &Test9{}) 155 | return nil 156 | } 157 | 158 | func (t *Test9System) Update(event ecs.Event) { 159 | iter := ecs.GetComponentAll[Test9](t) 160 | for c := iter.Begin(); !iter.End(); c = iter.Next() { 161 | for i := 0; i < DummyMaxFor; i++ { 162 | c.Test9 += i 163 | } 164 | } 165 | } 166 | 167 | type Test10System struct { 168 | ecs.System[Test10System] 169 | } 170 | 171 | func (t *Test10System) Init(si ecs.SystemInitConstraint) error { 172 | t.SetRequirements(si, &Test10{}) 173 | return nil 174 | } 175 | 176 | func (t *Test10System) Update(event ecs.Event) { 177 | iter := ecs.GetComponentAll[Test10](t) 178 | for c := iter.Begin(); !iter.End(); c = iter.Next() { 179 | for i := 0; i < DummyMaxFor; i++ { 180 | c.Test10 += i 181 | } 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /example/fake-simple-game-server/client/fake_client.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/zllangct/ecs" 7 | "math/rand" 8 | "test_ecs_fake_server/network" 9 | "time" 10 | ) 11 | 12 | type FakeClient struct { 13 | } 14 | 15 | func NewClient() *FakeClient { 16 | return &FakeClient{} 17 | } 18 | 19 | func (f *FakeClient) Run(ctx context.Context) { 20 | var c []*network.TcpConn 21 | for i := 0; i < 5; i++ { 22 | conn := network.Dial("127.0.0.1:3333") 23 | c = append(c, conn) 24 | } 25 | 26 | // simulation to send pkg 27 | go func() { 28 | for { 29 | time.Sleep(time.Second * time.Duration(rand.Intn(5))) 30 | idx := rand.Intn(len(c)) 31 | c[idx].Write(fmt.Sprintf("chat:hi, i am %d", idx)) 32 | } 33 | }() 34 | 35 | // simulation to control player 36 | go func() { 37 | for { 38 | time.Sleep(time.Second * time.Duration(rand.Intn(5))) 39 | idx := rand.Intn(len(c)) 40 | v := rand.Intn(1000) 41 | dir := [3]int{0, 0, 0} 42 | dir[rand.Intn(3)] = 1 43 | c[idx].Write(fmt.Sprintf("move:%d,%d,%d:%d", dir[0], dir[1], dir[2], v)) 44 | } 45 | }() 46 | 47 | // simulation to accept pkg 48 | for i, conn := range c { 49 | go func(idx int, conn *network.TcpConn) { 50 | for { 51 | pkg := conn.Read() 52 | ecs.Log.Infof("client[%d] recv: %+v", idx, pkg) 53 | } 54 | }(i, conn) 55 | } 56 | 57 | <-ctx.Done() 58 | } 59 | -------------------------------------------------------------------------------- /example/fake-simple-game-server/game/chat.go: -------------------------------------------------------------------------------- 1 | package game 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | type ChatRoom struct { 8 | clients *sync.Map 9 | } 10 | 11 | func NewChatRoom(c *sync.Map) *ChatRoom { 12 | return &ChatRoom{ 13 | clients: &sync.Map{}, 14 | } 15 | } 16 | 17 | func (c *ChatRoom) Talk(content string) { 18 | c.clients.Range(func(k, v interface{}) bool { 19 | sess := v.(*Session) 20 | sess.Conn.Write(content) 21 | return true 22 | }) 23 | } 24 | -------------------------------------------------------------------------------- /example/fake-simple-game-server/game/empty_system.go: -------------------------------------------------------------------------------- 1 | package game 2 | 3 | import "github.com/zllangct/ecs" 4 | 5 | type EmptySystem struct { 6 | ecs.System[EmptySystem] 7 | isPreStart bool 8 | isStart bool 9 | isPostStart bool 10 | } 11 | 12 | func (e *EmptySystem) Init(si ecs.SystemInitConstraint) error { 13 | ecs.Log.Info("empty system init") 14 | return nil 15 | } 16 | 17 | func (e *EmptySystem) Start(event ecs.Event) { 18 | ecs.Log.Info("empty system start") 19 | } 20 | 21 | func (e *EmptySystem) PreUpdate(event ecs.Event) { 22 | if e.isPreStart { 23 | return 24 | } 25 | e.isPreStart = true 26 | ecs.Log.Info("empty system pre update") 27 | } 28 | 29 | func (e *EmptySystem) Update(event ecs.Event) { 30 | if e.isStart { 31 | return 32 | } 33 | e.isStart = true 34 | ecs.Log.Info("empty system update") 35 | } 36 | 37 | func (e *EmptySystem) PostUpdate(event ecs.Event) { 38 | if e.isPostStart { 39 | return 40 | } 41 | e.isPostStart = true 42 | ecs.Log.Info("empty system post update") 43 | } 44 | -------------------------------------------------------------------------------- /example/fake-simple-game-server/game/fake_game.go: -------------------------------------------------------------------------------- 1 | package game 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "github.com/zllangct/ecs" 7 | "strconv" 8 | "strings" 9 | "sync" 10 | "test_ecs_fake_server/network" 11 | ) 12 | 13 | var send chan Msg2Client = make(chan Msg2Client, 10) 14 | 15 | type Msg2Client struct { 16 | SessionID int 17 | Content interface{} 18 | } 19 | 20 | type FakeGame struct { 21 | clients sync.Map 22 | world *ecs.AsyncWorld 23 | chatRoom *ChatRoom 24 | } 25 | 26 | func NewGame() *FakeGame { 27 | return &FakeGame{} 28 | } 29 | 30 | func (f *FakeGame) Run(ctx context.Context) { 31 | f.InitEcs() 32 | f.InitChat() 33 | f.InitNetwork() 34 | } 35 | 36 | func (f *FakeGame) InitEcs() { 37 | //create config 38 | config := ecs.NewDefaultWorldConfig() 39 | 40 | //create a world and startup 41 | f.world = ecs.NewAsyncWorld(config) 42 | f.world.Startup() 43 | 44 | //register your system 45 | ecs.RegisterSystem[MoveSystem](f.world) 46 | ecs.RegisterSystem[SyncSystem](f.world) 47 | ecs.RegisterSystem[EmptySystem](f.world) 48 | } 49 | 50 | func (f *FakeGame) EnterGame(sess *Session) { 51 | f.world.Wait(func(gaw ecs.SyncWrapper) error { 52 | e := gaw.NewEntity() 53 | gaw.Add(e, &PlayerComponent{ 54 | SessionID: sess.SessionID, 55 | }) 56 | gaw.Add(e, &Position{ 57 | X: 100, 58 | Y: 100, 59 | Z: 100, 60 | }) 61 | gaw.Add(e, &Movement{ 62 | V: 2000, 63 | Dir: [3]int{1, 0, 0}, 64 | }) 65 | sess.Entity = e 66 | return nil 67 | }) 68 | } 69 | 70 | func (f *FakeGame) InitNetwork() { 71 | lis, err := network.Listen() 72 | if err != nil { 73 | return 74 | } 75 | 76 | go func() { 77 | for { 78 | select { 79 | case m := <-send: 80 | obj, ok := f.clients.Load(m.SessionID) 81 | if ok { 82 | sess := obj.(*Session) 83 | sess.Conn.Write(m.Content) 84 | } 85 | } 86 | } 87 | }() 88 | 89 | seq := 0 90 | for { 91 | conn := lis.Accept() 92 | seq++ 93 | sess := &Session{ 94 | SessionID: seq, 95 | Conn: conn, 96 | } 97 | 98 | go func(conn *network.TcpConn, sess *Session) { 99 | f.OnClientEnter(sess) 100 | for { 101 | pkg := conn.Read() 102 | f.Dispatch(pkg, sess) 103 | } 104 | }(conn, sess) 105 | } 106 | } 107 | 108 | func SendToClient(sessionId int, content interface{}) { 109 | send <- Msg2Client{ 110 | SessionID: sessionId, 111 | Content: content, 112 | } 113 | } 114 | 115 | func (f *FakeGame) OnClientEnter(sess *Session) { 116 | f.clients.Store(sess.SessionID, sess) 117 | f.EnterGame(sess) 118 | } 119 | 120 | func (f *FakeGame) Dispatch(pkg interface{}, sess *Session) { 121 | content, ok := pkg.(string) 122 | if !ok { 123 | return 124 | } 125 | 126 | split := strings.Split(content, ":") 127 | op := split[0] 128 | 129 | switch op { 130 | case "chat": 131 | // not handle by ecs 132 | f.chatRoom.Talk(split[1]) 133 | case "move": 134 | // handle by ecs 135 | if len(split) != 3 { 136 | return 137 | } 138 | d := strings.Split(split[1], ",") 139 | if len(d) != 3 { 140 | return 141 | } 142 | var dir [3]int 143 | for i := 0; i < 3; i++ { 144 | value, _ := strconv.Atoi(d[i]) 145 | dir[i] = value 146 | } 147 | 148 | v, _ := strconv.Atoi(split[2]) 149 | f.Move(sess.Entity, v, dir) 150 | } 151 | } 152 | 153 | func (f *FakeGame) Move(entity ecs.Entity, v int, dir [3]int) { 154 | if f.world == nil { 155 | return 156 | } 157 | f.world.Sync(func(gaw ecs.SyncWrapper) error { 158 | u, ok := ecs.GetUtility[MoveSystemUtility](gaw) 159 | if !ok { 160 | return errors.New("can not find MoveSystemUtility") 161 | } 162 | return u.Move(entity, v, dir) 163 | }) 164 | } 165 | 166 | func (f *FakeGame) ChangeMovementTimeScale(timeScale float64) { 167 | if f.world == nil { 168 | return 169 | } 170 | f.world.Sync(func(gaw ecs.SyncWrapper) error { 171 | u, ok := ecs.GetUtility[MoveSystemUtility](gaw) 172 | if !ok { 173 | return errors.New("can not find MoveSystemUtility") 174 | } 175 | return u.UpdateTimeScale(timeScale) 176 | }) 177 | } 178 | 179 | func (f *FakeGame) InitChat() { 180 | f.chatRoom = NewChatRoom(&f.clients) 181 | } 182 | -------------------------------------------------------------------------------- /example/fake-simple-game-server/game/move_component.go: -------------------------------------------------------------------------------- 1 | package game 2 | 3 | import "github.com/zllangct/ecs" 4 | 5 | type Position struct { 6 | ecs.Component[Position] 7 | X int 8 | Y int 9 | Z int 10 | } 11 | 12 | type Movement struct { 13 | ecs.Component[Movement] 14 | V int 15 | Dir [3]int 16 | } 17 | -------------------------------------------------------------------------------- /example/fake-simple-game-server/game/move_system.go: -------------------------------------------------------------------------------- 1 | package game 2 | 3 | import ( 4 | "errors" 5 | "github.com/zllangct/ecs" 6 | "time" 7 | ) 8 | 9 | type MoveSystemUtility struct { 10 | ecs.Utility[MoveSystemUtility] 11 | } 12 | 13 | func (m *MoveSystemUtility) UpdateTimeScale(scale float64) error { 14 | s := m.GetSystem() 15 | if s == nil { 16 | return errors.New("system is nil") 17 | } 18 | sys := s.(*MoveSystem) 19 | sys.timeScale = scale 20 | return nil 21 | } 22 | 23 | func (m *MoveSystemUtility) Move(entity ecs.Entity, v int, dir [3]int) error { 24 | s := m.GetSystem() 25 | if s == nil { 26 | return errors.New("system is nil") 27 | } 28 | mov := ecs.GetComponent[Movement](s, entity) 29 | mov.V = v 30 | mov.Dir = dir 31 | return nil 32 | } 33 | 34 | type MoveSystemData struct { 35 | P *Position 36 | M *Movement 37 | } 38 | 39 | type MoveSystem struct { 40 | ecs.System[MoveSystem] 41 | timeScale float64 42 | deltaTime time.Duration 43 | getter *ecs.Shape[MoveSystemData] 44 | } 45 | 46 | func (m *MoveSystem) Init(si ecs.SystemInitConstraint) { 47 | m.SetRequirements(si, &Position{}, &Movement{}) 48 | ecs.BindUtility[MoveSystemUtility](si) 49 | m.getter = ecs.NewShape[MoveSystemData](si) 50 | } 51 | 52 | func (m *MoveSystem) UpdateTimeScale(timeScale []interface{}) error { 53 | ecs.Log.Info("time scale change to ", timeScale[0]) 54 | m.timeScale = timeScale[0].(float64) 55 | return nil 56 | } 57 | 58 | func (m *MoveSystem) Update(event ecs.Event) { 59 | delta := event.Delta 60 | 61 | m.deltaTime += delta 62 | isPrint := false 63 | if m.deltaTime > time.Second*3 { 64 | isPrint = true 65 | m.deltaTime = 0 66 | } 67 | 68 | iter := m.getter.Get() 69 | for shp := iter.Begin(); !iter.End(); shp = iter.Next() { 70 | mv := shp.M 71 | p := shp.P 72 | _, _ = p, mv 73 | p.X = p.X + int(float64(mv.Dir[0]*mv.V)*delta.Seconds()) 74 | p.Y = p.Y + int(float64(mv.Dir[1]*mv.V)*delta.Seconds()) 75 | p.Z = p.Z + int(float64(mv.Dir[2]*mv.V)*delta.Seconds()) 76 | 77 | if isPrint { 78 | e := p.Owner() 79 | ecs.Log.Info("target id:", e, " delta:", delta, " current position:", p.X, p.Y, p.Z) 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /example/fake-simple-game-server/game/player_component.go: -------------------------------------------------------------------------------- 1 | package game 2 | 3 | import "github.com/zllangct/ecs" 4 | 5 | type PlayerComponent struct { 6 | ecs.Component[PlayerComponent] 7 | Name string 8 | Level int 9 | SessionID int 10 | } 11 | -------------------------------------------------------------------------------- /example/fake-simple-game-server/game/position_sync_system.go: -------------------------------------------------------------------------------- 1 | package game 2 | 3 | import "github.com/zllangct/ecs" 4 | 5 | type PlayerPosition struct { 6 | SessionID int 7 | Pos Position 8 | } 9 | 10 | type SyncSystem struct { 11 | ecs.System[SyncSystem] 12 | } 13 | 14 | func (m *SyncSystem) Init(si ecs.SystemInitConstraint) error { 15 | m.SetRequirements(si, &Position{}, &PlayerComponent{}) 16 | return nil 17 | } 18 | 19 | func (m *SyncSystem) PostUpdate(event ecs.Event) { 20 | p := ecs.GetComponentAll[Position](m) 21 | for i := p.Begin(); !p.End(); i = p.Next() { 22 | pc := ecs.GetRelated[PlayerComponent](m, i.Owner()) 23 | if pc == nil { 24 | continue 25 | } 26 | SendToClient(pc.SessionID, PlayerPosition{ 27 | SessionID: pc.SessionID, 28 | Pos: *i, 29 | }) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /example/fake-simple-game-server/game/session.go: -------------------------------------------------------------------------------- 1 | package game 2 | 3 | import ( 4 | "github.com/zllangct/ecs" 5 | "test_ecs_fake_server/network" 6 | ) 7 | 8 | type Session struct { 9 | SessionID int 10 | Conn *network.TcpConn 11 | Entity ecs.Entity 12 | } 13 | -------------------------------------------------------------------------------- /example/fake-simple-game-server/gm/gm.go: -------------------------------------------------------------------------------- 1 | package gm 2 | 3 | import ( 4 | "context" 5 | "math/rand" 6 | "test_ecs_fake_server/game" 7 | "time" 8 | ) 9 | 10 | type GM struct { 11 | game *game.FakeGame 12 | } 13 | 14 | func NewGM() *GM { 15 | return &GM{} 16 | } 17 | 18 | func (g *GM) Run(ctx context.Context, game *game.FakeGame) { 19 | g.game = game 20 | 21 | timeScale := 0 22 | for { 23 | time.Sleep(time.Second * time.Duration(rand.Intn(5))) 24 | if timeScale == 0 { 25 | g.ChangeMovementTimeScale(1.2) 26 | timeScale = 1 27 | } else { 28 | g.ChangeMovementTimeScale(1.0) 29 | timeScale = 0 30 | } 31 | } 32 | } 33 | 34 | func (g *GM) ChangeMovementTimeScale(timeScale float64) { 35 | g.game.ChangeMovementTimeScale(timeScale) 36 | } 37 | -------------------------------------------------------------------------------- /example/fake-simple-game-server/go.mod: -------------------------------------------------------------------------------- 1 | module test_ecs_fake_server 2 | 3 | go 1.18 4 | 5 | replace github.com/zllangct/ecs => ./../.. 6 | 7 | require github.com/zllangct/ecs v0.0.0 8 | -------------------------------------------------------------------------------- /example/fake-simple-game-server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "github.com/zllangct/ecs" 6 | "net/http" 7 | _ "net/http/pprof" 8 | "test_ecs_fake_server/client" 9 | "test_ecs_fake_server/game" 10 | "test_ecs_fake_server/gm" 11 | ) 12 | 13 | func main() { 14 | ecs.Log.Info("game start...") 15 | go func() { 16 | ecs.Log.Info(http.ListenAndServe("localhost:8889", nil)) 17 | }() 18 | 19 | ctx := context.Background() 20 | 21 | //my game 22 | game := game.NewGame() 23 | //game manager 24 | gm := gm.NewGM() 25 | ////client manager 26 | cm := client.NewClient() 27 | 28 | go game.Run(ctx) 29 | go gm.Run(ctx, game) 30 | go cm.Run(ctx) 31 | 32 | <-ctx.Done() 33 | ecs.Log.Info("game end...") 34 | } 35 | -------------------------------------------------------------------------------- /example/fake-simple-game-server/network/fake_net.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | type TcpConn struct { 4 | r chan interface{} 5 | w chan interface{} 6 | } 7 | 8 | func NewTcpConn() *TcpConn { 9 | return &TcpConn{ 10 | r: make(chan interface{}, 10), 11 | w: make(chan interface{}, 10), 12 | } 13 | } 14 | 15 | func (t *TcpConn) Write(in interface{}) { 16 | //ecs.Log.Info("Tcp Send message:", in) 17 | t.w <- in 18 | } 19 | 20 | func (t *TcpConn) Read() interface{} { 21 | read := <-t.r 22 | //ecs.Log.Info("Tcp Read message:", read) 23 | return read 24 | } 25 | 26 | var ch chan *TcpConn = make(chan *TcpConn, 10) 27 | 28 | type FakeTcpServer struct{} 29 | 30 | func Listen() (*FakeTcpServer, error) { 31 | return &FakeTcpServer{}, nil 32 | } 33 | 34 | func Dial(addr string) *TcpConn { 35 | connSrc := NewTcpConn() 36 | connDst := NewTcpConn() 37 | connDst.r, connDst.w = connSrc.w, connSrc.r 38 | ch <- connDst 39 | return connSrc 40 | } 41 | 42 | func (f *FakeTcpServer) Accept() *TcpConn { 43 | return <-ch 44 | } 45 | -------------------------------------------------------------------------------- /fixed_string.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import ( 4 | "fmt" 5 | "unsafe" 6 | ) 7 | 8 | const ( 9 | __FixedMax = 128 10 | ) 11 | 12 | type FixedString[T any] struct { 13 | data T 14 | len int 15 | } 16 | 17 | func (f *FixedString[T]) Clear() { 18 | f.len = 0 19 | } 20 | 21 | func (f *FixedString[T]) Empty() bool { 22 | return f.len == 0 23 | } 24 | 25 | func (f *FixedString[T]) Len() int { 26 | return f.len 27 | } 28 | 29 | func (f *FixedString[T]) String() string { 30 | return string((*(*[__FixedMax]byte)(unsafe.Pointer(&(f.data))))[:f.len]) 31 | } 32 | 33 | func (f *FixedString[T]) Set(s string) { 34 | if len(s) > int(unsafe.Sizeof(f.data)) { 35 | panic(fmt.Sprintf("fixed string max size: %d, received size: %d", unsafe.Sizeof(f.data), len(s))) 36 | } 37 | f.len = len(s) 38 | if f.len != 0 { 39 | copy((*(*[__FixedMax]byte)(unsafe.Pointer(&(f.data))))[:unsafe.Sizeof(f.data)], s) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /fixed_string_test.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "os" 7 | "testing" 8 | ) 9 | 10 | func TestFixedString_Generate(t *testing.T) { 11 | return 12 | filePath := "./fixed_string_utils.go" 13 | file, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE, 0666) 14 | if err != nil { 15 | fmt.Println("文件打开失败", err) 16 | } 17 | defer file.Close() 18 | write := bufio.NewWriter(file) 19 | h1 := `package ecs 20 | 21 | ` 22 | write.WriteString(h1) 23 | h2 := "type Fixed%d [%d]byte\n" 24 | for i := 1; i < 129; i++ { 25 | write.WriteString(fmt.Sprintf(h2, i, i)) 26 | } 27 | write.Flush() 28 | } 29 | 30 | func TestFixedString_String(t *testing.T) { 31 | 32 | tests := []struct { 33 | name string 34 | arg string 35 | want string 36 | }{ 37 | { 38 | name: "test", 39 | arg: "hello world", 40 | want: "hello world", 41 | }, 42 | { 43 | name: "test1", 44 | arg: "hello 中文 ☺", 45 | want: "hello 中文 ☺", 46 | }, 47 | } 48 | for _, tt := range tests { 49 | t.Run(tt.name, func(t *testing.T) { 50 | f := FixedString[Fixed128]{} 51 | f.Set(tt.arg) 52 | if got := f.String(); got != tt.want { 53 | t.Errorf("String() = %v, want %v", got, tt.want) 54 | } 55 | }) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /fixed_string_utils.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | type Fixed1 [1]byte 4 | type Fixed2 [2]byte 5 | type Fixed3 [3]byte 6 | type Fixed4 [4]byte 7 | type Fixed5 [5]byte 8 | type Fixed6 [6]byte 9 | type Fixed7 [7]byte 10 | type Fixed8 [8]byte 11 | type Fixed9 [9]byte 12 | type Fixed10 [10]byte 13 | type Fixed11 [11]byte 14 | type Fixed12 [12]byte 15 | type Fixed13 [13]byte 16 | type Fixed14 [14]byte 17 | type Fixed15 [15]byte 18 | type Fixed16 [16]byte 19 | type Fixed17 [17]byte 20 | type Fixed18 [18]byte 21 | type Fixed19 [19]byte 22 | type Fixed20 [20]byte 23 | type Fixed21 [21]byte 24 | type Fixed22 [22]byte 25 | type Fixed23 [23]byte 26 | type Fixed24 [24]byte 27 | type Fixed25 [25]byte 28 | type Fixed26 [26]byte 29 | type Fixed27 [27]byte 30 | type Fixed28 [28]byte 31 | type Fixed29 [29]byte 32 | type Fixed30 [30]byte 33 | type Fixed31 [31]byte 34 | type Fixed32 [32]byte 35 | type Fixed33 [33]byte 36 | type Fixed34 [34]byte 37 | type Fixed35 [35]byte 38 | type Fixed36 [36]byte 39 | type Fixed37 [37]byte 40 | type Fixed38 [38]byte 41 | type Fixed39 [39]byte 42 | type Fixed40 [40]byte 43 | type Fixed41 [41]byte 44 | type Fixed42 [42]byte 45 | type Fixed43 [43]byte 46 | type Fixed44 [44]byte 47 | type Fixed45 [45]byte 48 | type Fixed46 [46]byte 49 | type Fixed47 [47]byte 50 | type Fixed48 [48]byte 51 | type Fixed49 [49]byte 52 | type Fixed50 [50]byte 53 | type Fixed51 [51]byte 54 | type Fixed52 [52]byte 55 | type Fixed53 [53]byte 56 | type Fixed54 [54]byte 57 | type Fixed55 [55]byte 58 | type Fixed56 [56]byte 59 | type Fixed57 [57]byte 60 | type Fixed58 [58]byte 61 | type Fixed59 [59]byte 62 | type Fixed60 [60]byte 63 | type Fixed61 [61]byte 64 | type Fixed62 [62]byte 65 | type Fixed63 [63]byte 66 | type Fixed64 [64]byte 67 | type Fixed65 [65]byte 68 | type Fixed66 [66]byte 69 | type Fixed67 [67]byte 70 | type Fixed68 [68]byte 71 | type Fixed69 [69]byte 72 | type Fixed70 [70]byte 73 | type Fixed71 [71]byte 74 | type Fixed72 [72]byte 75 | type Fixed73 [73]byte 76 | type Fixed74 [74]byte 77 | type Fixed75 [75]byte 78 | type Fixed76 [76]byte 79 | type Fixed77 [77]byte 80 | type Fixed78 [78]byte 81 | type Fixed79 [79]byte 82 | type Fixed80 [80]byte 83 | type Fixed81 [81]byte 84 | type Fixed82 [82]byte 85 | type Fixed83 [83]byte 86 | type Fixed84 [84]byte 87 | type Fixed85 [85]byte 88 | type Fixed86 [86]byte 89 | type Fixed87 [87]byte 90 | type Fixed88 [88]byte 91 | type Fixed89 [89]byte 92 | type Fixed90 [90]byte 93 | type Fixed91 [91]byte 94 | type Fixed92 [92]byte 95 | type Fixed93 [93]byte 96 | type Fixed94 [94]byte 97 | type Fixed95 [95]byte 98 | type Fixed96 [96]byte 99 | type Fixed97 [97]byte 100 | type Fixed98 [98]byte 101 | type Fixed99 [99]byte 102 | type Fixed100 [100]byte 103 | type Fixed101 [101]byte 104 | type Fixed102 [102]byte 105 | type Fixed103 [103]byte 106 | type Fixed104 [104]byte 107 | type Fixed105 [105]byte 108 | type Fixed106 [106]byte 109 | type Fixed107 [107]byte 110 | type Fixed108 [108]byte 111 | type Fixed109 [109]byte 112 | type Fixed110 [110]byte 113 | type Fixed111 [111]byte 114 | type Fixed112 [112]byte 115 | type Fixed113 [113]byte 116 | type Fixed114 [114]byte 117 | type Fixed115 [115]byte 118 | type Fixed116 [116]byte 119 | type Fixed117 [117]byte 120 | type Fixed118 [118]byte 121 | type Fixed119 [119]byte 122 | type Fixed120 [120]byte 123 | type Fixed121 [121]byte 124 | type Fixed122 [122]byte 125 | type Fixed123 [123]byte 126 | type Fixed124 [124]byte 127 | type Fixed125 [125]byte 128 | type Fixed126 [126]byte 129 | type Fixed127 [127]byte 130 | type Fixed128 [128]byte 131 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/zllangct/ecs 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zllangct/ecs/ac8917a3ed6e077f9e2d3ca2b98ddc48a4d17adb/go.sum -------------------------------------------------------------------------------- /goroutine_id.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2020 Dan Kortschak. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package goroutine provides a single function that will return the runtime's 6 | // ID number for the calling goroutine. 7 | // 8 | // The implementation is derived from Laevus Dexter's comment in Gophers' Slack #darkarts, 9 | // https://gophers.slack.com/archives/C1C1YSQBT/p1593885226448300 post which linked to 10 | // this playground snippet https://play.golang.org/p/CSOp9wyzydP. 11 | 12 | package ecs 13 | 14 | import ( 15 | "reflect" 16 | "unsafe" 17 | ) 18 | 19 | // goroutineID returns the runtime ID of the calling goroutine. 20 | func goroutineID() int64 { 21 | return *(*int64)(add(getg(), goidoff)) 22 | } 23 | 24 | func getg() unsafe.Pointer { 25 | return *(*unsafe.Pointer)(add(getm(), curgoff)) 26 | } 27 | 28 | //go:linkname add runtime.add 29 | //go:nosplit 30 | func add(p unsafe.Pointer, x uintptr) unsafe.Pointer 31 | 32 | //go:linkname getm runtime.getm 33 | func getm() unsafe.Pointer 34 | 35 | var ( 36 | curgoff = offset("*runtime.m", "curg") 37 | goidoff = offset("*runtime.g", "goid") 38 | ) 39 | 40 | // offset returns the offset into typ for the given field. 41 | func offset(typ, field string) uintptr { 42 | rt := toType(typesByString(typ)[0]) 43 | f, _ := rt.Elem().FieldByName(field) 44 | return f.Offset 45 | } 46 | 47 | //go:linkname typesByString reflect.typesByString 48 | func typesByString(s string) []unsafe.Pointer 49 | 50 | //go:linkname toType reflect.toType 51 | func toType(t unsafe.Pointer) reflect.Type 52 | -------------------------------------------------------------------------------- /goroutine_pool.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import ( 4 | runtime2 "runtime" 5 | ) 6 | 7 | // Worker goroutine struct. 8 | type Worker struct { 9 | p *Pool 10 | jobQueue chan func() 11 | stop chan struct{} 12 | } 13 | 14 | // Start goroutine pool. 15 | func (w *Worker) Start() { 16 | c := func() (c bool) { 17 | //defer func() { 18 | // if r := recover(); r != nil { 19 | // Log.Error(r) 20 | // } 21 | // c = true 22 | //}() 23 | var job func() 24 | for { 25 | select { 26 | case job = <-w.jobQueue: 27 | case job = <-w.p.jobQueue: 28 | case <-w.stop: 29 | return 30 | } 31 | job() 32 | } 33 | }() 34 | if c { 35 | go w.Start() 36 | } 37 | } 38 | 39 | // Pool is goroutine pool config. 40 | type Pool struct { 41 | size uint32 42 | jobQueueSize uint32 43 | jobQueue chan func() 44 | workers []*Worker 45 | } 46 | 47 | // NewPool news goroutine pool 48 | func NewPool(size uint32, jobQueueSize uint32) *Pool { 49 | if size == 0 { 50 | size = uint32(2 * runtime2.NumCPU()) 51 | } 52 | if jobQueueSize == 0 { 53 | jobQueueSize = uint32(runtime2.NumCPU()) 54 | } 55 | jobQueue := make(chan func(), jobQueueSize*size) 56 | workerQueue := make([]*Worker, size) 57 | 58 | pool := &Pool{ 59 | size: uint32(size), 60 | jobQueueSize: uint32(jobQueueSize), 61 | jobQueue: jobQueue, 62 | workers: workerQueue, 63 | } 64 | for i := 0; i < cap(pool.workers); i++ { 65 | worker := &Worker{ 66 | p: pool, 67 | jobQueue: make(chan func(), pool.jobQueueSize), 68 | stop: make(chan struct{}), 69 | } 70 | pool.workers[i] = worker 71 | } 72 | return pool 73 | } 74 | 75 | // Add hashKey is an optional parameter, job will be executed in a random worker 76 | // when hashKey is regardless, in fixed worker calculated by hash when hashKey is 77 | // specified 78 | func (p *Pool) Add(job func(), hashKey ...uint32) { 79 | if len(hashKey) > 0 { 80 | p.workers[hashKey[0]%p.size].jobQueue <- job 81 | return 82 | } 83 | p.jobQueue <- job 84 | } 85 | 86 | // Start all workers 87 | func (p *Pool) Start() { 88 | var worker *Worker 89 | for i := 0; i < cap(p.workers); i++ { 90 | worker = p.workers[i] 91 | go worker.Start() 92 | } 93 | } 94 | 95 | // Size get the pool size 96 | func (p *Pool) Size() uint32 { 97 | return p.size 98 | } 99 | 100 | // Release rtStop all workers 101 | func (p *Pool) Release() { 102 | for _, worker := range p.workers { 103 | worker.stop <- struct{}{} 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /goroutine_pool_benchmark_test.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import ( 4 | "sync" 5 | "testing" 6 | ) 7 | 8 | const ( 9 | runTimes = 25 10 | poolSize = 12 11 | queueSize = 50 12 | ) 13 | 14 | func demoTask() { 15 | //time.Sleep(time.Nanosecond * 10) 16 | } 17 | 18 | // BenchmarkGoroutine benchmark the goroutine doing tasks. 19 | func BenchmarkGoroutine(b *testing.B) { 20 | var wg sync.WaitGroup 21 | for i := 0; i < b.N; i++ { 22 | wg.Add(runTimes) 23 | 24 | for j := 0; j < runTimes; j++ { 25 | go func() { 26 | defer wg.Done() 27 | demoTask() 28 | }() 29 | } 30 | 31 | wg.Wait() 32 | } 33 | } 34 | 35 | // BenchmarkGpool benchmarks the goroutine pool. 36 | func BenchmarkGpool(b *testing.B) { 37 | pool := NewPool(poolSize, queueSize) 38 | pool.Start() 39 | 40 | defer pool.Release() 41 | var wg sync.WaitGroup 42 | 43 | b.ResetTimer() 44 | for i := 0; i < b.N; i++ { 45 | wg.Add(runTimes) 46 | for j := 0; j < runTimes; j++ { 47 | pool.Add(func() { 48 | defer wg.Done() 49 | demoTask() 50 | }) 51 | } 52 | 53 | wg.Wait() 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /internal_type_mock.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import ( 4 | "unsafe" 5 | ) 6 | 7 | // Signed is a constraint that permits any signed integer type. 8 | // If future releases of Go add new predeclared signed integer types, 9 | // this constraint will be modified to include them. 10 | type Signed interface { 11 | ~int | ~int8 | ~int16 | ~int32 | ~int64 12 | } 13 | 14 | // Unsigned is a constraint that permits any unsigned integer type. 15 | // If future releases of Go add new predeclared unsigned integer types, 16 | // this constraint will be modified to include them. 17 | type Unsigned interface { 18 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr 19 | } 20 | 21 | // Integer is a constraint that permits any integer type. 22 | // If future releases of Go add new predeclared integer types, 23 | // this constraint will be modified to include them. 24 | type Integer interface { 25 | Signed | Unsigned 26 | } 27 | 28 | // Float is a constraint that permits any floating-point type. 29 | // If future releases of Go add new predeclared floating-point types, 30 | // this constraint will be modified to include them. 31 | type Float interface { 32 | ~float32 | ~float64 33 | } 34 | 35 | // Complex is a constraint that permits any complex numeric type. 36 | // If future releases of Go add new predeclared complex numeric types, 37 | // this constraint will be modified to include them. 38 | type Complex interface { 39 | ~complex64 | ~complex128 40 | } 41 | 42 | // Ordered is a constraint that permits any ordered type: any type 43 | // that supports the operators < <= >= >. 44 | // If future releases of Go add new ordered types, 45 | // this constraint will be modified to include them. 46 | type Ordered interface { 47 | Integer | Float | ~string 48 | } 49 | 50 | type eface struct { 51 | _type *_type 52 | data unsafe.Pointer 53 | } 54 | 55 | type iface struct { 56 | tab *itab 57 | data unsafe.Pointer 58 | } 59 | 60 | type itab struct { 61 | inter *interfacetype 62 | _type *_type 63 | hash uint32 64 | _ [4]byte 65 | fun [1]uintptr 66 | } 67 | 68 | type interfacetype struct { 69 | typ _type 70 | pkgpath name 71 | mhdr []imethod 72 | } 73 | 74 | type _type struct { 75 | size uintptr 76 | ptrdata uintptr 77 | hash uint32 78 | tflag tflag 79 | align uint8 80 | fieldAlign uint8 81 | kind uint8 82 | equal func(unsafe.Pointer, unsafe.Pointer) bool 83 | gcdata *byte 84 | str nameOff 85 | ptrToThis typeOff 86 | } 87 | 88 | type name struct { 89 | bytes *byte 90 | } 91 | 92 | type imethod struct { 93 | name nameOff // name of method 94 | typ typeOff // .(*FuncType) underneath 95 | } 96 | 97 | type tflag uint8 98 | type nameOff int32 // offset to a name 99 | type typeOff int32 // offset to an *rtype 100 | -------------------------------------------------------------------------------- /logger.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | "runtime" 8 | ) 9 | 10 | type Logger interface { 11 | Debug(v ...interface{}) 12 | Info(v ...interface{}) 13 | Error(v ...interface{}) 14 | Fatal(v ...interface{}) 15 | 16 | Debugf(fmt string, v ...interface{}) 17 | Infof(fmt string, v ...interface{}) 18 | Errorf(fmt string, v ...interface{}) 19 | Fatalf(fmt string, v ...interface{}) 20 | } 21 | 22 | var Log Logger = NewStdLog() 23 | 24 | type StdLogLevel uint8 25 | 26 | const ( 27 | StdLogLevelDebug StdLogLevel = iota 28 | StdLogLevelInfo 29 | StdLogLevelError 30 | StdLogLevelFatal 31 | StdLogLevelNoPrint 32 | ) 33 | 34 | type StdLog struct { 35 | logger *log.Logger 36 | level StdLogLevel 37 | } 38 | 39 | func NewStdLog(level ...StdLogLevel) *StdLog { 40 | l := StdLogLevelDebug 41 | if len(level) > 0 { 42 | l = level[0] 43 | } 44 | return &StdLog{ 45 | level: l, 46 | logger: log.New(os.Stdout, "", log.Lshortfile), 47 | } 48 | } 49 | 50 | func (p StdLog) Debug(v ...interface{}) { 51 | p.logger.Output(2, fmt.Sprintf("[DEBUG][%d] %s", goroutineID(), fmt.Sprint(v...))) 52 | } 53 | 54 | func (p StdLog) Debugf(format string, v ...interface{}) { 55 | p.logger.Output(2, fmt.Sprintf("[DEBUG][%d] %s", goroutineID(), fmt.Sprintf(format, v...))) 56 | } 57 | 58 | func (p StdLog) Info(v ...interface{}) { 59 | if p.level > StdLogLevelInfo { 60 | return 61 | } 62 | p.logger.Output(2, fmt.Sprint(v...)) 63 | } 64 | 65 | func (p StdLog) Infof(format string, v ...interface{}) { 66 | if p.level > StdLogLevelInfo { 67 | return 68 | } 69 | p.logger.Output(2, fmt.Sprintf(format, v...)) 70 | } 71 | 72 | func (p StdLog) Error(v ...interface{}) { 73 | if p.level > StdLogLevelError { 74 | return 75 | } 76 | buf := make([]byte, 1024) 77 | for { 78 | n := runtime.Stack(buf, false) 79 | if n < len(buf) { 80 | buf = buf[:n] 81 | break 82 | } 83 | buf = make([]byte, 2*len(buf)) 84 | } 85 | s := fmt.Sprint(append(v, "\n", string(buf))...) 86 | p.logger.Output(2, s) 87 | } 88 | 89 | func (p StdLog) Errorf(format string, v ...interface{}) { 90 | if p.level > StdLogLevelError { 91 | return 92 | } 93 | buf := make([]byte, 1024) 94 | for { 95 | n := runtime.Stack(buf, false) 96 | if n < len(buf) { 97 | buf = buf[:n] 98 | break 99 | } 100 | buf = make([]byte, 2*len(buf)) 101 | } 102 | s := fmt.Sprint(fmt.Sprintf(format, v...), "\n", string(buf)) 103 | p.logger.Output(2, s) 104 | } 105 | 106 | func (p StdLog) Fatal(v ...interface{}) { 107 | if p.level > StdLogLevelFatal { 108 | return 109 | } 110 | p.Error(v...) 111 | os.Exit(1) 112 | } 113 | 114 | func (p StdLog) Fatalf(format string, v ...interface{}) { 115 | if p.level > StdLogLevelFatal { 116 | return 117 | } 118 | p.Errorf(format, v...) 119 | os.Exit(1) 120 | } 121 | -------------------------------------------------------------------------------- /metrics.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import "time" 4 | 5 | type Metrics struct { 6 | enable bool 7 | isPrint bool 8 | m map[string]*MetricReporter 9 | } 10 | 11 | func (m *Metrics) NewReporter(name string) *MetricReporter { 12 | mr := &MetricReporter{ 13 | name: name, 14 | sampleElapsed: []reporterStep{}, 15 | } 16 | mr.metrics = m 17 | return mr 18 | } 19 | 20 | func (m *Metrics) Print() { 21 | if !m.enable { 22 | return 23 | } 24 | for _, reporter := range m.m { 25 | reporter.Print() 26 | } 27 | } 28 | 29 | func NewMetrics(enable bool, print bool) *Metrics { 30 | return &Metrics{ 31 | enable: enable, 32 | isPrint: print, 33 | m: make(map[string]*MetricReporter), 34 | } 35 | } 36 | 37 | type reporterStep struct { 38 | name string 39 | elapsed time.Duration 40 | } 41 | 42 | type MetricReporter struct { 43 | name string 44 | metrics *Metrics 45 | start time.Time 46 | last time.Time 47 | sampleElapsed []reporterStep 48 | elapsedTotal time.Duration 49 | } 50 | 51 | func (m *MetricReporter) Start() { 52 | if !m.metrics.enable { 53 | return 54 | } 55 | m.start = time.Now() 56 | m.last = m.start 57 | } 58 | 59 | func (m *MetricReporter) Sample(name string) { 60 | if !m.metrics.enable { 61 | return 62 | } 63 | now := time.Now() 64 | m.elapsedTotal = now.Sub(m.start) 65 | m.sampleElapsed = append(m.sampleElapsed, reporterStep{ 66 | name: name, 67 | elapsed: now.Sub(m.last), 68 | }) 69 | m.last = now 70 | } 71 | 72 | func (m *MetricReporter) Stop() { 73 | if !m.metrics.enable { 74 | return 75 | } 76 | now := time.Now() 77 | m.elapsedTotal = now.Sub(m.start) 78 | if m.metrics != nil { 79 | m.metrics.m[m.name] = m 80 | } 81 | } 82 | 83 | func (m *MetricReporter) Print(force ...bool) { 84 | if !m.metrics.enable || !m.metrics.isPrint { 85 | if len(force) > 0 && force[0] { 86 | } else { 87 | return 88 | } 89 | } 90 | Log.Infof("%s: cost: %+v\n", m.name, m.elapsedTotal) 91 | for _, r := range m.sampleElapsed { 92 | Log.Infof(" ├─%20s: %+v\n", r.name, r.elapsed) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /optimizer.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import ( 4 | "reflect" 5 | "sort" 6 | "time" 7 | ) 8 | 9 | type ShapeInfo struct { 10 | typ reflect.Type 11 | eNum int64 12 | shapes []IShape 13 | } 14 | 15 | type OptimizerReporter struct { 16 | shapeUsage map[reflect.Type]IShape 17 | } 18 | 19 | func (o *OptimizerReporter) init() { 20 | o.shapeUsage = map[reflect.Type]IShape{} 21 | } 22 | 23 | type optimizer struct { 24 | world *ecsWorld 25 | startTime time.Time 26 | expireTime time.Time 27 | lastSample time.Time 28 | shapeInfos []*ShapeInfo 29 | lastCollectConsumption time.Duration 30 | } 31 | 32 | func newOptimizer(world *ecsWorld) *optimizer { 33 | return &optimizer{world: world} 34 | } 35 | 36 | // Collect 采集分布在各个系统中的OptimizerReporter 37 | func (o *optimizer) collect() { 38 | start := time.Now() 39 | var opts []*OptimizerReporter 40 | for _, value := range o.world.systemFlow.systems { 41 | system, ok := value.(ISystem) 42 | if !ok { 43 | continue 44 | } 45 | if system != nil { 46 | opts = append(opts, system.getOptimizer()) 47 | } 48 | } 49 | //all shapes 50 | var shapeRef = map[reflect.Type]*ShapeInfo{} 51 | for _, opt := range opts { 52 | for _, shp := range opt.shapeUsage { 53 | if info, ok := shapeRef[shp.getType()]; ok { 54 | info.eNum += shp.base().executeNum 55 | } else { 56 | shapeInfo := &ShapeInfo{ 57 | typ: shp.getType(), 58 | eNum: shp.base().executeNum, 59 | shapes: []IShape{shp}, 60 | } 61 | shapeRef[shp.getType()] = shapeInfo 62 | } 63 | } 64 | } 65 | //sort 66 | o.shapeInfos = []*ShapeInfo{} 67 | for _, info := range shapeRef { 68 | o.shapeInfos = append(o.shapeInfos, info) 69 | } 70 | sort.Slice(o.shapeInfos, func(i, j int) bool { 71 | return o.shapeInfos[i].eNum > o.shapeInfos[j].eNum 72 | }) 73 | 74 | o.lastCollectConsumption = time.Since(start) 75 | } 76 | 77 | func (o *optimizer) optimize(IdleTime time.Duration, force bool) { 78 | Log.Infof("start optimize, rest time: %v", IdleTime) 79 | o.startTime = time.Now() 80 | o.lastSample = time.Now() 81 | o.expireTime = o.startTime.Add(IdleTime) 82 | 83 | o.collect() 84 | elapsed := o.elapsedStep() 85 | Log.Infof("collect step 1: %v", elapsed) 86 | 87 | o.memTidy(force) 88 | 89 | rest := o.expire() 90 | total := time.Now().Sub(o.startTime) 91 | Log.Infof("end optimize, rest time: %v, total: %v", rest, total) 92 | } 93 | 94 | func (o *optimizer) expire() time.Duration { 95 | return time.Until(o.expireTime) 96 | } 97 | 98 | func (o *optimizer) elapsed() time.Duration { 99 | return time.Now().Sub(o.startTime) 100 | } 101 | 102 | func (o *optimizer) elapsedStep() time.Duration { 103 | now := time.Now() 104 | r := now.Sub(o.lastSample) 105 | o.lastSample = now 106 | return r 107 | } 108 | 109 | func (o *optimizer) memTidy(force bool) { 110 | //seq := uint32(0) 111 | //m := map[interface{}][]*EntityInfo{} 112 | //o.world.entities.foreach(func(entity Entity, info *EntityInfo) bool { 113 | // c := info.getCompound().Type() 114 | // _, ok := m[c] 115 | // if !ok { 116 | // m[c] = []*EntityInfo{} 117 | // } 118 | // m[c] = append(m[c], info) 119 | // return true 120 | //}) 121 | // 122 | //elapsed := o.elapsedStep() 123 | //rest := o.expire() 124 | //Log.Infof("memTidy step 1: %v, expire: %v", elapsed, rest) 125 | //if !force && rest < time.Millisecond { 126 | // return 127 | //} 128 | // 129 | //for _, infos := range m { 130 | // for _, info := range infos { 131 | // seq++ 132 | // for _, component := range info.components { 133 | // component.setSeq(seq) 134 | // c := o.world.components.getComponentSet(component.Type()).GetByEntity(int64(component.Owner().Entity())) 135 | // verify := c.(IComponent) 136 | // println(component.debugAddress(), verify.debugAddress()) 137 | // if verify.getSeq() != component.getSeq() { 138 | // Log.Errorf("component seq error, %v, %v", verify.getSeq(), component.getSeq()) 139 | // } 140 | // } 141 | // } 142 | //} 143 | // 144 | //elapsed = o.elapsedStep() 145 | //rest = o.expire() 146 | //Log.Infof("memTidy step 2: %v, expire: %v", elapsed, rest) 147 | //if !force && rest < time.Millisecond { 148 | // return 149 | //} 150 | // 151 | //for _, collection := range o.world.components.getCollections() { 152 | // collection.Sort() 153 | // if !force && o.expire() < time.Millisecond { 154 | // break 155 | // } 156 | //} 157 | // 158 | //elapsed = o.elapsedStep() 159 | //rest = o.expire() 160 | //Log.Infof("memTidy step 3: %v, expire: %v", elapsed, rest) 161 | } 162 | -------------------------------------------------------------------------------- /optimizer_benchmark_test.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import ( 4 | "math/rand" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | const ( 10 | testOptimizerDummyMaxFor = 10 11 | testOptimizerEntityMax = 1000000 12 | ) 13 | 14 | type __optimizer_Bench_C_1 struct { 15 | Component[__optimizer_Bench_C_1] 16 | Test1 int 17 | } 18 | 19 | type __optimizer_Bench_C_2 struct { 20 | Component[__optimizer_Bench_C_2] 21 | Test2 int 22 | } 23 | 24 | type __optimizer_Bench_S_1 struct { 25 | System[__optimizer_Bench_S_1] 26 | } 27 | 28 | func (t *__optimizer_Bench_S_1) Init(si SystemInitConstraint) { 29 | t.SetRequirements(si, &__optimizer_Bench_C_1{}, &__optimizer_Bench_C_2{}) 30 | } 31 | 32 | func (t *__optimizer_Bench_S_1) Update(event Event) { 33 | iter := GetComponentAll[__optimizer_Bench_C_1](t) 34 | for c := iter.Begin(); !iter.End(); c = iter.Next() { 35 | c2 := GetRelated[__optimizer_Bench_C_2](t, c.owner) 36 | if c2 == nil { 37 | continue 38 | } 39 | for i := 0; i < testOptimizerDummyMaxFor; i++ { 40 | c.Test1 += i 41 | } 42 | 43 | for i := 0; i < testOptimizerDummyMaxFor; i++ { 44 | c2.Test2 += i 45 | } 46 | } 47 | } 48 | 49 | type __optimizer_Bench_GameECS struct { 50 | world *SyncWorld 51 | entities []Entity 52 | } 53 | 54 | func (g *__optimizer_Bench_GameECS) init() { 55 | println("init") 56 | config := NewDefaultWorldConfig() 57 | g.world = NewSyncWorld(config) 58 | 59 | RegisterSystem[__optimizer_Bench_S_1](g.world) 60 | 61 | for i := 0; i < testOptimizerEntityMax; i++ { 62 | c := &__optimizer_Bench_C_1{} 63 | e := g.world.newEntity() 64 | g.world.Add(e.Entity(), c) 65 | g.entities = append(g.entities, e.Entity()) 66 | } 67 | rand.Seed(0) 68 | rand.Shuffle(len(g.entities), func(i, j int) { g.entities[i], g.entities[j] = g.entities[j], g.entities[i] }) 69 | 70 | for i := 0; i < testOptimizerEntityMax; i++ { 71 | c := &__optimizer_Bench_C_2{} 72 | g.world.addComponent(g.entities[i], c) 73 | } 74 | } 75 | 76 | func BenchmarkNoOptimizer(b *testing.B) { 77 | //go func() { 78 | // http.ListenAndServe(":6060", nil) 79 | //}() 80 | println("start") 81 | game := &__optimizer_Bench_GameECS{} 82 | game.init() 83 | game.world.update() 84 | 85 | b.ResetTimer() 86 | b.ReportAllocs() 87 | 88 | for i := 0; i < b.N; i++ { 89 | game.world.update() 90 | } 91 | } 92 | 93 | func BenchmarkWithOptimizer(b *testing.B) { 94 | //go func() { 95 | // http.ListenAndServe(":6060", nil) 96 | //}() 97 | 98 | game := &__optimizer_Bench_GameECS{} 99 | game.init() 100 | game.world.update() 101 | 102 | game.world.optimize(time.Second*10, true) 103 | 104 | b.ResetTimer() 105 | b.ReportAllocs() 106 | 107 | for i := 0; i < b.N; i++ { 108 | game.world.update() 109 | } 110 | } 111 | 112 | func BenchmarkTest(b *testing.B) { 113 | arr := make([]int, 0, 100) 114 | for i, _ := range arr { 115 | arr[i] = i 116 | } 117 | b.ResetTimer() 118 | 119 | for i := 0; i < b.N; i++ { 120 | 121 | } 122 | } 123 | 124 | func BenchmarkTest2(b *testing.B) { 125 | type test struct { 126 | Name string 127 | Age int 128 | } 129 | t := test{Name: "test", Age: 1} 130 | m := map[test]int{t: 1} 131 | for i := 0; i < b.N; i++ { 132 | _ = m[t] 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /ordered_int_set.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | type OrderedIntSet[T Integer] []T 4 | 5 | func (c *OrderedIntSet[T]) InsertIndex(it T) int { 6 | if len(*c) == 0 { 7 | return 0 8 | } 9 | l := 0 10 | r := len(*c) - 1 11 | m := 0 12 | for l < r { 13 | m = (l + r) / 2 14 | if (*c)[m] > it { 15 | r = m - 1 16 | } else if (*c)[m] < it { 17 | l = m + 1 18 | } else { 19 | return -1 20 | } 21 | } 22 | if (*c)[l] < it { 23 | l = l + 1 24 | } else if (*c)[l] > it { 25 | } else { 26 | l = l - 1 27 | } 28 | return l 29 | } 30 | 31 | func (c *OrderedIntSet[T]) Find(it T) int { 32 | l := 0 33 | r := len(*c) - 1 34 | m := 0 35 | for l <= r { 36 | m = (l + r) / 2 37 | if (*c)[m] == it { 38 | return m 39 | } else if (*c)[m] > it { 40 | r = m - 1 41 | } else { 42 | l = m + 1 43 | } 44 | } 45 | return -1 46 | } 47 | 48 | func (c *OrderedIntSet[T]) Exist(it T) bool { 49 | return c.Find(it) != -1 50 | } 51 | 52 | func (c *OrderedIntSet[T]) IsSubSet(subSet OrderedIntSet[T]) bool { 53 | offset := 0 54 | length := len(*c) 55 | exist := false 56 | var temp T 57 | for i := 0; i < len(subSet); i++ { 58 | exist = false 59 | temp = subSet[i] 60 | for j := offset; j < length; j++ { 61 | if (*c)[j] == temp { 62 | offset = j + 1 63 | exist = true 64 | break 65 | } 66 | } 67 | if !exist { 68 | return false 69 | } 70 | } 71 | return true 72 | } 73 | 74 | func (c *OrderedIntSet[T]) Add(it T) bool { 75 | idx := c.InsertIndex(it) 76 | if idx < 0 { 77 | return false 78 | } 79 | *c = append(*c, 0) 80 | copy((*c)[idx+1:], (*c)[idx:len(*c)-1]) 81 | (*c)[idx] = it 82 | return true 83 | } 84 | 85 | func (c *OrderedIntSet[T]) Remove(it T) bool { 86 | idx := c.Find(it) 87 | if idx < 0 { 88 | return false 89 | } 90 | *c = append((*c)[:idx], (*c)[idx+1:]...) 91 | return true 92 | } 93 | -------------------------------------------------------------------------------- /ordered_int_set_test.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import "testing" 4 | 5 | func TestOrderedIntSet_Add(t *testing.T) { 6 | c := OrderedIntSet[uint16]{} 7 | insert := []uint16{7, 3, 6, 2, 9, 4} 8 | for _, it := range insert { 9 | c.Add(it) 10 | } 11 | 12 | want := []uint16{2, 3, 4, 6, 7, 9} 13 | for i := 0; i < len(c); i++ { 14 | if c[i] != want[i] { 15 | t.Errorf("c[%d] = %d, want %d", i, c[i], want[i]) 16 | } 17 | } 18 | } 19 | 20 | func TestOrderedIntSet_Remove(t *testing.T) { 21 | c := OrderedIntSet[uint16]{} 22 | insert := []uint16{7, 3, 6, 2, 9, 4} 23 | for _, it := range insert { 24 | c.Add(it) 25 | } 26 | 27 | c.Remove(3) 28 | 29 | c.Add(1) 30 | 31 | want := []uint16{1, 2, 4, 6, 7, 9} 32 | for i := 0; i < len(c); i++ { 33 | if c[i] != want[i] { 34 | t.Errorf("c[%d] = %d, want %d", i, c[i], want[i]) 35 | } 36 | } 37 | } 38 | 39 | func TestOrderedIntSet_InsertIndex(t *testing.T) { 40 | c := OrderedIntSet[uint16]{} 41 | insert := []uint16{7, 3, 6, 2, 9, 4} 42 | for _, it := range insert { 43 | c.Add(it) 44 | } 45 | 46 | want := []uint16{2, 3, 4, 6, 7, 9} 47 | for i := 0; i < len(c); i++ { 48 | if c[i] != want[i] { 49 | t.Errorf("c[%d] = %d, want %d", i, c[i], want[i]) 50 | } 51 | } 52 | 53 | wantIndex := 3 54 | if got := c.InsertIndex(5); got != wantIndex { 55 | t.Errorf("insertIndex() = %v, want %v", got, wantIndex) 56 | } 57 | } 58 | 59 | func TestOrderedIntSet_Find(t *testing.T) { 60 | c := OrderedIntSet[uint16]{} 61 | insert := []uint16{7, 3, 6, 2, 9, 4} 62 | for _, it := range insert { 63 | c.Add(it) 64 | } 65 | 66 | want := []uint16{2, 3, 4, 6, 7, 9} 67 | for i := 0; i < len(c); i++ { 68 | if c[i] != want[i] { 69 | t.Errorf("c[%d] = %d, want %d", i, c[i], want[i]) 70 | } 71 | } 72 | 73 | wantIndex := 4 74 | if got := c.Find(7); got != wantIndex { 75 | t.Errorf("Find() = %v, want %v", got, wantIndex) 76 | } 77 | } 78 | 79 | func TestOrderedIntSet_IsSubSet(t *testing.T) { 80 | c := OrderedIntSet[uint16]{} 81 | insert := []uint16{7, 3, 6, 2, 9, 4} 82 | for _, it := range insert { 83 | c.Add(it) 84 | } 85 | 86 | want := []uint16{2, 3, 4, 6, 7, 9} 87 | for i := 0; i < len(c); i++ { 88 | if c[i] != want[i] { 89 | t.Errorf("c[%d] = %d, want %d", i, c[i], want[i]) 90 | } 91 | } 92 | 93 | subSet := []uint16{3, 4, 6} 94 | wantBool := true 95 | if got := c.IsSubSet(subSet); got != wantBool { 96 | t.Errorf("IsSubSet() = %v, want %v", got, wantBool) 97 | } 98 | 99 | subSet = []uint16{2, 3, 4, 6, 7, 9} 100 | wantBool = true 101 | if got := c.IsSubSet(subSet); got != wantBool { 102 | t.Errorf("IsSubSet() = %v, want %v", got, wantBool) 103 | } 104 | 105 | subSet = []uint16{3, 4, 8} 106 | wantBool = false 107 | if got := c.IsSubSet(subSet); got != wantBool { 108 | t.Errorf("IsSubSet() = %v, want %v", got, wantBool) 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /res/img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zllangct/ecs/ac8917a3ed6e077f9e2d3ca2b98ddc48a4d17adb/res/img.png -------------------------------------------------------------------------------- /res/img1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zllangct/ecs/ac8917a3ed6e077f9e2d3ca2b98ddc48a4d17adb/res/img1.png -------------------------------------------------------------------------------- /res/img2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zllangct/ecs/ac8917a3ed6e077f9e2d3ca2b98ddc48a4d17adb/res/img2.png -------------------------------------------------------------------------------- /res/img3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zllangct/ecs/ac8917a3ed6e077f9e2d3ca2b98ddc48a4d17adb/res/img3.png -------------------------------------------------------------------------------- /res/img4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zllangct/ecs/ac8917a3ed6e077f9e2d3ca2b98ddc48a4d17adb/res/img4.png -------------------------------------------------------------------------------- /serialize.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | // TODO 世界的序列化、反序列化 4 | type ICustomSerialize interface { 5 | Serialize() []byte 6 | DeSerialize(b []byte) 7 | } 8 | -------------------------------------------------------------------------------- /shape_getter.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import ( 4 | "reflect" 5 | "unsafe" 6 | ) 7 | 8 | type IShape interface { 9 | base() *shapeBase 10 | getType() reflect.Type 11 | } 12 | 13 | type shapeBase struct { 14 | sys ISystem 15 | executeNum int64 16 | typ reflect.Type 17 | } 18 | 19 | func (s *shapeBase) base() *shapeBase { 20 | return s 21 | } 22 | 23 | func (s *shapeBase) init(typ reflect.Type, getter IShape) { 24 | opt := s.sys.getOptimizer() 25 | if _, ok := opt.shapeUsage[typ]; !ok { 26 | opt.shapeUsage[typ] = getter 27 | } 28 | } 29 | 30 | type ShapeIndices struct { 31 | subTypes []uint16 32 | subOffset []uintptr 33 | containers []IComponentSet 34 | readOnly []bool 35 | } 36 | 37 | type Shape[T any] struct { 38 | shapeBase 39 | initializer SystemInitConstraint 40 | mainKeyIndex int 41 | subTypes []uint16 42 | subOffset []uintptr 43 | containers []IComponentSet 44 | readOnly []bool 45 | cur *T 46 | valid bool 47 | } 48 | 49 | func NewShape[T any](initializer SystemInitConstraint) *Shape[T] { 50 | if initializer.isValid() { 51 | panic("out of initialization stage") 52 | } 53 | sys := initializer.getSystem() 54 | getter := &Shape[T]{ 55 | shapeBase: shapeBase{sys: sys}, 56 | initializer: initializer, 57 | } 58 | 59 | typ := reflect.TypeOf(getter) 60 | getter.init(typ, getter) 61 | 62 | sysReq := sys.GetRequirements() 63 | if sysReq == nil { 64 | return nil 65 | } 66 | 67 | getter.cur = new(T) 68 | typIns := reflect.TypeOf(*getter.cur) 69 | for i := 0; i < typIns.NumField(); i++ { 70 | field := typIns.Field(i) 71 | if !field.Type.Implements(reflect.TypeOf((*IComponent)(nil)).Elem()) || !sys.isRequire(field.Type.Elem()) { 72 | continue 73 | } 74 | if r, ok := sysReq[field.Type.Elem()]; ok { 75 | if r.getPermission() == ComponentReadOnly { 76 | getter.readOnly = append(getter.readOnly, true) 77 | } else { 78 | getter.readOnly = append(getter.readOnly, false) 79 | } 80 | } 81 | meta := sys.World().getComponentMetaInfoByType(field.Type.Elem()) 82 | getter.subTypes = append(getter.subTypes, meta.it) 83 | getter.subOffset = append(getter.subOffset, field.Offset) 84 | } 85 | 86 | getter.containers = make([]IComponentSet, len(getter.subTypes)) 87 | 88 | if len(getter.subTypes) == 0 { 89 | return nil 90 | } 91 | 92 | getter.valid = true 93 | 94 | return getter 95 | } 96 | 97 | func (s *Shape[T]) IsValid() bool { 98 | return s.valid 99 | } 100 | 101 | func (s *Shape[T]) getType() reflect.Type { 102 | if s.typ == nil { 103 | s.typ = TypeOf[Shape[T]]() 104 | } 105 | return s.typ 106 | } 107 | 108 | func (s *Shape[T]) Get() IShapeIterator[T] { 109 | s.executeNum++ 110 | 111 | if !s.valid { 112 | return EmptyShapeIter[T]() 113 | } 114 | 115 | var mainComponent IComponentSet 116 | var mainKeyIndex int 117 | for i := 0; i < len(s.subTypes); i++ { 118 | c := s.sys.World().getComponentSetByIntType(s.subTypes[i]) 119 | if c == nil || c.Len() == 0 { 120 | return EmptyShapeIter[T]() 121 | } 122 | if mainComponent == nil || mainComponent.Len() > c.Len() { 123 | mainComponent = c 124 | mainKeyIndex = i 125 | } 126 | s.containers[i] = c 127 | } 128 | 129 | if s.mainKeyIndex == 0 { 130 | mainKeyIndex = s.mainKeyIndex 131 | mainComponent = s.containers[mainKeyIndex] 132 | } 133 | 134 | return NewShapeIterator[T]( 135 | ShapeIndices{ 136 | subTypes: s.subTypes, 137 | subOffset: s.subOffset, 138 | containers: s.containers, 139 | readOnly: s.readOnly, 140 | }, 141 | mainKeyIndex) 142 | } 143 | 144 | func (s *Shape[T]) GetSpecific(entity Entity) (*T, bool) { 145 | if !s.valid { 146 | return s.cur, false 147 | } 148 | for i := 0; i < len(s.subTypes); i++ { 149 | subPointer := s.containers[i].getPointerByEntity(entity) 150 | if subPointer == nil { 151 | return s.cur, false 152 | } 153 | if s.readOnly[i] { 154 | *(**byte)(unsafe.Add(unsafe.Pointer(s.cur), s.subOffset[i])) = &(*(*byte)(subPointer)) 155 | } else { 156 | *(**byte)(unsafe.Add(unsafe.Pointer(s.cur), s.subOffset[i])) = (*byte)(subPointer) 157 | } 158 | } 159 | return s.cur, true 160 | } 161 | 162 | func (s *Shape[T]) SetGuide(component IComponent) *Shape[T] { 163 | meta := s.initializer.getSystem().World().getComponentMetaInfoByType(component.Type()) 164 | for i, r := range s.subTypes { 165 | if r == meta.it { 166 | s.mainKeyIndex = i 167 | return s 168 | } 169 | } 170 | return s 171 | } 172 | -------------------------------------------------------------------------------- /shape_getter_test.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | type __ShapeGetter_Test_C_1 struct { 9 | Component[__ShapeGetter_Test_C_1] 10 | Field1 int 11 | } 12 | 13 | type __ShapeGetter_Test_C_2 struct { 14 | Component[__ShapeGetter_Test_C_2] 15 | Field1 int 16 | } 17 | 18 | type __ShapeGetter_Test_Shape_1 struct { 19 | c1 *__ShapeGetter_Test_C_1 20 | c2 *__ShapeGetter_Test_C_2 21 | } 22 | 23 | type __ShapeGetter_Test_S_1 struct { 24 | System[__ShapeGetter_Test_S_1] 25 | 26 | getter1 *Shape[__ShapeGetter_Test_Shape_1] 27 | } 28 | 29 | func (t *__ShapeGetter_Test_S_1) Init(initializer SystemInitConstraint) { 30 | t.SetRequirements(initializer, &__ShapeGetter_Test_C_1{}, &__ShapeGetter_Test_C_2{}) 31 | 32 | t.getter1 = NewShape[__ShapeGetter_Test_Shape_1](initializer) 33 | if t.getter1 == nil { 34 | initializer.SetBroken("invalid getter") 35 | } 36 | } 37 | 38 | func (t *__ShapeGetter_Test_S_1) Update(event Event) { 39 | Log.Infof("__ShapeGetter_Test_S_1.Update, frame:%d", event.Frame) 40 | iter := t.getter1.Get() 41 | for s := iter.Begin(); !iter.End(); s = iter.Next() { 42 | Log.Infof("s.c1:%+v, s.c2:%+v", s.c1, s.c2) 43 | } 44 | } 45 | 46 | func TestNewShapeGetter(t *testing.T) { 47 | world := NewSyncWorld(NewDefaultWorldConfig()) 48 | RegisterSystem[__ShapeGetter_Test_S_1](world) 49 | 50 | world.Startup() 51 | 52 | for i := 0; i < 3; i++ { 53 | e := world.newEntity().Entity() 54 | world.Add(e, &__ShapeGetter_Test_C_1{Field1: i}, &__ShapeGetter_Test_C_2{Field1: i * 10}) 55 | } 56 | 57 | world.Update() 58 | time.Sleep(time.Second) 59 | world.Update() 60 | } 61 | -------------------------------------------------------------------------------- /shape_iter.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import "unsafe" 4 | 5 | type IShapeIterator[T any] interface { 6 | Begin() *T 7 | Val() *T 8 | Next() *T 9 | End() bool 10 | } 11 | 12 | type ShapeIter[T any] struct { 13 | indices ShapeIndices 14 | maxLen int 15 | offset int 16 | begin int 17 | mainKeyIndex int 18 | cur *T 19 | } 20 | 21 | func EmptyShapeIter[T any]() IShapeIterator[T] { 22 | return &ShapeIter[T]{} 23 | } 24 | 25 | func NewShapeIterator[T any](indices ShapeIndices, mainKeyIndex int) IShapeIterator[T] { 26 | iter := &ShapeIter[T]{ 27 | indices: indices, 28 | maxLen: indices.containers[mainKeyIndex].Len(), 29 | mainKeyIndex: mainKeyIndex, 30 | offset: 0, 31 | } 32 | 33 | return iter 34 | } 35 | 36 | // TODO 热点 37 | func (s *ShapeIter[T]) tryNext() *T { 38 | skip := false 39 | find := false 40 | var p unsafe.Pointer 41 | var ec *EmptyComponent 42 | for i := s.offset; i < s.maxLen; i++ { 43 | //TODO check if this is the best way to do this 44 | p = s.indices.containers[s.mainKeyIndex].getPointerByIndex(int64(s.offset)) 45 | ec = (*EmptyComponent)(p) 46 | if s.indices.readOnly[s.mainKeyIndex] { 47 | *(**byte)(unsafe.Add(unsafe.Pointer(s.cur), s.indices.subOffset[s.mainKeyIndex])) = &(*(*byte)(p)) 48 | } else { 49 | *(**byte)(unsafe.Add(unsafe.Pointer(s.cur), s.indices.subOffset[s.mainKeyIndex])) = (*byte)(p) 50 | } 51 | entity := ec.Owner() 52 | skip = s.getSiblings(entity) 53 | if !skip { 54 | s.offset = i 55 | find = true 56 | break 57 | } 58 | } 59 | if !find { 60 | s.cur = nil 61 | } 62 | 63 | return s.cur 64 | } 65 | 66 | func (s *ShapeIter[T]) getSiblings(entity Entity) bool { 67 | for i := 0; i < len(s.indices.subTypes); i++ { 68 | if i == s.mainKeyIndex { 69 | continue 70 | } 71 | subPointer := s.indices.containers[i].getPointerByEntity(entity) 72 | if subPointer == nil { 73 | return true 74 | } 75 | s.trans(i, subPointer) 76 | } 77 | return false 78 | } 79 | 80 | func (s *ShapeIter[T]) trans(i int, subPointer unsafe.Pointer) { 81 | if s.indices.readOnly[i] { 82 | *(**byte)(unsafe.Add(unsafe.Pointer(s.cur), s.indices.subOffset[i])) = &(*(*byte)(subPointer)) 83 | } else { 84 | *(**byte)(unsafe.Add(unsafe.Pointer(s.cur), s.indices.subOffset[i])) = (*byte)(subPointer) 85 | } 86 | } 87 | 88 | func (s *ShapeIter[T]) End() bool { 89 | if s.cur == nil { 90 | return true 91 | } 92 | return false 93 | } 94 | 95 | func (s *ShapeIter[T]) Begin() *T { 96 | if s.maxLen != 0 { 97 | s.offset = 0 98 | s.cur = new(T) 99 | s.tryNext() 100 | } 101 | return s.cur 102 | } 103 | 104 | func (s *ShapeIter[T]) Val() *T { 105 | if s.cur == nil || !s.End() { 106 | s.Begin() 107 | } 108 | return s.cur 109 | } 110 | 111 | func (s *ShapeIter[T]) Next() *T { 112 | s.offset++ 113 | if !s.End() { 114 | s.tryNext() 115 | } else { 116 | s.cur = nil 117 | } 118 | return s.cur 119 | } 120 | -------------------------------------------------------------------------------- /sparse_array.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | type SparseArray[K Integer, V any] struct { 4 | UnorderedCollection[V] 5 | indices []int32 6 | idx2Key map[int32]int32 7 | maxKey K 8 | shrinkThreshold int32 9 | initSize int 10 | } 11 | 12 | func NewSparseArray[K Integer, V any](initSize ...int) *SparseArray[K, V] { 13 | typ := TypeOf[V]() 14 | eleSize := typ.Size() 15 | size := InitMaxSize / eleSize 16 | if len(initSize) > 0 { 17 | size = uintptr(initSize[0]) / eleSize 18 | } 19 | c := &SparseArray[K, V]{ 20 | UnorderedCollection: UnorderedCollection[V]{ 21 | data: make([]V, 0, size), 22 | eleSize: eleSize, 23 | }, 24 | idx2Key: map[int32]int32{}, 25 | initSize: int(size), 26 | } 27 | 28 | if size > 0 { 29 | c.indices = make([]int32, 0, size) 30 | } 31 | 32 | switch any(*new(K)).(type) { 33 | case int8, uint8: 34 | c.shrinkThreshold = 127 35 | case uint16: 36 | c.shrinkThreshold = 255 37 | default: 38 | c.shrinkThreshold = 1024 39 | } 40 | 41 | return c 42 | } 43 | 44 | func (g *SparseArray[K, V]) Add(key K, value *V) *V { 45 | length := len(g.indices) 46 | // already existed 47 | if key < K(length) && g.indices[key] != 0 { 48 | return nil 49 | } 50 | _, idx := g.UnorderedCollection.Add(value) 51 | if key >= K(length) { 52 | m := K(0) 53 | if length == 0 { 54 | m = key + 1 55 | } else if length < int(g.shrinkThreshold) { 56 | m = key * 2 57 | } else { 58 | m = key * 5 / 4 59 | } 60 | newIndices := make([]int32, m) 61 | count := copy(newIndices, g.indices) 62 | if count != length { 63 | panic("copy failed") 64 | } 65 | g.indices = newIndices 66 | } 67 | 68 | g.idx2Key[int32(idx)] = int32(key) 69 | g.indices[key] = int32(idx + 1) 70 | if key > g.maxKey { 71 | g.maxKey = key 72 | } 73 | 74 | return &g.data[idx] 75 | } 76 | 77 | func (g *SparseArray[K, V]) Remove(key K) *V { 78 | if key > g.maxKey { 79 | return nil 80 | } 81 | idx := g.indices[key] - 1 82 | removed, oldIndex, newIndex := g.UnorderedCollection.Remove(int64(idx)) 83 | 84 | lastKey := g.idx2Key[int32(oldIndex)] 85 | g.indices[lastKey] = int32(newIndex + 1) 86 | g.indices[key] = 0 87 | g.idx2Key[idx] = lastKey 88 | delete(g.idx2Key, int32(oldIndex)) 89 | 90 | g.shrink(key) 91 | 92 | return removed 93 | } 94 | 95 | func (g *SparseArray[K, V]) Exist(key K) bool { 96 | if key > g.maxKey { 97 | return false 98 | } 99 | return !(g.indices[key] == 0) 100 | } 101 | 102 | func (g *SparseArray[K, V]) Get(key K) *V { 103 | if key > g.maxKey { 104 | return nil 105 | } 106 | idx := g.indices[key] - 1 107 | if idx < 0 { 108 | return nil 109 | } 110 | return g.UnorderedCollection.Get(int64(idx)) 111 | } 112 | 113 | func (g *SparseArray[K, V]) Clear() { 114 | if g.Len() == 0 { 115 | return 116 | } 117 | g.UnorderedCollection.Clear() 118 | if int(g.maxKey) < 1024 { 119 | for i := 0; i < len(g.indices); i++ { 120 | g.indices[i] = 0 121 | } 122 | } else { 123 | g.indices = make([]int32, 0, g.initSize) 124 | } 125 | g.maxKey = 0 126 | g.idx2Key = map[int32]int32{} 127 | } 128 | 129 | func (g *SparseArray[K, V]) shrink(key K) { 130 | if key < g.maxKey { 131 | return 132 | } 133 | 134 | g.maxKey = 0 135 | for i := key; i > 0; i-- { 136 | if g.indices[i] != 0 { 137 | g.maxKey = i 138 | break 139 | } 140 | } 141 | 142 | if int32(g.maxKey) < g.shrinkThreshold { 143 | g.maxKey = K(g.shrinkThreshold) 144 | } 145 | 146 | if len(g.indices) > 1024 && int(g.maxKey) < len(g.indices)/2 { 147 | m := (g.maxKey + 1) * 5 / 4 148 | newIndices := make([]int32, m) 149 | count := copy(newIndices, g.indices[:m]) 150 | if count != int(m) { 151 | panic("copy failed") 152 | } 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /system.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import ( 4 | "reflect" 5 | "sync" 6 | "unsafe" 7 | ) 8 | 9 | type SystemState uint8 10 | 11 | const ( 12 | SystemStateInvalid SystemState = iota 13 | SystemStateInit 14 | SystemStateStart 15 | SystemStatePause 16 | SystemStateUpdate 17 | SystemStateDestroy 18 | SystemStateDestroyed 19 | ) 20 | 21 | type SystemInitConstraint struct { 22 | sys *ISystem 23 | } 24 | 25 | func (s *SystemInitConstraint) getSystem() ISystem { 26 | if s.sys == nil { 27 | panic("out of initialization stage") 28 | } 29 | return *s.sys 30 | } 31 | 32 | func (s *SystemInitConstraint) SetBroken(reason string) { 33 | (*s.sys).setBroken() 34 | panic(reason) 35 | } 36 | 37 | func (s *SystemInitConstraint) isValid() bool { 38 | return *s.sys == nil 39 | } 40 | 41 | type ISystem interface { 42 | Type() reflect.Type 43 | Order() Order 44 | World() IWorld 45 | GetRequirements() map[reflect.Type]IRequirement 46 | IsRequire(component IComponent) bool 47 | ID() int64 48 | GetUtility() IUtility 49 | 50 | pause() 51 | resume() 52 | stop() 53 | getPointer() unsafe.Pointer 54 | isRequire(componentType reflect.Type) bool 55 | setOrder(order Order) 56 | setRequirements(initializer SystemInitConstraint, rqs ...IRequirement) 57 | getState() SystemState 58 | setState(state SystemState) 59 | setSecurity(isSafe bool) 60 | isThreadSafe() bool 61 | setExecuting(isExecuting bool) 62 | isExecuting() bool 63 | baseInit(world *ecsWorld, ins ISystem) 64 | getOptimizer() *OptimizerReporter 65 | getGetterCache() *GetterCache 66 | setBroken() 67 | isValid() bool 68 | setUtility(u IUtility) 69 | } 70 | 71 | type SystemObject interface { 72 | __SystemIdentification() 73 | } 74 | 75 | type SystemPointer[T SystemObject] interface { 76 | ISystem 77 | *T 78 | } 79 | 80 | type systemIdentification struct{} 81 | 82 | func (s systemIdentification) __SystemIdentification() {} 83 | 84 | type System[T SystemObject] struct { 85 | systemIdentification 86 | lock sync.Mutex 87 | requirements map[reflect.Type]IRequirement 88 | getterCache *GetterCache 89 | order Order 90 | optimizerReporter *OptimizerReporter 91 | world *ecsWorld 92 | utility IUtility 93 | realType reflect.Type 94 | state SystemState 95 | valid bool 96 | isSafe bool 97 | executing bool 98 | id int64 99 | } 100 | 101 | func (s *System[T]) instance() (sys ISystem) { 102 | (*iface)(unsafe.Pointer(&sys)).data = unsafe.Pointer(s) 103 | return 104 | } 105 | 106 | func (s *System[T]) rawInstance() *T { 107 | return (*T)(unsafe.Pointer(s)) 108 | } 109 | 110 | func (s *System[T]) ID() int64 { 111 | if s.id == 0 { 112 | s.id = LocalUniqueID() 113 | } 114 | return s.id 115 | } 116 | 117 | func (s *System[T]) SetRequirements(initializer SystemInitConstraint, rqs ...IRequirement) { 118 | if initializer.isValid() { 119 | panic("out of initialization stage") 120 | } 121 | s.setRequirements(initializer, rqs...) 122 | } 123 | 124 | func (s *System[T]) setRequirementsInternal(rqs ...IRequirement) { 125 | if s.requirements == nil { 126 | s.requirements = map[reflect.Type]IRequirement{} 127 | } 128 | var typ reflect.Type 129 | for _, value := range rqs { 130 | typ = value.Type() 131 | s.requirements[typ] = value 132 | } 133 | } 134 | 135 | func (s *System[T]) isInitialized() bool { 136 | return s.state >= SystemStateInit 137 | } 138 | 139 | func (s *System[T]) setRequirements(initializer SystemInitConstraint, rqs ...IRequirement) { 140 | if s.requirements == nil { 141 | s.requirements = map[reflect.Type]IRequirement{} 142 | } 143 | var typ reflect.Type 144 | for _, value := range rqs { 145 | typ = value.Type() 146 | value.check(initializer) 147 | s.requirements[typ] = value 148 | s.World().getComponentMetaInfoByType(typ) 149 | } 150 | } 151 | 152 | func (s *System[T]) setUtility(u IUtility) { 153 | s.utility = u 154 | } 155 | 156 | func (s *System[T]) setSecurity(isSafe bool) { 157 | s.isSafe = isSafe 158 | } 159 | func (s *System[T]) isThreadSafe() bool { 160 | return s.isSafe 161 | } 162 | 163 | func (s *System[T]) GetUtility() IUtility { 164 | return s.utility 165 | } 166 | 167 | func (s *System[T]) pause() { 168 | if s.getState() == SystemStateUpdate { 169 | s.setState(SystemStatePause) 170 | } 171 | } 172 | 173 | func (s *System[T]) resume() { 174 | if s.getState() == SystemStatePause { 175 | s.setState(SystemStateUpdate) 176 | } 177 | } 178 | 179 | func (s *System[T]) stop() { 180 | if s.getState() < SystemStateDestroy { 181 | s.setState(SystemStateDestroy) 182 | } 183 | } 184 | 185 | func (s *System[T]) getState() SystemState { 186 | return s.state 187 | } 188 | 189 | func (s *System[T]) setState(state SystemState) { 190 | s.state = state 191 | } 192 | 193 | func (s *System[T]) setBroken() { 194 | s.valid = false 195 | } 196 | 197 | func (s *System[T]) isValid() bool { 198 | return s.valid 199 | } 200 | 201 | func (s *System[T]) setExecuting(isExecuting bool) { 202 | s.executing = isExecuting 203 | } 204 | 205 | func (s *System[T]) isExecuting() bool { 206 | return s.executing 207 | } 208 | 209 | func (s *System[T]) GetRequirements() map[reflect.Type]IRequirement { 210 | return s.requirements 211 | } 212 | 213 | func (s *System[T]) IsRequire(com IComponent) bool { 214 | return s.isRequire(com.Type()) 215 | } 216 | 217 | func (s *System[T]) isRequire(typ reflect.Type) bool { 218 | _, ok := s.requirements[typ] 219 | return ok 220 | } 221 | 222 | func (s *System[T]) baseInit(world *ecsWorld, ins ISystem) { 223 | s.requirements = map[reflect.Type]IRequirement{} 224 | s.getterCache = NewGetterCache(len(s.requirements)) 225 | 226 | if ins.Order() == OrderInvalid { 227 | s.setOrder(OrderDefault) 228 | } 229 | s.world = world 230 | 231 | s.valid = true 232 | 233 | initializer := SystemInitConstraint{} 234 | is := ISystem(s) 235 | initializer.sys = &is 236 | if i, ok := ins.(InitReceiver); ok { 237 | err := TryAndReport(func() error { 238 | return i.Init(initializer) 239 | }) 240 | if err != nil { 241 | Log.Error(err) 242 | } 243 | } 244 | *initializer.sys = nil 245 | initializer.sys = nil 246 | 247 | s.state = SystemStateStart 248 | } 249 | 250 | func (s *System[T]) getPointer() unsafe.Pointer { 251 | return unsafe.Pointer(s) 252 | } 253 | 254 | func (s *System[T]) Type() reflect.Type { 255 | if s.realType == nil { 256 | s.realType = TypeOf[T]() 257 | } 258 | return s.realType 259 | } 260 | 261 | func (s *System[T]) setOrder(order Order) { 262 | if s.isInitialized() { 263 | return 264 | } 265 | 266 | s.order = order 267 | } 268 | 269 | func (s *System[T]) Order() Order { 270 | return s.order 271 | } 272 | 273 | func (s *System[T]) World() IWorld { 274 | return s.world 275 | } 276 | 277 | func (s *System[T]) GetEntityInfo(entity Entity) (*EntityInfo, bool) { 278 | return s.world.getEntityInfo(entity) 279 | } 280 | 281 | // get optimizer 282 | func (s *System[T]) getOptimizer() *OptimizerReporter { 283 | if s.optimizerReporter == nil { 284 | s.optimizerReporter = &OptimizerReporter{} 285 | s.optimizerReporter.init() 286 | } 287 | return s.optimizerReporter 288 | } 289 | 290 | func (s *System[T]) getGetterCache() *GetterCache { 291 | return s.getterCache 292 | } 293 | -------------------------------------------------------------------------------- /system_event.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import "time" 4 | 5 | type Event struct { 6 | Frame uint64 7 | Delta time.Duration 8 | } 9 | 10 | type InitReceiver interface { 11 | Init(initializer SystemInitConstraint) error 12 | } 13 | 14 | type SyncBeforeStartReceiver interface { 15 | SyncBeforeStart(event Event) 16 | } 17 | 18 | type StartReceiver interface { 19 | Start(event Event) 20 | } 21 | 22 | type SyncAfterStartReceiver interface { 23 | SyncAfterStart(event Event) 24 | } 25 | 26 | type SyncBeforePreUpdateReceiver interface { 27 | SyncBeforePreUpdate(event Event) 28 | } 29 | 30 | type PreUpdateReceiver interface { 31 | PreUpdate(event Event) 32 | } 33 | 34 | type SyncAfterPreUpdateReceiver interface { 35 | SyncAfterPreUpdate(event Event) 36 | } 37 | 38 | type SyncBeforeUpdateReceiver interface { 39 | SyncBeforeUpdate(event Event) 40 | } 41 | 42 | type UpdateReceiver interface { 43 | Update(event Event) 44 | } 45 | 46 | type SyncAfterUpdateReceiver interface { 47 | SyncAfterUpdate(event Event) 48 | } 49 | 50 | type SyncBeforePostUpdateReceiver interface { 51 | SyncBeforePostUpdate(event Event) 52 | } 53 | 54 | type PostUpdateReceiver interface { 55 | PostUpdate(event Event) 56 | } 57 | 58 | type SyncAfterPostUpdateReceiver interface { 59 | SyncAfterPostUpdate(event Event) 60 | } 61 | 62 | type SyncBeforeDestroyReceiver interface { 63 | SyncBeforeDestroy(event Event) 64 | } 65 | 66 | type DestroyReceiver interface { 67 | Destroy(event Event) 68 | } 69 | 70 | type SyncAfterPostDestroyReceiver interface { 71 | SyncAfterDestroy(event Event) 72 | } 73 | -------------------------------------------------------------------------------- /system_flow.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "sync" 7 | ) 8 | 9 | const ( 10 | StageSyncBeforeStart Stage = iota 11 | StageStart 12 | StageSyncAfterStart 13 | 14 | StageSyncBeforePreUpdate 15 | StagePreUpdate 16 | StageSyncAfterPreUpdate 17 | 18 | StageSyncBeforeUpdate 19 | StageUpdate 20 | StageSyncAfterUpdate 21 | 22 | StageSyncBeforePostUpdate 23 | StagePostUpdate 24 | StageSyncAfterPostUpdate 25 | 26 | StageSyncBeforeDestroy 27 | StageDestroy 28 | StageSyncAfterDestroy 29 | ) 30 | 31 | // Stage system execute period:start->pre_update->update->pre_destroy->destroy 32 | type Stage uint32 33 | 34 | // Order default suborder of system 35 | type Order int32 36 | 37 | const ( 38 | OrderFront Order = -1 39 | OrderInvalid Order = 0 40 | OrderAppend Order = 99999999 41 | OrderDefault Order = OrderAppend 42 | ) 43 | 44 | // SystemGroupList extension of system group slice 45 | type SystemGroupList []*SystemGroup 46 | 47 | // system execute flow 48 | type systemFlow struct { 49 | world *ecsWorld 50 | stages map[Stage]SystemGroupList 51 | stageList []Stage 52 | systems map[reflect.Type]ISystem 53 | wg *sync.WaitGroup 54 | } 55 | 56 | func newSystemFlow(runtime *ecsWorld) *systemFlow { 57 | sf := &systemFlow{ 58 | world: runtime, 59 | systems: map[reflect.Type]ISystem{}, 60 | wg: &sync.WaitGroup{}, 61 | } 62 | sf.init() 63 | return sf 64 | } 65 | 66 | // initialize the system flow 67 | func (p *systemFlow) init() { 68 | p.stageList = []Stage{ 69 | StageSyncBeforeStart, 70 | StageStart, 71 | StageSyncAfterStart, 72 | 73 | StageSyncBeforePreUpdate, 74 | StagePreUpdate, 75 | StageSyncAfterPreUpdate, 76 | 77 | StageSyncBeforeUpdate, 78 | StageUpdate, 79 | StageSyncAfterUpdate, 80 | 81 | StageSyncBeforePostUpdate, 82 | StagePostUpdate, 83 | StageSyncAfterPostUpdate, 84 | 85 | StageSyncBeforeDestroy, 86 | StageDestroy, 87 | StageSyncAfterDestroy, 88 | } 89 | p.reset() 90 | } 91 | 92 | func (p *systemFlow) reset() { 93 | p.stages = make(map[Stage]SystemGroupList) 94 | for _, value := range p.stageList { 95 | p.stages[value] = SystemGroupList{} 96 | sgFront := NewSystemGroup() 97 | sgFront.order = OrderFront 98 | sgAppend := NewSystemGroup() 99 | sgAppend.order = OrderAppend 100 | p.stages[value] = append(p.stages[value], sgFront, sgAppend) 101 | } 102 | } 103 | 104 | func (p *systemFlow) flushTempTask() { 105 | tasks := p.world.components.getTempTasks() 106 | p.wg.Add(len(tasks)) 107 | for _, task := range tasks { 108 | wg := p.wg 109 | fn := task 110 | p.world.addJob(func() { 111 | fn() 112 | wg.Done() 113 | }) 114 | } 115 | p.wg.Wait() 116 | } 117 | 118 | func (p *systemFlow) systemUpdate(event Event) { 119 | var sq SystemGroupList 120 | var sys ISystem 121 | var imp bool = false 122 | var runSync bool = false 123 | var fn func(event Event) 124 | for _, period := range p.stageList { 125 | sq = p.stages[period] 126 | for _, sl := range sq { 127 | if sl.systemCount() == 0 { 128 | continue 129 | } 130 | for ss := sl.Begin(); !sl.End(); ss = sl.Next() { 131 | if systemCount := len(ss); systemCount != 0 { 132 | for i := 0; i < systemCount; i++ { 133 | sys = ss[i] 134 | 135 | if !sys.isValid() { 136 | continue 137 | } 138 | 139 | imp = false 140 | runSync = false 141 | state := ss[i].getState() 142 | 143 | if period > StageSyncAfterStart { 144 | if state == SystemStateStart { 145 | state = SystemStateUpdate 146 | sys.setState(SystemStateUpdate) 147 | } 148 | } 149 | 150 | if state == SystemStateStart { 151 | if period > StageSyncAfterStart { 152 | continue 153 | } 154 | switch period { 155 | case StageSyncBeforeStart: 156 | system, ok := sys.(SyncBeforeStartReceiver) 157 | fn = system.SyncBeforeStart 158 | imp = ok 159 | runSync = true 160 | case StageStart: 161 | system, ok := sys.(StartReceiver) 162 | fn = system.Start 163 | imp = ok 164 | runSync = false 165 | case StageSyncAfterStart: 166 | system, ok := sys.(SyncAfterStartReceiver) 167 | fn = system.SyncAfterStart 168 | imp = ok 169 | runSync = true 170 | } 171 | } else if state == SystemStateUpdate { 172 | if period < StageSyncBeforePreUpdate || period > StageSyncAfterPostUpdate { 173 | continue 174 | } 175 | switch period { 176 | case StageSyncBeforePreUpdate: 177 | system, ok := sys.(SyncBeforePreUpdateReceiver) 178 | fn = system.SyncBeforePreUpdate 179 | imp = ok 180 | runSync = true 181 | case StagePreUpdate: 182 | system, ok := sys.(PreUpdateReceiver) 183 | fn = system.PreUpdate 184 | imp = ok 185 | runSync = true 186 | case StageSyncAfterPreUpdate: 187 | system, ok := sys.(SyncAfterPreUpdateReceiver) 188 | fn = system.SyncAfterPreUpdate 189 | imp = ok 190 | runSync = true 191 | 192 | case StageSyncBeforeUpdate: 193 | system, ok := sys.(SyncBeforeUpdateReceiver) 194 | fn = system.SyncBeforeUpdate 195 | imp = ok 196 | runSync = true 197 | case StageUpdate: 198 | system, ok := sys.(UpdateReceiver) 199 | fn = system.Update 200 | imp = ok 201 | runSync = false 202 | case StageSyncAfterUpdate: 203 | system, ok := sys.(SyncAfterUpdateReceiver) 204 | fn = system.SyncAfterUpdate 205 | imp = ok 206 | runSync = true 207 | 208 | case StageSyncBeforePostUpdate: 209 | system, ok := sys.(SyncBeforePostUpdateReceiver) 210 | fn = system.SyncBeforePostUpdate 211 | imp = ok 212 | runSync = true 213 | case StagePostUpdate: 214 | system, ok := sys.(PostUpdateReceiver) 215 | fn = system.PostUpdate 216 | imp = ok 217 | runSync = false 218 | case StageSyncAfterPostUpdate: 219 | system, ok := sys.(SyncAfterPostUpdateReceiver) 220 | fn = system.SyncAfterPostUpdate 221 | imp = ok 222 | runSync = true 223 | } 224 | } else if state == SystemStateDestroy { 225 | if period < StageSyncBeforeDestroy { 226 | continue 227 | } 228 | switch period { 229 | case StageSyncBeforeDestroy: 230 | system, ok := sys.(SyncBeforeDestroyReceiver) 231 | fn = system.SyncBeforeDestroy 232 | imp = ok 233 | runSync = true 234 | case StageDestroy: 235 | system, ok := sys.(DestroyReceiver) 236 | fn = system.Destroy 237 | imp = ok 238 | runSync = false 239 | case StageSyncAfterDestroy: 240 | system, ok := sys.(SyncAfterPostDestroyReceiver) 241 | fn = system.SyncAfterDestroy 242 | imp = ok 243 | runSync = true 244 | 245 | sys.setState(SystemStateDestroyed) 246 | } 247 | } 248 | 249 | if !imp { 250 | continue 251 | } 252 | if runSync { 253 | sys.setExecuting(true) 254 | sys.setSecurity(true) 255 | fn(event) 256 | sys.setSecurity(false) 257 | sys.setExecuting(false) 258 | } else { 259 | wrapper := func(fn func(event2 Event), e Event) func() { 260 | sys.setExecuting(true) 261 | return func() { 262 | defer func() { 263 | sys.setExecuting(false) 264 | p.wg.Done() 265 | }() 266 | fn(e) 267 | } 268 | } 269 | p.wg.Add(1) 270 | p.world.addJob(wrapper(fn, event)) 271 | } 272 | } 273 | } 274 | p.wg.Wait() 275 | } 276 | } 277 | } 278 | } 279 | 280 | func (p *systemFlow) run(event Event) { 281 | reporter := p.world.metrics.NewReporter("system_flow_run") 282 | reporter.Start() 283 | 284 | //Log.Info("system flow # Temp Task Execute #") 285 | p.flushTempTask() 286 | reporter.Sample("Temp Task Execute") 287 | 288 | //Log.Info("system flow # Logic #") 289 | p.systemUpdate(event) 290 | reporter.Sample("system execute") 291 | 292 | //Log.Info("system flow # Clear Disposable #") 293 | p.world.components.clearDisposable() 294 | reporter.Sample("Clear Disposable") 295 | 296 | p.flushTempTask() 297 | reporter.Sample("Temp Task Execute") 298 | 299 | reporter.Stop() 300 | reporter.Print() 301 | } 302 | 303 | // register method only in world init or func init(){} 304 | func (p *systemFlow) register(system ISystem) { 305 | if p.world.getStatus() != WorldStatusInitialized { 306 | panic("system register only in world init") 307 | } 308 | 309 | //init function call 310 | system.baseInit(p.world, system) 311 | 312 | order := system.Order() 313 | if order > OrderAppend { 314 | Log.Errorf("system order must less then %d, resort order to %d", OrderAppend+1, OrderAppend) 315 | order = OrderAppend 316 | } 317 | 318 | for _, period := range p.stageList { 319 | 320 | if !p.isImpEvent(system, period) { 321 | continue 322 | } 323 | 324 | sl := p.stages[period] 325 | if order == OrderFront { 326 | p.stages[period][0].insert(system) 327 | } else if order == OrderAppend { 328 | p.stages[period][len(sl)-1].insert(system) 329 | } else { 330 | for i, v := range sl { 331 | if order == v.order { 332 | v.insert(system) 333 | break 334 | } else if order < v.order { 335 | sg := NewSystemGroup() 336 | sg.order = order 337 | sg.insert(system) 338 | temp := append(SystemGroupList{}, sl[i-1:]...) 339 | p.stages[period] = append(append(sl[:i-1], sg), temp...) 340 | break 341 | } 342 | } 343 | } 344 | } 345 | 346 | p.systems[system.Type()] = system 347 | } 348 | 349 | func (p *systemFlow) isImpEvent(system ISystem, period Stage) bool { 350 | imp := false 351 | switch period { 352 | case StageSyncBeforeStart: 353 | _, imp = system.(SyncBeforeStartReceiver) 354 | case StageStart: 355 | _, imp = system.(StartReceiver) 356 | case StageSyncAfterStart: 357 | _, imp = system.(SyncAfterStartReceiver) 358 | case StageSyncBeforePreUpdate: 359 | _, imp = system.(SyncBeforePreUpdateReceiver) 360 | case StagePreUpdate: 361 | _, imp = system.(PreUpdateReceiver) 362 | case StageSyncAfterPreUpdate: 363 | _, imp = system.(SyncAfterPreUpdateReceiver) 364 | case StageSyncBeforeUpdate: 365 | _, imp = system.(SyncBeforeUpdateReceiver) 366 | case StageUpdate: 367 | _, imp = system.(UpdateReceiver) 368 | case StageSyncAfterUpdate: 369 | _, imp = system.(SyncAfterUpdateReceiver) 370 | case StageSyncBeforePostUpdate: 371 | _, imp = system.(SyncBeforePostUpdateReceiver) 372 | case StagePostUpdate: 373 | _, imp = system.(PostUpdateReceiver) 374 | case StageSyncAfterPostUpdate: 375 | _, imp = system.(SyncAfterPostUpdateReceiver) 376 | case StageSyncBeforeDestroy: 377 | _, imp = system.(SyncBeforeDestroyReceiver) 378 | case StageDestroy: 379 | _, imp = system.(DestroyReceiver) 380 | case StageSyncAfterDestroy: 381 | _, imp = system.(SyncAfterPostDestroyReceiver) 382 | } 383 | return imp 384 | } 385 | 386 | func (p *systemFlow) stop() { 387 | p.reset() 388 | } 389 | 390 | func (p *systemFlow) SystemInfoPrint() { 391 | m := map[Stage]string{ 392 | StageSyncBeforeStart: "StageSyncBeforeStart", 393 | StageStart: "StageStart", 394 | StageSyncAfterStart: "StageSyncAfterStart", 395 | 396 | StageSyncBeforePreUpdate: "StageSyncBeforePreUpdate", 397 | StagePreUpdate: "StagePreUpdate", 398 | StageSyncAfterPreUpdate: "StageSyncAfterPreUpdate", 399 | 400 | StageSyncBeforeUpdate: "StageSyncBeforeUpdate", 401 | StageUpdate: "StageUpdate", 402 | StageSyncAfterUpdate: "StageSyncAfterUpdate", 403 | 404 | StageSyncBeforePostUpdate: "StageSyncBeforePostUpdate", 405 | StagePostUpdate: "StagePostUpdate", 406 | StageSyncAfterPostUpdate: "StageSyncAfterPostUpdate", 407 | 408 | StageSyncBeforeDestroy: "StageSyncBeforeDestroy", 409 | StageDestroy: "StageDestroy", 410 | StageSyncAfterDestroy: "StageSyncAfterDestroy", 411 | } 412 | Log.Infof("┌──────────────── # System Info # ─────────────────") 413 | Log.Infof("├─ Total: %d", len(p.systems)) 414 | 415 | var output []string 416 | var sq SystemGroupList 417 | for pi, period := range p.stageList { 418 | var slContent []string 419 | sq = p.stages[period] 420 | for i, sl := range sq { 421 | sl.resort() 422 | batchTotal := sl.batchCount() 423 | batch := 0 424 | var batchContent []string 425 | for ss := sl.Begin(); !sl.End(); ss = sl.Next() { 426 | if systemCount := len(ss); systemCount != 0 { 427 | 428 | str := "│ │ └─ " 429 | if batch == batchTotal-1 { 430 | str = "│ └─ " 431 | } 432 | for i := 0; i < systemCount; i++ { 433 | str += fmt.Sprintf("%s ", ss[i].Type().Name()) 434 | } 435 | if batch == batchTotal-1 { 436 | batchContent = append(batchContent, fmt.Sprintf("│ └─ Batch %d", batch)) 437 | } else { 438 | batchContent = append(batchContent, fmt.Sprintf("│ ├─ Batch %d", batch)) 439 | } 440 | batchContent = append(batchContent, str) 441 | } 442 | batch++ 443 | } 444 | if len(batchContent) > 0 { 445 | s := make([]string, 0, len(batchContent)+1) 446 | if i == len(sq)-1 { 447 | s = append(s, fmt.Sprintf("│ └─ Order %d", i)) 448 | } else { 449 | s = append(s, fmt.Sprintf("│ ├─ Order %d", i)) 450 | } 451 | s = append(s, batchContent...) 452 | slContent = append(slContent, s...) 453 | } 454 | } 455 | if len(slContent) > 0 { 456 | s := make([]string, 0, len(slContent)+1) 457 | if pi == len(p.stageList)-1 { 458 | s = append(s, fmt.Sprintf("└─ Stage %s", m[period])) 459 | } else { 460 | s = append(s, fmt.Sprintf("├─ Stage %s", m[period])) 461 | } 462 | s = append(s, slContent...) 463 | output = append(output, s...) 464 | } 465 | } 466 | 467 | for _, v := range output { 468 | Log.Info(v) 469 | } 470 | Log.Infof("└────────────── # System Info End # ───────────────") 471 | } 472 | -------------------------------------------------------------------------------- /system_group.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import ( 4 | "reflect" 5 | "sort" 6 | ) 7 | 8 | var emptySystemGroupIterator = &SystemGroupIterator{} 9 | 10 | // Node system tree node 11 | type Node struct { 12 | parent *Node 13 | children []*Node 14 | val ISystem 15 | } 16 | 17 | func (p *Node) isFriend(node *Node) bool { 18 | for com, r := range p.val.GetRequirements() { 19 | for comTarget, rTarget := range node.val.GetRequirements() { 20 | if comTarget == com { 21 | if r.getPermission() == ComponentReadOnly && rTarget.getPermission() == ComponentReadOnly { 22 | continue 23 | } 24 | return true 25 | } 26 | } 27 | } 28 | return false 29 | } 30 | 31 | func (p *Node) attach(node *Node) { 32 | isAttached := false 33 | for i := 0; i < len(p.children); i++ { 34 | if p.children[i].isFriend(node) { 35 | p.children[i].attach(node) 36 | isAttached = true 37 | break 38 | } 39 | } 40 | if !isAttached { 41 | if p.val == node.val { 42 | Log.Error("repeated system") 43 | return 44 | } 45 | p.children = append(p.children, node) 46 | } 47 | } 48 | 49 | // SystemGroup system group ordered by interrelation 50 | type SystemGroup struct { 51 | SystemGroupIterator 52 | systems []*Node 53 | ref map[reflect.Type]int 54 | root *Node 55 | order Order 56 | batchTotal int 57 | maxPeerBatch int 58 | ordered bool 59 | } 60 | 61 | func NewSystemGroup() *SystemGroup { 62 | return &SystemGroup{ 63 | systems: make([]*Node, 0), 64 | ref: map[reflect.Type]int{}, 65 | ordered: true, 66 | root: &Node{ 67 | parent: nil, 68 | children: []*Node{}, 69 | val: nil, 70 | }, 71 | } 72 | } 73 | 74 | func (p *SystemGroup) refCount(rqs map[reflect.Type]IRequirement) int { 75 | ref := 0 76 | for com, _ := range rqs { 77 | ref += p.ref[com] - 1 78 | } 79 | return ref 80 | } 81 | 82 | func (p *SystemGroup) resort() { 83 | if p.ordered { 84 | return 85 | } 86 | sort.Slice(p.systems, func(i, j int) bool { 87 | return p.refCount(p.systems[i].val.GetRequirements()) > 88 | p.refCount(p.systems[j].val.GetRequirements()) 89 | }) 90 | 91 | p.root.children = []*Node{} 92 | for _, node := range p.systems { 93 | node.children = []*Node{} 94 | p.root.attach(node) 95 | } 96 | p.ordered = true 97 | 98 | p.batchTotal = 0 99 | p.maxPeerBatch = 0 100 | 101 | var top []*Node = p.root.children 102 | for len(top) > 0 { 103 | count := 0 104 | temp := top 105 | top = make([]*Node, 0) 106 | for _, node := range temp { 107 | count++ 108 | top = append(top, node.children...) 109 | } 110 | if count > p.maxPeerBatch { 111 | p.maxPeerBatch = count 112 | } 113 | p.batchTotal++ 114 | } 115 | 116 | p.resetIter() 117 | } 118 | 119 | func (p *SystemGroup) resetIter() { 120 | if p.group == nil { 121 | p.group = p 122 | } 123 | curLen := len(p.SystemGroupIterator.top) 124 | if curLen-p.maxPeerBatch == 1 { 125 | p.SystemGroupIterator.top = p.SystemGroupIterator.top[:p.maxPeerBatch] 126 | p.SystemGroupIterator.topTemp = p.SystemGroupIterator.topTemp[:p.maxPeerBatch] 127 | p.SystemGroupIterator.buffer = p.SystemGroupIterator.buffer[:p.maxPeerBatch] 128 | } else if curLen-p.maxPeerBatch == -1 { 129 | p.SystemGroupIterator.top = append(p.SystemGroupIterator.top, (*Node)(nil)) 130 | p.SystemGroupIterator.topTemp = append(p.SystemGroupIterator.topTemp, (*Node)(nil)) 131 | p.SystemGroupIterator.buffer = append(p.SystemGroupIterator.buffer, ISystem(nil)) 132 | } else if curLen-p.maxPeerBatch == 0 { 133 | // do nothing 134 | } else { 135 | p.SystemGroupIterator.top = make([]*Node, p.maxPeerBatch) 136 | p.SystemGroupIterator.topTemp = make([]*Node, p.maxPeerBatch) 137 | p.SystemGroupIterator.buffer = make([]ISystem, p.maxPeerBatch) 138 | } 139 | } 140 | 141 | func (p *SystemGroup) systemCount() int { 142 | return len(p.systems) 143 | } 144 | 145 | func (p *SystemGroup) batchCount() int { 146 | return p.batchTotal 147 | } 148 | 149 | func (p *SystemGroup) maxSystemCountPeerBatch() int { 150 | return p.maxPeerBatch 151 | } 152 | 153 | // get all systems 154 | func (p *SystemGroup) all() []ISystem { 155 | systems := make([]ISystem, len(p.systems)) 156 | for i, n := range p.systems { 157 | systems[i] = n.val 158 | } 159 | return systems 160 | } 161 | 162 | // insert system 163 | func (p *SystemGroup) insert(sys ISystem) { 164 | //get system's required components 165 | rqs := sys.GetRequirements() 166 | if len(rqs) == 0 { 167 | //panic("invalid system") 168 | } 169 | //reference count 170 | for com, _ := range rqs { 171 | if _, ok := p.ref[com]; ok { 172 | p.ref[com] += 1 173 | } else { 174 | p.ref[com] = 1 175 | } 176 | } 177 | //add system 178 | node := &Node{ 179 | children: make([]*Node, 0), 180 | val: sys, 181 | } 182 | p.systems = append(p.systems, node) 183 | //set unordered 184 | p.ordered = false 185 | } 186 | 187 | // has system 188 | func (p *SystemGroup) has(sys ISystem) bool { 189 | for _, system := range p.systems { 190 | if system.val.Type() == sys.Type() { 191 | return true 192 | } 193 | } 194 | return false 195 | } 196 | 197 | // remove system 198 | func (p *SystemGroup) remove(sys ISystem) { 199 | //get system's required components 200 | rqs := sys.GetRequirements() 201 | has := false 202 | for i, system := range p.systems { 203 | if system.val.ID() == sys.ID() { 204 | p.systems = append(p.systems[:i], p.systems[i+1:]...) 205 | has = true 206 | break 207 | } 208 | } 209 | if !has { 210 | return 211 | } 212 | //reference count 213 | for com, _ := range rqs { 214 | if _, ok := p.ref[com]; ok { 215 | p.ref[com] -= 1 216 | } else { 217 | panic("component ref wrong") 218 | } 219 | } 220 | //set unordered 221 | p.ordered = false 222 | } 223 | 224 | func (p *SystemGroup) iter() *SystemGroupIterator { 225 | if !p.ordered { 226 | p.resort() 227 | } 228 | 229 | if p.maxPeerBatch == 0 { 230 | return emptySystemGroupIterator 231 | } 232 | 233 | return &SystemGroupIterator{ 234 | group: p, 235 | top: make([]*Node, p.maxPeerBatch), 236 | topTemp: make([]*Node, p.maxPeerBatch), 237 | buffer: make([]ISystem, p.maxPeerBatch), 238 | } 239 | } 240 | 241 | type SystemGroupIterator struct { 242 | group *SystemGroup 243 | top []*Node 244 | topTemp []*Node 245 | buffer []ISystem 246 | topSize int 247 | size int 248 | } 249 | 250 | func (s *SystemGroupIterator) Begin() []ISystem { 251 | if s.group == nil { 252 | return nil 253 | } 254 | if !s.group.ordered { 255 | s.group.resort() 256 | } 257 | copy(s.top, s.group.root.children) 258 | s.topSize = len(s.group.root.children) 259 | return s.Next() 260 | } 261 | 262 | func (s *SystemGroupIterator) Next() []ISystem { 263 | if s.topSize == 0 { 264 | s.size = 0 265 | return nil 266 | } 267 | s.topTemp, s.top = s.top, s.topTemp 268 | tempSize := s.topSize 269 | s.topSize = 0 270 | s.size = 0 271 | for i := 0; i < tempSize; i++ { 272 | s.buffer[s.size] = s.topTemp[i].val 273 | s.size++ 274 | for j := 0; j < len(s.topTemp[i].children); j++ { 275 | s.top[s.topSize+j] = s.topTemp[i].children[j] 276 | } 277 | s.topSize += len(s.topTemp[i].children) 278 | } 279 | 280 | if s.size == 0 { 281 | return nil 282 | } 283 | return s.buffer[:s.size] 284 | } 285 | 286 | func (s *SystemGroupIterator) End() bool { 287 | return s.size == 0 288 | } 289 | -------------------------------------------------------------------------------- /system_group_test.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | type __systemGroup_Test_C_1 struct { 8 | Component[__systemGroup_Test_C_1] 9 | } 10 | type __systemGroup_Test_C_2 struct { 11 | Component[__systemGroup_Test_C_2] 12 | } 13 | type __systemGroup_Test_C_3 struct { 14 | Component[__systemGroup_Test_C_3] 15 | } 16 | type __systemGroup_Test_C_4 struct { 17 | Component[__systemGroup_Test_C_4] 18 | } 19 | type __systemGroup_Test_C_5 struct { 20 | Component[__systemGroup_Test_C_5] 21 | } 22 | type __systemGroup_Test_C_6 struct { 23 | Component[__systemGroup_Test_C_6] 24 | } 25 | type __systemGroup_Test_C_7 struct { 26 | Component[__systemGroup_Test_C_7] 27 | } 28 | type __systemGroup_Test_C_8 struct { 29 | Component[__systemGroup_Test_C_8] 30 | } 31 | type __systemGroup_Test_C_9 struct { 32 | Component[__systemGroup_Test_C_9] 33 | } 34 | type __systemGroup_Test_C_10 struct { 35 | Component[__systemGroup_Test_C_10] 36 | } 37 | 38 | type __systemGroup_Test_S_1 struct { 39 | System[__systemGroup_Test_S_1] 40 | Name int 41 | } 42 | 43 | func NewTestSystem(ID int, rqs ...IRequirement) *__systemGroup_Test_S_1 { 44 | s := &__systemGroup_Test_S_1{Name: ID} 45 | s.setRequirementsInternal(rqs...) 46 | return s 47 | } 48 | 49 | func (p *__systemGroup_Test_S_1) Call(label int) interface{} { 50 | switch label { 51 | case 1: 52 | println(p.Name) 53 | } 54 | return nil 55 | } 56 | 57 | func newSystemGroupTestSystem() []ISystem { 58 | return []ISystem{ 59 | NewTestSystem(1, &__systemGroup_Test_C_1{}, &__systemGroup_Test_C_2{}), 60 | NewTestSystem(2, &ReadOnly[__systemGroup_Test_C_1]{}, &__systemGroup_Test_C_3{}), 61 | NewTestSystem(3, &__systemGroup_Test_C_2{}, &__systemGroup_Test_C_5{}), 62 | NewTestSystem(4, &__systemGroup_Test_C_2{}, &__systemGroup_Test_C_3{}, &__systemGroup_Test_C_6{}), 63 | NewTestSystem(5, &__systemGroup_Test_C_7{}), 64 | NewTestSystem(6, &__systemGroup_Test_C_9{}, &__systemGroup_Test_C_10{}), 65 | NewTestSystem(7, &__systemGroup_Test_C_6{}), 66 | NewTestSystem(8, &__systemGroup_Test_C_1{}, &__systemGroup_Test_C_5{}), 67 | NewTestSystem(9, &__systemGroup_Test_C_4{}, &__systemGroup_Test_C_6{}), 68 | NewTestSystem(10, &__systemGroup_Test_C_7{}, &__systemGroup_Test_C_5{}), 69 | NewTestSystem(11, &ReadOnly[__systemGroup_Test_C_1]{}), 70 | } 71 | } 72 | 73 | func TestNewSystemGroupIterEmpty(t *testing.T) { 74 | sg := NewSystemGroup() 75 | sg.resort() 76 | 77 | Log.Infof("========== system count %d, Batch count: %d, Max peer Batch: %d:", sg.systemCount(), sg.batchCount(), sg.maxSystemCountPeerBatch()) 78 | 79 | for ss := sg.Begin(); !sg.End(); ss = sg.Next() { 80 | Log.Info("========== batch:") 81 | for _, s := range ss { 82 | Log.Infof("%s\n", ObjectToString(s)) 83 | } 84 | } 85 | } 86 | 87 | func TestNewSystemGroupIter(t *testing.T) { 88 | tests := newSystemGroupTestSystem() 89 | sg := NewSystemGroup() 90 | for _, test := range tests { 91 | sg.insert(test) 92 | } 93 | 94 | sg.resort() 95 | 96 | Log.Infof("========== system count %d, Batch count: %d, Max peer Batch: %d:", sg.systemCount(), sg.batchCount(), sg.maxSystemCountPeerBatch()) 97 | 98 | for ss := sg.Begin(); !sg.End(); ss = sg.Next() { 99 | Log.Info("========== batch:") 100 | for _, s := range ss { 101 | Log.Infof("%s\n", ObjectToString(s)) 102 | } 103 | } 104 | } 105 | 106 | func TestNewSystemGroupIterTemp(t *testing.T) { 107 | tests := newSystemGroupTestSystem() 108 | sg := NewSystemGroup() 109 | for _, test := range tests { 110 | sg.insert(test) 111 | } 112 | 113 | sg.resort() 114 | 115 | Log.Infof("========== system count %d, Batch count: %d, Max peer Batch: %d:", sg.systemCount(), sg.batchCount(), sg.maxSystemCountPeerBatch()) 116 | 117 | for ss := sg.Begin(); !sg.End(); ss = sg.Next() { 118 | Log.Info("========== batch:") 119 | for _, s := range ss { 120 | Log.Infof("%s\n", ObjectToString(s)) 121 | } 122 | } 123 | 124 | for ss := sg.Begin(); !sg.End(); ss = sg.Next() { 125 | Log.Info("========== batch:") 126 | for _, s := range ss { 127 | Log.Infof("%s\n", ObjectToString(s)) 128 | } 129 | } 130 | } 131 | 132 | func BenchmarkSystemGroupIter(b *testing.B) { 133 | tests := newSystemGroupTestSystem() 134 | sg := NewSystemGroup() 135 | for _, test := range tests { 136 | sg.insert(test) 137 | } 138 | 139 | sg.resort() 140 | 141 | b.ResetTimer() 142 | for i := 0; i < b.N; i++ { 143 | for ss := sg.Begin(); !sg.End(); ss = sg.Next() { 144 | _ = ss 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /system_requirement.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import ( 4 | "reflect" 5 | "unsafe" 6 | ) 7 | 8 | const ( 9 | ComponentReadWrite ComponentPermission = 0 10 | ComponentReadOnly ComponentPermission = 1 11 | ) 12 | 13 | type ComponentPermission uint8 14 | 15 | type IRequirement interface { 16 | Type() reflect.Type 17 | getPermission() ComponentPermission 18 | check(initializer SystemInitConstraint) 19 | } 20 | 21 | type ReadOnly[T ComponentObject] struct{} 22 | 23 | func (r *ReadOnly[T]) Type() reflect.Type { 24 | return TypeOf[T]() 25 | } 26 | 27 | func (r *ReadOnly[T]) getPermission() ComponentPermission { 28 | return ComponentReadOnly 29 | } 30 | 31 | func (r *ReadOnly[T]) check(initializer SystemInitConstraint) { 32 | ins := any((*T)(unsafe.Pointer(r))).(IComponent) 33 | ins.check(initializer) 34 | } 35 | -------------------------------------------------------------------------------- /unordered_collection.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import ( 4 | "unsafe" 5 | ) 6 | 7 | const ( 8 | InitMaxSize = 1024 * 16 9 | //InitMaxSize = 0 10 | SeqMax uint32 = 0xFFFFFFFF 11 | ) 12 | 13 | type UnorderedCollection[T any] struct { 14 | eleSize uintptr 15 | len int64 16 | initSize int64 17 | data []T 18 | } 19 | 20 | func NewUnorderedCollection[T any](initSize ...int) *UnorderedCollection[T] { 21 | typ := TypeOf[T]() 22 | eleSize := typ.Size() 23 | size := InitMaxSize / eleSize 24 | c := &UnorderedCollection[T]{ 25 | data: make([]T, 0, size), 26 | } 27 | if len(initSize) > 0 { 28 | c.initSize = int64(initSize[0]) 29 | c.eleSize = uintptr(initSize[0]) / eleSize 30 | } 31 | return c 32 | } 33 | 34 | func (c *UnorderedCollection[T]) Get(idx int64) *T { 35 | return (*T)(unsafe.Add(unsafe.Pointer(&c.data[0]), uintptr(idx)*c.eleSize)) 36 | } 37 | 38 | func (c *UnorderedCollection[T]) Add(element *T) (*T, int64) { 39 | if int64(len(c.data)) > c.len { 40 | c.data[c.len] = *element 41 | } else { 42 | c.data = append(c.data, *element) 43 | } 44 | idx := c.len 45 | c.len++ 46 | return &c.data[idx], idx 47 | } 48 | 49 | func (c *UnorderedCollection[T]) Remove(idx int64) (*T, int64, int64) { 50 | if idx < 0 { 51 | return nil, 0, 0 52 | } 53 | lastIdx := c.len - 1 54 | 55 | c.data[idx], c.data[lastIdx] = c.data[lastIdx], c.data[idx] 56 | c.shrink() 57 | c.len-- 58 | removed := c.data[lastIdx] 59 | return &removed, lastIdx, idx 60 | } 61 | 62 | func (c *UnorderedCollection[T]) Len() int { 63 | return int(c.len) 64 | } 65 | 66 | func (c *UnorderedCollection[T]) Range(f func(element *T) bool) { 67 | for i := int64(0); i < c.len; i++ { 68 | if !f(&c.data[i]) { 69 | break 70 | } 71 | } 72 | } 73 | 74 | func (c *UnorderedCollection[T]) Clear() { 75 | c.data = make([]T, 0, c.initSize) 76 | c.len = 0 77 | } 78 | 79 | func (c *UnorderedCollection[T]) shrink() { 80 | var threshold int64 81 | if len(c.data) < InitMaxSize { 82 | return 83 | } else { 84 | threshold = int64(float64(c.len) * 1.25) 85 | } 86 | if int64(len(c.data)) > threshold { 87 | //c.data = c.data[:threshold] 88 | newData := make([]T, threshold) 89 | copy(newData, c.data) 90 | c.data = newData 91 | } 92 | } 93 | 94 | func (c *UnorderedCollection[T]) getIndexByElePointer(element *T) int64 { 95 | if c.len == 0 { 96 | return -1 97 | } 98 | offset := uintptr(unsafe.Pointer(element)) - uintptr(unsafe.Pointer(&c.data[0])) 99 | if offset%c.eleSize != 0 { 100 | return -1 101 | } 102 | idx := int64(offset / c.eleSize) 103 | if idx < 0 || idx > c.len-1 { 104 | return -1 105 | } 106 | return idx 107 | } 108 | 109 | func NewUnorderedCollectionIterator[T ComponentObject](collection *UnorderedCollection[T], readOnly ...bool) Iterator[T] { 110 | iter := &Iter[T]{ 111 | data: collection.data, 112 | len: collection.Len(), 113 | eleSize: collection.eleSize, 114 | offset: 0, 115 | } 116 | if len(readOnly) > 0 { 117 | iter.readOnly = readOnly[0] 118 | } 119 | if iter.len != 0 { 120 | iter.head = unsafe.Pointer(&collection.data[0]) 121 | if iter.readOnly { 122 | iter.curTemp = collection.data[0] 123 | iter.cur = &iter.curTemp 124 | } else { 125 | iter.cur = &(collection.data[0]) 126 | } 127 | } 128 | 129 | return iter 130 | } 131 | -------------------------------------------------------------------------------- /unordered_collection_test.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import ( 4 | "testing" 5 | "unsafe" 6 | ) 7 | 8 | type __unorderedCollection_Test_item struct { 9 | Component[__unorderedCollection_Test_item] 10 | ItemID int64 11 | Arr [3]int 12 | } 13 | 14 | func TestUnorderedCollection_Iterator(t *testing.T) { 15 | //准备数据 16 | caseCount := 50 17 | var srcList []__unorderedCollection_Test_item 18 | for i := 0; i < caseCount; i++ { 19 | srcList = append(srcList, __unorderedCollection_Test_item{ 20 | ItemID: int64(i), 21 | Arr: [3]int{1, 2, 3}, 22 | }) 23 | } 24 | 25 | //创建容器(无序数据集) 26 | c := NewUnorderedCollection[__unorderedCollection_Test_item]() 27 | 28 | //添加数据 29 | for i := 0; i < caseCount; i++ { 30 | _, _ = c.Add(&srcList[i]) 31 | } 32 | 33 | //遍历风格 1: 34 | for iter := NewUnorderedCollectionIterator(c); !iter.End(); iter.Next() { 35 | v := iter.Val() 36 | _ = v 37 | //t.Logf("style 1: %+v\n", v) 38 | } 39 | 40 | //遍历风格 2: 41 | iter := NewUnorderedCollectionIterator(c) 42 | for c := iter.Begin(); !iter.End(); c = iter.Next() { 43 | _ = c 44 | //t.Logf("style 2: %+v\n", c) 45 | } 46 | } 47 | 48 | func BenchmarkUnorderedCollection_SliceIter(b *testing.B) { 49 | var slice []__unorderedCollection_Test_item 50 | // collection 有ID生成,此处用通常方式模拟 51 | var id2index = map[int]int{} 52 | 53 | var ids []int64 54 | total := 100000 55 | for n := 0; n < total; n++ { 56 | item := &__unorderedCollection_Test_item{ 57 | ItemID: int64(n), 58 | } 59 | slice = append(slice, *item) 60 | id2index[n] = n 61 | ids = append(ids, int64(n)) 62 | } 63 | 64 | b.ResetTimer() 65 | 66 | for n := 0; n < b.N; n++ { 67 | for i := 0; i < 10000; i++ { 68 | _ = slice[i] 69 | } 70 | } 71 | } 72 | 73 | func BenchmarkUnorderedCollection_Write(b *testing.B) { 74 | c := NewUnorderedCollection[__unorderedCollection_Test_item]() 75 | b.ResetTimer() 76 | 77 | for n := 0; n < b.N; n++ { 78 | item := &__unorderedCollection_Test_item{ 79 | ItemID: int64(n), 80 | } 81 | ret, _ := c.Add(item) 82 | _ = ret 83 | } 84 | } 85 | 86 | func BenchmarkUnorderedCollection_SliceRead(b *testing.B) { 87 | total := 100000 88 | c := NewUnorderedCollection[__unorderedCollection_Test_item]() 89 | arr := make([]__unorderedCollection_Test_item, total) 90 | for n := 0; n < total; n++ { 91 | item := __unorderedCollection_Test_item{ 92 | ItemID: int64(n), 93 | } 94 | _, _ = c.Add(&item) 95 | arr = append(arr, item) 96 | } 97 | 98 | fn := func(idx int64) *__unorderedCollection_Test_item { 99 | return nil 100 | } 101 | 102 | b.ResetTimer() 103 | 104 | b.Run("slice", func(b *testing.B) { 105 | for n := 0; n < b.N; n++ { 106 | _ = arr[n%total] 107 | } 108 | }) 109 | b.Run("unordered collection", func(b *testing.B) { 110 | for n := 0; n < b.N; n++ { 111 | _ = c.Get(int64(n % total)) 112 | } 113 | }) 114 | b.Run("unordered collection 2", func(b *testing.B) { 115 | for n := 0; n < b.N; n++ { 116 | _ = (*__unorderedCollection_Test_item)(unsafe.Add(unsafe.Pointer(&c.data[0]), uintptr(n%total)*c.eleSize)) 117 | } 118 | }) 119 | b.Run("unordered collection empty func", func(b *testing.B) { 120 | for n := 0; n < b.N; n++ { 121 | _ = fn(int64(n % total)) 122 | } 123 | }) 124 | } 125 | 126 | func BenchmarkUnorderedCollection_Read(b *testing.B) { 127 | c := NewUnorderedCollection[__unorderedCollection_Test_item]() 128 | var ids []int64 129 | total := 100000 130 | for n := 0; n < total; n++ { 131 | item := &__unorderedCollection_Test_item{ 132 | ItemID: int64(n), 133 | } 134 | _, _ = c.Add(item) 135 | ids = append(ids, int64(n+1)) 136 | } 137 | 138 | b.ResetTimer() 139 | 140 | for n := 0; n < b.N; n++ { 141 | _ = c.Get(int64((n + 1) % total)) 142 | } 143 | } 144 | 145 | func BenchmarkUnorderedCollection_ReadUnsafe(b *testing.B) { 146 | var ids []int64 147 | total := 100000 148 | data := make([]__unorderedCollection_Test_item, total) 149 | for n := 0; n < total; n++ { 150 | item := __unorderedCollection_Test_item{ 151 | ItemID: int64(n), 152 | } 153 | data = append(data, item) 154 | ids = append(ids, int64(n+1)) 155 | } 156 | eleSize := unsafe.Sizeof(__unorderedCollection_Test_item{}) 157 | 158 | b.ResetTimer() 159 | b.Run("direct", func(b *testing.B) { 160 | for n := 0; n < b.N; n++ { 161 | _ = data[int64((n+1)%total)] 162 | } 163 | }) 164 | 165 | b.Run("unsafe", func(b *testing.B) { 166 | for n := 0; n < b.N; n++ { 167 | _ = (*__unorderedCollection_Test_item)(unsafe.Add(unsafe.Pointer(&data[0]), uintptr(int64((n+1)%total))*eleSize)) 168 | } 169 | }) 170 | } 171 | -------------------------------------------------------------------------------- /unordered_collection_with_id.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import "unsafe" 4 | 5 | type ICollectionWithID interface { 6 | Len() int 7 | Clear() 8 | Range(func(v *any) bool) 9 | GetByIndex(idx int64) any 10 | GetByID(id int64) any 11 | } 12 | 13 | type UnorderedCollectionWithID[T any] struct { 14 | UnorderedCollection[T] 15 | ids map[int64]int64 16 | idx2id map[int64]int64 17 | seq int64 18 | } 19 | 20 | func NewUnorderedCollectionWithID[T any](initSize ...int) *UnorderedCollectionWithID[T] { 21 | typ := TypeOf[T]() 22 | eleSize := typ.Size() 23 | size := InitMaxSize / eleSize 24 | if len(initSize) > 0 { 25 | eleSize = uintptr(initSize[0]) / eleSize 26 | } 27 | c := &UnorderedCollectionWithID[T]{ 28 | ids: map[int64]int64{}, 29 | idx2id: map[int64]int64{}, 30 | UnorderedCollection: UnorderedCollection[T]{ 31 | data: make([]T, 0, size), 32 | eleSize: eleSize, 33 | }, 34 | } 35 | return c 36 | } 37 | 38 | func (c *UnorderedCollectionWithID[T]) getID() int64 { 39 | ok := false 40 | for !ok { 41 | c.seq++ 42 | if _, exist := c.ids[c.seq]; !exist { 43 | break 44 | } 45 | } 46 | return c.seq 47 | } 48 | 49 | func (c *UnorderedCollectionWithID[T]) Add(element *T, elementID ...int64) (*T, int64) { 50 | _, idx := c.UnorderedCollection.Add(element) 51 | var id int64 52 | if len(elementID) > 0 { 53 | id = elementID[0] 54 | } else { 55 | id = c.getID() 56 | } 57 | c.ids[id] = idx 58 | c.idx2id[idx] = id 59 | 60 | return &c.data[idx], id 61 | } 62 | 63 | func (c *UnorderedCollectionWithID[T]) remove(id int64) *T { 64 | idx, ok := c.ids[id] 65 | if !ok { 66 | return nil 67 | } 68 | 69 | removed, oldIndex, newIndex := c.UnorderedCollection.Remove(idx) 70 | 71 | lastId := c.idx2id[oldIndex] 72 | c.ids[lastId] = newIndex 73 | c.idx2id[newIndex] = lastId 74 | delete(c.idx2id, oldIndex) 75 | delete(c.ids, id) 76 | 77 | return removed 78 | } 79 | 80 | func (c *UnorderedCollectionWithID[T]) Remove(id int64) { 81 | c.remove(id) 82 | } 83 | 84 | func (c *UnorderedCollectionWithID[T]) RemoveAndReturn(id int64) *T { 85 | cpy := *c.remove(id) 86 | return &cpy 87 | } 88 | 89 | func (c *UnorderedCollectionWithID[T]) getByID(id int64) *T { 90 | idx, ok := c.ids[id] 91 | if !ok { 92 | return nil 93 | } 94 | return c.UnorderedCollection.Get(idx) 95 | } 96 | 97 | func (c *UnorderedCollectionWithID[T]) GetByID(id int64) any { 98 | return c.getByID(id) 99 | } 100 | 101 | func (c *UnorderedCollectionWithID[T]) GetByIndex(idx int64) any { 102 | return c.UnorderedCollection.Get(idx) 103 | } 104 | 105 | func NewUnorderedCollectionWithIDIterator[T ComponentObject](collection *UnorderedCollectionWithID[T], readOnly ...bool) Iterator[T] { 106 | iter := &Iter[T]{ 107 | data: collection.data, 108 | len: collection.Len(), 109 | eleSize: collection.eleSize, 110 | offset: 0, 111 | } 112 | if len(readOnly) > 0 { 113 | iter.readOnly = readOnly[0] 114 | } 115 | if iter.len != 0 { 116 | iter.head = unsafe.Pointer(&collection.data[0]) 117 | if iter.readOnly { 118 | iter.curTemp = collection.data[0] 119 | iter.cur = &iter.curTemp 120 | } else { 121 | iter.cur = &(collection.data[0]) 122 | } 123 | } 124 | 125 | return iter 126 | } 127 | -------------------------------------------------------------------------------- /unordered_collection_with_id_test.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func BenchmarkUnorderedCollectionWithID_Read(b *testing.B) { 8 | c := NewUnorderedCollectionWithID[__unorderedCollection_Test_item]() 9 | var ids []int64 10 | total := 100000 11 | for n := 0; n < total; n++ { 12 | item := &__unorderedCollection_Test_item{ 13 | ItemID: int64(n), 14 | } 15 | _, _ = c.Add(item) 16 | ids = append(ids, int64(n+1)) 17 | } 18 | 19 | b.ResetTimer() 20 | 21 | for n := 0; n < b.N; n++ { 22 | _ = c.GetByID(ids[(n+1)%total]) 23 | } 24 | } 25 | 26 | func BenchmarkUnorderedCollectionWithID_Iter(b *testing.B) { 27 | c := NewUnorderedCollectionWithID[__unorderedCollection_Test_item]() 28 | var ids []int64 29 | total := 100000 30 | for n := 0; n < total; n++ { 31 | item := &__unorderedCollection_Test_item{ 32 | ItemID: int64(n), 33 | } 34 | _, _ = c.Add(item) 35 | ids = append(ids, int64(n)) 36 | } 37 | 38 | iter := NewUnorderedCollectionWithIDIterator(c) 39 | 40 | b.ResetTimer() 41 | 42 | for n := 0; n < b.N; n++ { 43 | for item := iter.Begin(); !iter.End(); item = iter.Next() { 44 | _ = item 45 | } 46 | } 47 | } 48 | 49 | func BenchmarkUnorderedCollectionWithID_SliceWrite(b *testing.B) { 50 | var slice []__unorderedCollection_Test_item 51 | var id2index = map[int]int{} 52 | 53 | for n := 0; n < b.N; n++ { 54 | item := &__unorderedCollection_Test_item{ 55 | ItemID: int64(n), 56 | } 57 | slice = append(slice, *item) 58 | id2index[n] = n 59 | } 60 | } 61 | 62 | func BenchmarkUnorderedCollectionWithID_SliceRead(b *testing.B) { 63 | var slice []__unorderedCollection_Test_item 64 | // collection 有ID生成,此处用通常方式模拟 65 | var id2index = map[int]int{} 66 | 67 | var ids []int64 68 | total := 100000 69 | for n := 0; n < total; n++ { 70 | item := &__unorderedCollection_Test_item{ 71 | ItemID: int64(n), 72 | } 73 | slice = append(slice, *item) 74 | id2index[n] = n 75 | ids = append(ids, int64(n)) 76 | } 77 | 78 | b.ResetTimer() 79 | 80 | for n := 0; n < b.N; n++ { 81 | _ = slice[id2index[n%total]] 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /utility.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import ( 4 | "reflect" 5 | "unsafe" 6 | ) 7 | 8 | type IUtilityGetter interface { 9 | getWorld() IWorld 10 | } 11 | 12 | type UtilityGetter struct { 13 | world *IWorld 14 | } 15 | 16 | func (g UtilityGetter) getWorld() IWorld { 17 | return *g.world 18 | } 19 | 20 | type UtilityObject interface { 21 | __UtilityIdentification() 22 | } 23 | 24 | type UtilityPointer[T UtilityObject] interface { 25 | IUtility 26 | *T 27 | } 28 | 29 | type IUtility interface { 30 | Type() reflect.Type 31 | getPointer() unsafe.Pointer 32 | setSystem(sys ISystem) 33 | setWorld(world IWorld) 34 | } 35 | 36 | type utilityIdentification struct{} 37 | 38 | func (u utilityIdentification) __UtilityIdentification() {} 39 | 40 | type Utility[T UtilityObject] struct { 41 | utilityIdentification 42 | w IWorld 43 | sys ISystem 44 | } 45 | 46 | func (u *Utility[T]) setWorld(w IWorld) { 47 | u.w = w 48 | } 49 | 50 | func (u *Utility[T]) setSystem(sys ISystem) { 51 | u.sys = sys 52 | } 53 | 54 | func (u *Utility[T]) GetSystem() ISystem { 55 | return u.sys 56 | } 57 | 58 | func (u *Utility[T]) getPointer() unsafe.Pointer { 59 | return unsafe.Pointer(u) 60 | } 61 | 62 | func (u *Utility[T]) Type() reflect.Type { 63 | return TypeOf[T]() 64 | } 65 | 66 | func (u *Utility[T]) GetRequirements() map[reflect.Type]IRequirement { 67 | return u.sys.GetRequirements() 68 | } 69 | 70 | func (u *Utility[T]) SystemPause() { 71 | u.sys.pause() 72 | } 73 | 74 | func (u *Utility[T]) SystemResume() { 75 | u.sys.resume() 76 | } 77 | 78 | func (u *Utility[T]) SystemStop() { 79 | u.sys.stop() 80 | } 81 | -------------------------------------------------------------------------------- /utils.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import ( 4 | "errors" 5 | "math/rand" 6 | "reflect" 7 | "runtime/debug" 8 | "sync/atomic" 9 | "time" 10 | "unsafe" 11 | ) 12 | 13 | var Empty struct{} = struct{}{} 14 | 15 | var seq uint32 16 | var timestamp int64 17 | 18 | var DebugTry = true 19 | 20 | func init() { 21 | rand.Seed(time.Now().UnixNano()) 22 | } 23 | 24 | func LocalUniqueID() int64 { 25 | tNow := int64(time.Now().UnixNano()) << 32 26 | tTemp := atomic.LoadInt64(×tamp) 27 | if tTemp != tNow { 28 | atomic.StoreUint32(&seq, 0) 29 | for { 30 | if atomic.CompareAndSwapInt64(×tamp, tTemp, tNow) { 31 | break 32 | } else { 33 | tTemp = atomic.LoadInt64(×tamp) 34 | tNow = int64(time.Now().UnixNano()) << 32 35 | } 36 | } 37 | } 38 | s := atomic.AddUint32(&seq, 1) 39 | return tNow + int64((s<<16)&0xFFFF0000+rand.Uint32()&0x0000FFFF) 40 | } 41 | 42 | func Try(task func(), catch ...func(error)) { 43 | defer (func() { 44 | if DebugTry { 45 | return 46 | } 47 | if r := recover(); r != nil { 48 | var str string 49 | switch r.(type) { 50 | case error: 51 | str = r.(error).Error() 52 | case string: 53 | str = r.(string) 54 | } 55 | err := errors.New(str + "\n" + string(debug.Stack())) 56 | if len(catch) > 0 { 57 | catch[0](err) 58 | } 59 | } 60 | })() 61 | task() 62 | } 63 | 64 | func TryAndReport(task func() error) (err error) { 65 | defer func() { 66 | if DebugTry { 67 | return 68 | } 69 | if r := recover(); r != nil { 70 | switch typ := r.(type) { 71 | case error: 72 | err = r.(error) 73 | case string: 74 | err = errors.New(r.(string)) 75 | default: 76 | _ = typ 77 | } 78 | } 79 | }() 80 | err = task() 81 | return nil 82 | } 83 | 84 | func IsPureValueType(typ reflect.Type) bool { 85 | switch typ.Kind() { 86 | case reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, 87 | reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, 88 | reflect.Float32, reflect.Float64: 89 | return true 90 | case reflect.Array: 91 | return IsPureValueType(typ.Elem()) 92 | case reflect.Struct: 93 | for i := 0; i < typ.NumField(); i++ { 94 | subType := typ.Field(i).Type 95 | if !IsPureValueType(subType) { 96 | return false 97 | } 98 | } 99 | return true 100 | default: 101 | return false 102 | } 103 | } 104 | 105 | func StrHash(str string, groupCount int) int { 106 | total := 0 107 | for i := 0; i < len(str); i++ { 108 | total += int(str[i]) 109 | } 110 | return total % groupCount 111 | } 112 | 113 | func memcmp(a unsafe.Pointer, b unsafe.Pointer, len uintptr) (ret bool) { 114 | for i := uintptr(0); i < len; i++ { 115 | if *(*byte)(unsafe.Add(a, i)) != *(*byte)(unsafe.Add(b, i)) { 116 | ret = false 117 | return 118 | } 119 | } 120 | ret = true 121 | Log.Infof("memory compare: %v, %v, %v, equal:%v", a, b, len, ret) 122 | return 123 | } 124 | -------------------------------------------------------------------------------- /utils_test.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestUniqueID(t *testing.T) { 8 | m := make(map[int64]struct{}) 9 | count := 0 10 | for i := 0; i < 50000000; i++ { 11 | id := LocalUniqueID() 12 | if _, ok := m[id]; ok { 13 | count += 1 14 | println("repeat:", count, id) 15 | } else { 16 | m[id] = struct{}{} 17 | } 18 | } 19 | } 20 | 21 | func BenchmarkUniqueID(b *testing.B) { 22 | for i := 0; i < b.N; i++ { 23 | id := LocalUniqueID() 24 | _ = id 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /world.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import ( 4 | "reflect" 5 | "runtime" 6 | "sync/atomic" 7 | "time" 8 | "unsafe" 9 | ) 10 | 11 | type WorldStatus int 12 | 13 | const ( 14 | WorldStatusInitializing WorldStatus = iota 15 | WorldStatusInitialized 16 | WorldStatusRunning 17 | WorldStatusStop 18 | ) 19 | 20 | type WorldConfig struct { 21 | Debug bool //Debug模式 22 | MetaInfoDebugPrint bool 23 | MainThreadCheck bool 24 | IsMetrics bool 25 | IsMetricsPrint bool 26 | CpuNum int //使用的最大cpu数量 27 | MaxPoolThread uint32 //线程池最大线程数量 28 | MaxPoolJobQueue uint32 //线程池最大任务队列长度 29 | HashCount int //容器桶数量 30 | CollectionVersion int 31 | FrameInterval time.Duration //帧间隔 32 | StopCallback func(world *ecsWorld) 33 | } 34 | 35 | func NewDefaultWorldConfig() *WorldConfig { 36 | return &WorldConfig{ 37 | Debug: true, 38 | MetaInfoDebugPrint: true, 39 | MainThreadCheck: true, 40 | IsMetrics: true, 41 | IsMetricsPrint: false, 42 | CpuNum: runtime.NumCPU(), 43 | MaxPoolThread: uint32(runtime.NumCPU() * 2), 44 | MaxPoolJobQueue: 10, 45 | HashCount: runtime.NumCPU() * 4, 46 | FrameInterval: time.Millisecond * 33, 47 | } 48 | } 49 | 50 | type IWorld interface { 51 | getStatus() WorldStatus 52 | getID() int64 53 | addFreeComponent(component IComponent) 54 | registerSystem(system ISystem) 55 | registerComponent(component IComponent) 56 | getMetrics() *Metrics 57 | getEntityInfo(id Entity) (*EntityInfo, bool) 58 | newEntity() *EntityInfo 59 | deleteEntity(entity Entity) 60 | getComponentMetaInfoByType(typ reflect.Type) *ComponentMetaInfo 61 | optimize(t time.Duration, force bool) 62 | getSystem(sys reflect.Type) (ISystem, bool) 63 | addUtility(utility IUtility) 64 | getUtilityForT(typ reflect.Type) (unsafe.Pointer, bool) 65 | update() 66 | setStatus(status WorldStatus) 67 | addComponent(entity Entity, component IComponent) 68 | deleteComponent(entity Entity, component IComponent) 69 | deleteComponentByIntType(entity Entity, it uint16) 70 | getComponentSet(typ reflect.Type) IComponentSet 71 | getComponentSetByIntType(typ uint16) IComponentSet 72 | getComponentCollection() IComponentCollection 73 | getComponentMeta() *componentMeta 74 | getOrCreateComponentMetaInfo(component IComponent) *ComponentMetaInfo 75 | checkMainThread() 76 | base() *ecsWorld 77 | } 78 | 79 | type ecsWorld struct { 80 | id int64 81 | status WorldStatus 82 | config *WorldConfig 83 | systemFlow *systemFlow 84 | components IComponentCollection 85 | entities *EntitySet 86 | optimizer *optimizer 87 | idGenerator *EntityIDGenerator 88 | componentMeta *componentMeta 89 | utilities map[reflect.Type]IUtility 90 | workPool *Pool 91 | metrics *Metrics 92 | frame uint64 93 | ts time.Time 94 | delta time.Duration 95 | pureUpdateDelta time.Duration 96 | mainThreadID int64 97 | } 98 | 99 | func (w *ecsWorld) init(config *WorldConfig) *ecsWorld { 100 | w.id = LocalUniqueID() 101 | w.systemFlow = nil 102 | w.config = config 103 | w.entities = NewEntityCollection() 104 | w.ts = time.Now() 105 | 106 | if w.config.MaxPoolThread <= 0 { 107 | w.config.MaxPoolThread = uint32(runtime.NumCPU()) 108 | } 109 | 110 | if w.config.MaxPoolJobQueue <= 0 { 111 | w.config.MaxPoolJobQueue = 20 112 | } 113 | 114 | w.workPool = NewPool(config.MaxPoolThread, config.MaxPoolJobQueue) 115 | 116 | w.idGenerator = NewEntityIDGenerator(1024, 10) 117 | 118 | w.componentMeta = NewComponentMeta(w) 119 | w.utilities = make(map[reflect.Type]IUtility) 120 | 121 | w.metrics = NewMetrics(w.config.IsMetrics, w.config.IsMetricsPrint) 122 | 123 | w.components = NewComponentCollection(w, config.HashCount) 124 | w.optimizer = newOptimizer(w) 125 | 126 | if w.config.FrameInterval <= 0 { 127 | w.config.FrameInterval = time.Millisecond * 33 128 | } 129 | 130 | if w.config.HashCount == 0 { 131 | w.config.HashCount = config.CpuNum 132 | } 133 | 134 | sf := newSystemFlow(w) 135 | w.systemFlow = sf 136 | 137 | w.setStatus(WorldStatusInitialized) 138 | 139 | return w 140 | } 141 | 142 | func (w *ecsWorld) base() *ecsWorld { 143 | return w 144 | } 145 | 146 | func (w *ecsWorld) getID() int64 { 147 | return w.id 148 | } 149 | 150 | func (w *ecsWorld) switchMainThread() { 151 | atomic.StoreInt64(&w.mainThreadID, goroutineID()) 152 | } 153 | 154 | func (w *ecsWorld) startup() { 155 | if w.getStatus() != WorldStatusInitialized { 156 | panic("world is not initialized or already running.") 157 | } 158 | 159 | if w.config.MetaInfoDebugPrint || w.config.Debug { 160 | w.systemFlow.SystemInfoPrint() 161 | w.componentMeta.ComponentMetaInfoPrint() 162 | } 163 | 164 | w.switchMainThread() 165 | w.workPool.Start() 166 | w.setStatus(WorldStatusRunning) 167 | } 168 | 169 | func (w *ecsWorld) update() { 170 | if w.config.MetaInfoDebugPrint { 171 | w.checkMainThread() 172 | } 173 | w.switchMainThread() 174 | if w.status != WorldStatusRunning { 175 | panic("world is not running, must startup first.") 176 | } 177 | e := Event{Delta: w.delta, Frame: w.frame} 178 | start := time.Now() 179 | w.systemFlow.run(e) 180 | now := time.Now() 181 | w.delta = now.Sub(w.ts) 182 | w.pureUpdateDelta = now.Sub(start) 183 | w.ts = now 184 | w.frame++ 185 | } 186 | 187 | func (w *ecsWorld) optimize(t time.Duration, force bool) { 188 | w.optimizer.optimize(t, force) 189 | } 190 | 191 | func (w *ecsWorld) stop() { 192 | w.workPool.Release() 193 | } 194 | 195 | func (w *ecsWorld) setStatus(status WorldStatus) { 196 | w.status = status 197 | } 198 | 199 | func (w *ecsWorld) getUtilityGetter() UtilityGetter { 200 | ug := UtilityGetter{} 201 | iw := IWorld(w) 202 | ug.world = &iw 203 | return ug 204 | } 205 | 206 | func (w *ecsWorld) addUtility(utility IUtility) { 207 | w.utilities[utility.Type()] = utility 208 | } 209 | func (w *ecsWorld) getUtilityForT(typ reflect.Type) (unsafe.Pointer, bool) { 210 | u, ok := w.utilities[typ] 211 | return u.getPointer(), ok 212 | } 213 | 214 | func (w *ecsWorld) getStatus() WorldStatus { 215 | return w.status 216 | } 217 | 218 | func (w *ecsWorld) getMetrics() *Metrics { 219 | return w.metrics 220 | } 221 | 222 | func (w *ecsWorld) registerSystem(system ISystem) { 223 | w.checkMainThread() 224 | w.systemFlow.register(system) 225 | } 226 | 227 | func (w *ecsWorld) registerComponent(component IComponent) { 228 | w.checkMainThread() 229 | w.componentMeta.GetOrCreateComponentMetaInfo(component) 230 | } 231 | 232 | func (w *ecsWorld) getSystem(sys reflect.Type) (ISystem, bool) { 233 | s, ok := w.systemFlow.systems[sys] 234 | if ok { 235 | return s.(ISystem), ok 236 | } 237 | return nil, ok 238 | } 239 | 240 | func (w *ecsWorld) addJob(job func(), hashKey ...uint32) { 241 | w.workPool.Add(job, hashKey...) 242 | } 243 | 244 | func (w *ecsWorld) addEntity(info EntityInfo) *EntityInfo { 245 | return w.entities.Add(info) 246 | } 247 | 248 | func (w *ecsWorld) getEntityInfo(entity Entity) (*EntityInfo, bool) { 249 | return w.entities.GetEntityInfo(entity) 250 | } 251 | 252 | func (w *ecsWorld) deleteEntity(entity Entity) { 253 | w.entities.Remove(entity) 254 | } 255 | 256 | func (w *ecsWorld) getComponentSet(typ reflect.Type) IComponentSet { 257 | return w.components.getComponentSet(typ) 258 | } 259 | 260 | func (w *ecsWorld) getComponentSetByIntType(it uint16) IComponentSet { 261 | return w.components.getComponentSetByIntType(it) 262 | } 263 | 264 | func (w *ecsWorld) getComponentMetaInfoByType(typ reflect.Type) *ComponentMetaInfo { 265 | return w.componentMeta.GetComponentMetaInfoByType(typ) 266 | } 267 | 268 | func (w *ecsWorld) getComponentCollection() IComponentCollection { 269 | return w.components 270 | } 271 | 272 | func (w *ecsWorld) getComponentMeta() *componentMeta { 273 | return w.componentMeta 274 | } 275 | 276 | func (w *ecsWorld) getOrCreateComponentMetaInfo(component IComponent) *ComponentMetaInfo { 277 | return w.componentMeta.GetOrCreateComponentMetaInfo(component) 278 | } 279 | 280 | func (w *ecsWorld) newEntity() *EntityInfo { 281 | info := EntityInfo{entity: w.idGenerator.NewID(), compound: NewCompound(4)} 282 | return w.addEntity(info) 283 | } 284 | 285 | func (w *ecsWorld) addComponent(entity Entity, component IComponent) { 286 | typ := component.Type() 287 | if !w.componentMeta.Exist(typ) { 288 | w.componentMeta.CreateComponentMetaInfo(component.Type(), component.getComponentType()) 289 | } 290 | w.components.operate(CollectionOperateAdd, entity, component) 291 | } 292 | 293 | func (w *ecsWorld) deleteComponent(entity Entity, component IComponent) { 294 | w.components.operate(CollectionOperateDelete, entity, component) 295 | } 296 | 297 | func (w *ecsWorld) deleteComponentByIntType(entity Entity, it uint16) { 298 | w.components.deleteOperate(CollectionOperateDelete, entity, it) 299 | } 300 | 301 | func (w *ecsWorld) addFreeComponent(component IComponent) { 302 | switch component.getComponentType() { 303 | case ComponentTypeFree, ComponentTypeFreeDisposable: 304 | default: 305 | Log.Errorf("component not free type, %s", component.Type().String()) 306 | return 307 | } 308 | w.addComponent(0, component) 309 | } 310 | 311 | func (w *ecsWorld) checkMainThread() { 312 | if !w.config.MainThreadCheck { 313 | return 314 | } 315 | if id := atomic.LoadInt64(&w.mainThreadID); id != goroutineID() && id > 0 { 316 | panic("not main thread") 317 | } 318 | } 319 | -------------------------------------------------------------------------------- /world_async.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | ) 7 | 8 | type SyncWrapper struct { 9 | world *IWorld 10 | } 11 | 12 | func (g SyncWrapper) getWorld() IWorld { 13 | return *g.world 14 | } 15 | 16 | func (g SyncWrapper) NewEntity() Entity { 17 | return g.getWorld().newEntity().Entity() 18 | } 19 | 20 | func (g SyncWrapper) DestroyEntity(entity Entity) { 21 | info, ok := (*g.world).getEntityInfo(entity) 22 | if !ok { 23 | return 24 | } 25 | info.Destroy(*g.world) 26 | } 27 | 28 | func (g SyncWrapper) Add(entity Entity, components ...IComponent) { 29 | info, ok := (*g.world).getEntityInfo(entity) 30 | if !ok { 31 | return 32 | } 33 | info.Add(*g.world, components...) 34 | } 35 | 36 | func (g SyncWrapper) Remove(entity Entity, components ...IComponent) { 37 | info, ok := (*g.world).getEntityInfo(entity) 38 | if !ok { 39 | return 40 | } 41 | info.Remove(*g.world, components...) 42 | } 43 | 44 | type syncTask struct { 45 | wait chan struct{} 46 | fn func(wrapper SyncWrapper) error 47 | } 48 | 49 | type AsyncWorld struct { 50 | ecsWorld 51 | lock sync.Mutex 52 | syncQueue []syncTask 53 | wStop chan struct{} 54 | stopHandler func(world *AsyncWorld) 55 | } 56 | 57 | func NewAsyncWorld(config *WorldConfig) *AsyncWorld { 58 | w := &AsyncWorld{ 59 | wStop: make(chan struct{}), 60 | } 61 | w.ecsWorld.init(config) 62 | return w 63 | } 64 | 65 | func (w *AsyncWorld) Startup() { 66 | //main loop 67 | go func() { 68 | w.startup() 69 | 70 | frameInterval := w.config.FrameInterval 71 | w.setStatus(WorldStatusRunning) 72 | Log.Info("start world success") 73 | 74 | for { 75 | select { 76 | case <-w.wStop: 77 | w.setStatus(WorldStatusStop) 78 | if w.stopHandler != nil { 79 | w.stopHandler(w) 80 | } 81 | w.systemFlow.stop() 82 | w.workPool.Release() 83 | return 84 | default: 85 | } 86 | if w != nil { 87 | w.dispatch() 88 | } 89 | w.update() 90 | //world.Info(delta, frameInterval - delta) 91 | if d := frameInterval - w.delta; d > 0 { 92 | time.Sleep(d) 93 | } 94 | } 95 | }() 96 | } 97 | 98 | func (w *AsyncWorld) Stop() { 99 | w.wStop <- struct{}{} 100 | } 101 | 102 | func (w *AsyncWorld) dispatch() { 103 | w.lock.Lock() 104 | defer w.lock.Unlock() 105 | 106 | gaw := SyncWrapper{} 107 | ig := IWorld(w) 108 | gaw.world = &ig 109 | for _, task := range w.syncQueue { 110 | err := TryAndReport(func() error { 111 | return task.fn(gaw) 112 | }) 113 | if err != nil { 114 | Log.Error(err) 115 | } 116 | if task.wait != nil { 117 | task.wait <- struct{}{} 118 | } 119 | } 120 | w.syncQueue = make([]syncTask, 0) 121 | 122 | *gaw.world = nil 123 | gaw.world = nil 124 | } 125 | 126 | func (w *AsyncWorld) Sync(fn func(g SyncWrapper) error) { 127 | w.lock.Lock() 128 | defer w.lock.Unlock() 129 | 130 | w.syncQueue = append(w.syncQueue, syncTask{ 131 | wait: nil, 132 | fn: fn, 133 | }) 134 | } 135 | 136 | func (w *AsyncWorld) Wait(fn func(g SyncWrapper) error) { 137 | w.lock.Lock() 138 | wait := make(chan struct{}) 139 | w.syncQueue = append(w.syncQueue, syncTask{ 140 | wait: wait, 141 | fn: fn, 142 | }) 143 | w.lock.Unlock() 144 | <-wait 145 | } 146 | -------------------------------------------------------------------------------- /world_sync.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import "time" 4 | 5 | type SyncWorld struct { 6 | ecsWorld 7 | } 8 | 9 | func NewSyncWorld(config *WorldConfig) *SyncWorld { 10 | w := &SyncWorld{} 11 | w.ecsWorld.init(config) 12 | return w 13 | } 14 | 15 | func (w *SyncWorld) Startup() { 16 | w.startup() 17 | } 18 | 19 | func (w *SyncWorld) Update() { 20 | w.update() 21 | } 22 | 23 | func (w *SyncWorld) Optimize(t time.Duration, force bool) {} 24 | 25 | func (w *SyncWorld) Stop() { 26 | w.stop() 27 | } 28 | 29 | func (w *SyncWorld) NewEntity() Entity { 30 | return w.newEntity().Entity() 31 | } 32 | 33 | func (w *SyncWorld) DestroyEntity(entity Entity) { 34 | info, ok := w.getEntityInfo(entity) 35 | if !ok { 36 | return 37 | } 38 | info.Destroy(w) 39 | } 40 | 41 | func (w *SyncWorld) Add(entity Entity, components ...IComponent) { 42 | info, ok := w.getEntityInfo(entity) 43 | if !ok { 44 | return 45 | } 46 | info.Add(w, components...) 47 | } 48 | 49 | func (w *SyncWorld) Remove(entity Entity, components ...IComponent) { 50 | info, ok := w.getEntityInfo(entity) 51 | if !ok { 52 | return 53 | } 54 | info.Remove(w, components...) 55 | } 56 | 57 | func (w *SyncWorld) getWorld() IWorld { 58 | return w 59 | } 60 | -------------------------------------------------------------------------------- /world_test.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import ( 4 | "errors" 5 | "sync" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | const ( 11 | __worldTest_Entity_Count int = 3 12 | ) 13 | 14 | type __world_Test_C_1 struct { 15 | Component[__world_Test_C_1] 16 | 17 | Field1 int 18 | Field2 int 19 | } 20 | 21 | type __world_Test_C_2 struct { 22 | Component[__world_Test_C_2] 23 | 24 | Field1 int 25 | Field2 int 26 | } 27 | 28 | type __world_Test_C_3 struct { 29 | Component[__world_Test_C_3] 30 | 31 | Name FixedString[Fixed16] 32 | } 33 | 34 | type __world_Test_S_1 struct { 35 | System[__world_Test_S_1] 36 | } 37 | 38 | func (w *__world_Test_S_1) Init(si SystemInitConstraint) error { 39 | w.SetRequirements(si, &__world_Test_C_1{}, &__world_Test_C_2{}, &__world_Test_C_3{}) 40 | BindUtility[__world_Test_U_Input](si) 41 | return nil 42 | } 43 | 44 | func (w *__world_Test_S_1) Update(event Event) { 45 | Log.Infof("Update: %d", event.Frame) 46 | iter := GetComponentAll[__world_Test_C_1](w) 47 | for c := iter.Begin(); !iter.End(); c = iter.Next() { 48 | c2 := GetRelated[__world_Test_C_2](w, c.owner) 49 | if c2 == nil { 50 | Log.Infof("Component(Owner:%d) has no related component __world_Test_C_2", c.Owner()) 51 | continue 52 | } 53 | for i := 0; i < testOptimizerDummyMaxFor; i++ { 54 | c.Field1 += i 55 | } 56 | 57 | for i := 0; i < testOptimizerDummyMaxFor; i++ { 58 | c2.Field2 += i 59 | } 60 | 61 | c3 := GetRelated[__world_Test_C_3](w, c.owner) 62 | 63 | Log.Infof("Component(Owner:%d) Changed: __world_Test_C_1: %d, __world_Test_C_2: %d, __world_Test_C_3: %s", c.Owner(), c.Field1, c2.Field2, c3.Name.String()) 64 | } 65 | } 66 | 67 | type __world_Test_U_Input struct { 68 | Utility[__world_Test_U_Input] 69 | } 70 | 71 | func (u *__world_Test_U_Input) ChangeName(entity Entity, name string) { 72 | c := GetComponent[__world_Test_C_3](u.GetSystem(), entity) 73 | if c == nil { 74 | return 75 | } 76 | old := c.Name.String() 77 | c.Name.Set(name) 78 | Log.Infof("Name changed, old: %s, new:%s", old, name) 79 | } 80 | 81 | func Test_ecsWorld_World(t *testing.T) { 82 | config := NewDefaultWorldConfig() 83 | 84 | world := NewSyncWorld(config) 85 | 86 | RegisterSystem[__world_Test_S_1](world) 87 | 88 | world.Startup() 89 | 90 | entities := make([]Entity, __worldTest_Entity_Count) 91 | 92 | for i := 0; i < __worldTest_Entity_Count; i++ { 93 | e1 := world.NewEntity() 94 | world.Add(e1, &__world_Test_C_1{}, &__world_Test_C_2{}, &__world_Test_C_3{}) 95 | entities[i] = e1 96 | } 97 | 98 | world.Update() 99 | 100 | u, ok := GetUtility[__world_Test_U_Input](world) 101 | if ok { 102 | u.ChangeName(entities[0], "name0") 103 | } 104 | 105 | world.Update() 106 | world.Stop() 107 | for false { 108 | world.Update() 109 | time.Sleep(time.Second) 110 | } 111 | } 112 | 113 | func Test_ecsWorld_World_launcher(t *testing.T) { 114 | config := NewDefaultWorldConfig() 115 | config.FrameInterval = time.Second 116 | 117 | world := NewAsyncWorld(config) 118 | 119 | RegisterSystem[__world_Test_S_1](world) 120 | 121 | world.Startup() 122 | 123 | wg := &sync.WaitGroup{} 124 | 125 | wg.Add(1) 126 | entities := make([]Entity, __worldTest_Entity_Count) 127 | world.Sync(func(gaw SyncWrapper) error { 128 | for i := 0; i < __worldTest_Entity_Count; i++ { 129 | e1 := gaw.NewEntity() 130 | gaw.Add(e1, &__world_Test_C_1{}, &__world_Test_C_2{}, &__world_Test_C_3{}) 131 | entities[i] = e1 132 | } 133 | wg.Done() 134 | return nil 135 | }) 136 | 137 | wg.Wait() 138 | 139 | wg.Add(1) 140 | world.Sync(func(gaw SyncWrapper) error { 141 | u, ok := GetUtility[__world_Test_U_Input](gaw) 142 | if !ok { 143 | return errors.New("utility not found") 144 | } 145 | u.ChangeName(entities[0], "name1") 146 | gaw.DestroyEntity(entities[1]) 147 | gaw.Remove(entities[2], &__world_Test_C_2{}) 148 | wg.Done() 149 | return nil 150 | }) 151 | 152 | wg.Wait() 153 | 154 | wg.Add(1) 155 | world.Sync(func(gaw SyncWrapper) error { 156 | u, ok := GetUtility[__world_Test_U_Input](gaw) 157 | if !ok { 158 | return errors.New("Utility not found") 159 | } 160 | u.ChangeName(entities[0], "name2") 161 | wg.Done() 162 | return nil 163 | }) 164 | 165 | wg.Wait() 166 | world.Stop() 167 | } 168 | --------------------------------------------------------------------------------