package gohan import ( "fmt" "log" "os" "reflect" "strconv" "strings" "sync" "time" "github.com/hajimehoshi/ebiten/v2" ) var debug int func init() { debugEnv := os.Getenv("GOHAN_DEBUG") debugEnv = strings.TrimSpace(debugEnv) debugEnv = strings.ToLower(debugEnv) i, err := strconv.Atoi(debugEnv) if err == nil { debug = i return } if debugEnv == "t" || debugEnv == "y" || debugEnv == "on" || debugEnv == "yes" || debugEnv == "true" { debug = 1 } } var w = newWorld() // world represents a collection of AllEntities, components and Systems. type world struct { maxEntityID Entity maxComponentID componentID components [][]interface{} // components[Entity][componentID]Component allEntities []Entity modifiedEntities []Entity removedEntities []Entity handledModifiedEntities map[Entity]bool // availableEntities is the set of EntityIDs available because they were // removed from the game. availableEntities []Entity systems []System systemEntities [][]Entity // Slice of entities matching each system. systemNeeds [][]componentID // Slice of ComponentIDs needed by each system. systemUses [][]componentID // Slice of ComponentIDs used by each system. systemComponentIDs [][]componentID // Slice of ComponentIDs needed or used by each system. systemComponentFields [][]reflect.Value // Slice of component struct fields used by each system. systemReceivesUpdate []bool systemReceivesDraw []bool systemUpdatedEntities int systemUpdatedEntitiesV int systemUpdatedEntitiesT time.Time systemDrawnEntities int systemDrawnEntitiesV int systemDrawnEntitiesT time.Time systemComponentNames []string haveSystemComponentName map[string]bool cacheTime time.Duration entityMutex sync.Mutex componentMutex sync.Mutex sync.Mutex } // NewWorld returns a new world. func newWorld() *world { w := &world{ cacheTime: time.Second, handledModifiedEntities: make(map[Entity]bool), haveSystemComponentName: make(map[string]bool), } // Pad slices to match IDs starting with 1. w.components = append(w.components, nil) w.systemComponentNames = append(w.systemComponentNames, "") return w } // AddSystem registers a system to start receiving Update and Draw calls. func AddSystem(system System) { w.Lock() defer w.Unlock() systemIndex := len(w.systems) w.systems = append(w.systems, system) w.systemReceivesUpdate = append(w.systemReceivesUpdate, true) w.systemReceivesDraw = append(w.systemReceivesDraw, true) w.systemEntities = append(w.systemEntities, nil) w.systemComponentFields = append(w.systemComponentFields, nil) w.entityMutex.Lock() defer w.entityMutex.Unlock() w.modifiedEntities = append(w.modifiedEntities, w.allEntities...) sV := reflect.ValueOf(system) sT := reflect.TypeOf(system) if sV.Kind() == reflect.Ptr { sV = sV.Elem() sT = sT.Elem() } if sV.Kind() != reflect.Struct { panic("system must be a struct type") } var usedComponentIDs []componentID var neededComponentIDs []componentID w.systemComponentIDs = append(w.systemComponentIDs, nil) for i := 0; i < sT.NumField(); i++ { field := sV.Field(i) if !field.CanSet() { continue } tag := sT.Field(i).Tag.Get("gohan") if tag == "-" { continue } //log.Println("SET FIELD", systemIndex, field.String(), tag, field.CanSet()) w.systemComponentFields[systemIndex] = append(w.systemComponentFields[systemIndex], field) id := componentIDByName(field.Type().String()) if tag == "?" { usedComponentIDs = append(usedComponentIDs, id) } else { neededComponentIDs = append(neededComponentIDs, id) } w.systemComponentIDs[systemIndex] = append(w.systemComponentIDs[systemIndex], id) } w.systemNeeds = append(w.systemNeeds, neededComponentIDs) w.systemUses = append(w.systemUses, usedComponentIDs) } /* // AddSystemAfter registers a system to start receiving Update and Draw calls // after the specified system (or systems) are called first. func AddSystemAfter(system System, after ...System) { gameSystems = append(gameSystems, system) gameSystemReceivesUpdate = append(gameSystemReceivesUpdate, true) gameSystemReceivesDraw = append(gameSystemReceivesDraw, true) gameSystemEntities = append(gameSystemEntities, nil) attachEntitiesToSystem(system) } */ func (w *world) setSystemComponentFields(e Entity, i int) { //log.Println(len(w.systemComponentFields[i])) //log.Println(w.systemComponentFields[i]) for j, field := range w.systemComponentFields[i] { //log.Println(j, field, field.String()) id := w.systemComponentIDs[i][j] //log.Println("SYSTEM", i, "FIELD", j, "ID", id) if w.components[e][id] == nil { field.Set(reflect.Zero(field.Type())) } else { field.Set(reflect.ValueOf(w.components[e][id])) } } } func (w *world) updateSystem(i int) (int, error) { w.propagateEntityChanges() updated := 0 for _, entity := range w.systemEntities[i] { w.setSystemComponentFields(entity, i) err := w.systems[i].Update(entity) if err != nil { if err == ErrUnregister { // Unregister system from Update events. w.systemReceivesUpdate[i] = false return 0, nil } return 0, fmt.Errorf("failed to update %s for entity %d: %+v", w.systemName(i), entity, err) } updated++ } return updated, nil } func (w *world) _handleRemovedEntities() { for _, entity := range w.removedEntities { // Remove from attached systems. REMOVED: for i := range w.systemEntities { for j, e := range w.systemEntities[i] { if e == entity { w.systemEntities[i] = _removeAt(w.systemEntities[i], j) continue REMOVED } } } } // Mark EntityID as available. w.availableEntities = append(w.availableEntities, w.removedEntities...) w.removedEntities = w.removedEntities[:0] } // _handleModifiedEntities handles changes to entity components by attaching // and detaching modified entities from affected systems. func (w *world) _handleModifiedEntities() { if len(w.modifiedEntities) == 0 { return } for _, entity := range w.modifiedEntities { if w.handledModifiedEntities[entity] { continue } w.handledModifiedEntities[entity] = true for i := range w.systems { systemEntityIndex := -1 for j, systemEntity := range w.systemEntities[i] { if systemEntity == entity { systemEntityIndex = j break } } var skip bool for _, componentID := range w.systemNeeds[i] { c := entity.getComponent(componentID) if c == nil { skip = true break } } if !skip { if systemEntityIndex != -1 { // Already attached. continue } w.systemEntities[i] = append(w.systemEntities[i], entity) if debug > 1 { log.Printf("Attached entity %d to %s.", entity, w.systemName(i)) } } else if systemEntityIndex != -1 { // Detach from system. w.systemEntities[i] = _removeAt(w.systemEntities[i], systemEntityIndex) } } } for k := range w.handledModifiedEntities { delete(w.handledModifiedEntities, k) } w.modifiedEntities = w.modifiedEntities[:0] } func (w *world) propagateEntityChanges() { w.entityMutex.Lock() defer w.entityMutex.Unlock() w._handleRemovedEntities() w._handleModifiedEntities() } // Update updates the game state. func Update() error { w.Lock() defer w.Unlock() var t time.Time if debug != 0 { t = time.Now() } var entitiesUpdated int for i, registered := range w.systemReceivesUpdate { if !registered { continue } updated, err := w.updateSystem(i) if err != nil { return err } entitiesUpdated += updated if debug != 0 { log.Printf("- %s: %d updated in %.2fms.", w.systemName(i), updated, float64(time.Since(t).Microseconds())/1000) } } w.systemUpdatedEntities = entitiesUpdated if debug != 0 { log.Printf("Handled %d entity updates in %.2fms.", entitiesUpdated, float64(time.Since(t).Microseconds())/1000) } return nil } // CurrentUpdates returns the number of System Update calls required to update // the game state. Because entities may be handled by more than one System, // this number may be higher than the number of active entities. func CurrentUpdates() int { if time.Since(w.systemUpdatedEntitiesT) >= w.cacheTime { w.systemUpdatedEntitiesV = w.systemUpdatedEntities w.systemUpdatedEntitiesT = time.Now() } return w.systemUpdatedEntitiesV } func (w *world) drawSystem(i int, screen *ebiten.Image) (int, error) { w.propagateEntityChanges() var drawn int for _, entity := range w.systemEntities[i] { w.setSystemComponentFields(entity, i) err := w.systems[i].Draw(entity, screen) if err != nil { if err == ErrUnregister { // Unregister system from Draw events. w.systemReceivesDraw[i] = false return 0, nil } return 0, fmt.Errorf("failed to draw %s for entity %d: %+v", w.systemName(i), entity, err) } drawn++ } return drawn, nil } // Draw draws the game on to the screen. func Draw(screen *ebiten.Image) error { w.Lock() defer w.Unlock() var t time.Time if debug != 0 { t = time.Now() } var entitiesDrawn int for i, registered := range w.systemReceivesDraw { if !registered { continue } drawn, err := w.drawSystem(i, screen) if err != nil { return err } entitiesDrawn += drawn if debug != 0 { log.Printf("- %s: %d drawn in %.2fms.", w.systemName(i), drawn, float64(time.Since(t).Microseconds())/1000) } } w.systemDrawnEntities = entitiesDrawn if debug != 0 { log.Printf("Handled %d entity draws in %.2fms.", entitiesDrawn, float64(time.Since(t).Microseconds())/1000) } return nil } // CurrentDraws returns the number of System Draw calls required to draw the // game on to the screen. Because entities may be handled by more than one // System, this number may be higher than the number of active entities. func CurrentDraws() int { if time.Since(w.systemDrawnEntitiesT) >= w.cacheTime { w.systemDrawnEntitiesV = w.systemDrawnEntities w.systemDrawnEntitiesT = time.Now() } return w.systemDrawnEntitiesV } // Preallocate creates and then immediately removes the specified number of entities. // Because Gohan reuses removed entities, this has the effect of pre-allocating // the memory used later to create entities normally. Pre-allocating enough // entities to run your application after its systems has been added, but // before any entities are created, will provide the greatest performance boost. func Preallocate(entities int) { if len(w.availableEntities) >= entities { return } e := make([]Entity, entities) for i := 0; i < entities; i++ { e[i] = NewEntity() } for i := 0; i < entities; i++ { e[i].Remove() } } func uniqueComponentIDs(v []componentID) []componentID { var list []componentID keys := make(map[componentID]bool) for _, entry := range v { if _, value := keys[entry]; !value { keys[entry] = true list = append(list, entry) } } return list } func (w *world) componentName(id componentID) string { if int(id) < len(w.systemComponentNames) { return w.systemComponentNames[id] } return strconv.Itoa(int(id)) } func (w *world) systemName(i int) string { if i < len(w.systems) { return getName(w.systems[i]) } return strconv.Itoa(i) } func getName(v interface{}) string { t := reflect.TypeOf(v) if t.Kind() == reflect.Ptr { return strings.Title(t.Elem().Name()) } else if t.Kind() == reflect.Struct { return strings.Title(t.Name()) } return "" } // Reset removes all entities, components and systems. func Reset() { old := w old.Lock() w = newWorld() old.Unlock() }