├── bitmask.go ├── ecs.go ├── entity.go └── world.go /bitmask.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | // bitCounter this is very fast func to counts the number of 1 bits in uint32 4 | // instead of doing o(32) we are doing only o(12) 5 | func bitCounter(val uint32) uint32 { 6 | var count uint32 7 | count = val - ((val >> 1) & 033333333333) - ((val >> 2) & 011111111111) 8 | return ((count + (count >> 3)) & 030707070707) % 63 9 | } 10 | 11 | // calcPosition simply counts the number of ones from right to left. 12 | // as an example, `combined` is `uint32` number which being AND with mask. 13 | // mask is a uint32 power of 2 number which tries to count the number of 14 | // one bits in combined varibale. 15 | // so, let's say 16 | // combined = 01101101 17 | // mask = 00001000 18 | // ------------------- 19 | // 3 20 | // this is useful when you have an array of object and you want to find 21 | // a specific one. rather than going through the array every time, you can 22 | // find the index of that specific item in constant time. 23 | func calcBitIndex(all, target uint32) uint32 { 24 | if all&target == 0 { 25 | return 0 26 | } 27 | 28 | var targetAllRightBits uint32 29 | 30 | //this is a trick that only works on power of 2 numbers. 31 | //for example 32 representation is 000100000. now in order to make only the right side 32 | //of one ones is double the target and subtracting by 1. so if you do that, 64 - 1 => 000011111 33 | //now when you all this with rightBits, you will get the current index. 34 | targetAllRightBits = 2*target - 1 35 | targetAllRightBits &= all 36 | 37 | return bitCounter(targetAllRightBits) 38 | } 39 | -------------------------------------------------------------------------------- /ecs.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | // Component represents the abstract version of data. 4 | // each component will associate with a meaningful data. 5 | // because of underneath structure, we can have upto 6 | // 32 components in total per game. 7 | // hope that enough... 8 | type Component interface { 9 | ComponentType() uint32 10 | } 11 | 12 | // Entity is a shell around multiple components. 13 | // components can be added or removed from entity either at 14 | // runtime or compile time. 15 | type Entity interface { 16 | Component(typ uint32) Component 17 | AddComponent(component Component) 18 | RemoveComponent(componentType uint32) 19 | HasComponentTypes(componentTypes uint32) bool 20 | } 21 | 22 | // Query is a bridge between System and Entity. 23 | // query can be used to fetch array of entities 24 | // based on the data that they have or accessing 25 | // an already exist system. 26 | type Query interface { 27 | Entities(componentTypes uint32) []Entity 28 | AddEntity(Entity) 29 | RemoveEntity(Entity) 30 | System(systemType uint32) System 31 | } 32 | 33 | // System contains all the methods which will needed for 34 | // hopfully all the scenarioes. There is an Update method 35 | // that is being called by World and was given a Query object 36 | // for manipulating entities. 37 | type System interface { 38 | SystemType() uint32 39 | Active() bool 40 | Start() 41 | Pause() 42 | Resume() 43 | End() 44 | Update(delta float32, query Query) 45 | } 46 | 47 | // World is a simple abstraction of minimum requirement of 48 | // method which needed for any game. 49 | type World interface { 50 | AddSystem(system System) 51 | RemoveSystem(systemType uint32) 52 | Update(delta float32) 53 | } 54 | -------------------------------------------------------------------------------- /entity.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | type entity32 struct { 4 | components []Component 5 | componentTypes uint32 6 | } 7 | 8 | func (e *entity32) indexOfComponent(componentTyp uint32) int { 9 | //position value is 1, 2, 3, ... 10 | //in order to conver it into index, we need to decrement it by one 11 | position := calcBitIndex(e.componentTypes, componentTyp) 12 | return int(position - 1) 13 | } 14 | 15 | func (e *entity32) Component(componentType uint32) Component { 16 | //make sure the component exists in list 17 | if e.componentTypes&componentType == 0 { 18 | return nil 19 | } 20 | 21 | index := e.indexOfComponent(componentType) 22 | 23 | return e.components[index] 24 | } 25 | 26 | func (e *entity32) AddComponent(component Component) { 27 | //make sure that component always passes as non nil value 28 | if component == nil { 29 | return 30 | } 31 | 32 | componentType := component.ComponentType() 33 | 34 | //component already inside this entity32 35 | if e.componentTypes&componentType != 0 { 36 | return 37 | } 38 | 39 | e.componentTypes = e.componentTypes | componentType 40 | 41 | index := e.indexOfComponent(componentType) 42 | 43 | //insert the new component into right index 44 | e.components = e.components[0 : len(e.components)+1] 45 | copy(e.components[index:], e.components[index+1:]) 46 | e.components[index] = component 47 | } 48 | 49 | func (e *entity32) RemoveComponent(componentType uint32) { 50 | //component doesn't have that component 51 | if e.componentTypes&componentType == 0 { 52 | return 53 | } 54 | 55 | index := e.indexOfComponent(componentType) 56 | 57 | //deleting the component from list 58 | copy(e.components[index:], e.components[index+1:]) 59 | e.components[len(e.components)-1] = nil 60 | e.components = e.components[:len(e.components)-1] 61 | } 62 | 63 | func (e *entity32) HasComponentTypes(componentTypes uint32) bool { 64 | return e.componentTypes&componentTypes != 0 65 | } 66 | 67 | func NewEntity32() Entity { 68 | return &entity32{} 69 | } 70 | -------------------------------------------------------------------------------- /world.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | type world struct { 4 | systemTypes uint32 5 | systems []System 6 | entities []Entity 7 | } 8 | 9 | func (w *world) indexOfSystem(systemType uint32) int { 10 | position := calcBitIndex(w.systemTypes, systemType) 11 | return int(position - 1) 12 | } 13 | 14 | func (w *world) AddSystem(system System) { 15 | //make sure that system always passes as non nil value 16 | if system == nil { 17 | return 18 | } 19 | 20 | systemType := system.SystemType() 21 | 22 | //system already inside this entity32 23 | if w.systemTypes&systemType != 0 { 24 | return 25 | } 26 | 27 | w.systemTypes = w.systemTypes | systemType 28 | 29 | index := w.indexOfSystem(systemType) 30 | 31 | //insert the new system into right index 32 | w.systems = w.systems[0 : len(w.systems)+1] 33 | copy(w.systems[index:], w.systems[index+1:]) 34 | w.systems[index] = system 35 | } 36 | 37 | func (w *world) RemoveSystem(systemType uint32) { 38 | //system doesn't have that system 39 | if w.systemTypes&systemType == 0 { 40 | return 41 | } 42 | 43 | index := w.indexOfSystem(systemType) 44 | 45 | //deleting the system from list 46 | copy(w.systems[index:], w.systems[index+1:]) 47 | w.systems[len(w.systems)-1] = nil 48 | w.systems = w.systems[:len(w.systems)-1] 49 | } 50 | 51 | func (w *world) Update(delta float32) { 52 | for _, system := range w.systems { 53 | if system.Active() { 54 | system.Update(delta, w) 55 | } 56 | } 57 | } 58 | 59 | func (w *world) Entities(componentTypes uint32) []Entity { 60 | var entities []Entity 61 | 62 | for _, entity := range w.entities { 63 | if entity.HasComponentTypes(componentTypes) { 64 | entities = append(entities, entity) 65 | } 66 | } 67 | 68 | return entities 69 | } 70 | 71 | func (w *world) System(systemType uint32) System { 72 | return nil 73 | } 74 | 75 | func (w *world) AddEntity(entity Entity) { 76 | w.entities = append(w.entities, entity) 77 | } 78 | 79 | func (w *world) RemoveEntity(target Entity) { 80 | index := -1 81 | for i, entity := range w.entities { 82 | if entity == target { 83 | index = i 84 | break 85 | } 86 | } 87 | 88 | if index == -1 { 89 | return 90 | } 91 | 92 | copy(w.entities[index:], w.entities[index+1:]) 93 | w.entities[len(w.entities)-1] = nil 94 | w.entities = w.entities[:len(w.entities)-1] 95 | } 96 | 97 | func NewWorld() World { 98 | return &world{} 99 | } 100 | --------------------------------------------------------------------------------