2025-06-25 20:14:07 +03:00
|
|
|
package ecs
|
|
|
|
|
|
|
|
import (
|
2025-06-26 23:48:54 +03:00
|
|
|
"iter"
|
|
|
|
"maps"
|
2025-06-25 20:14:07 +03:00
|
|
|
"slices"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"code.haedhutner.dev/mvv/LastMUD/internal/logging"
|
2025-06-26 23:48:54 +03:00
|
|
|
"code.haedhutner.dev/mvv/LastMUD/internal/util"
|
2025-06-25 20:14:07 +03:00
|
|
|
"github.com/google/uuid"
|
|
|
|
)
|
|
|
|
|
|
|
|
type Entity uuid.UUID
|
|
|
|
|
|
|
|
func CreateEntity(uuid uuid.UUID) Entity {
|
|
|
|
return Entity(uuid)
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewEntity() Entity {
|
|
|
|
return Entity(uuid.New())
|
|
|
|
}
|
|
|
|
|
|
|
|
func NilEntity() Entity {
|
|
|
|
return Entity(uuid.Nil)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e Entity) AsUUID() uuid.UUID {
|
|
|
|
return uuid.UUID(e)
|
|
|
|
}
|
|
|
|
|
|
|
|
type ComponentType int16
|
|
|
|
|
|
|
|
type Resource string
|
|
|
|
|
|
|
|
type Component interface {
|
|
|
|
Type() ComponentType
|
|
|
|
}
|
|
|
|
|
|
|
|
type ComponentStorage[T Component] struct {
|
|
|
|
forType ComponentType
|
|
|
|
storage map[Entity]T
|
|
|
|
}
|
|
|
|
|
|
|
|
func CreateComponentStorage[T Component](forType ComponentType) *ComponentStorage[T] {
|
|
|
|
return &ComponentStorage[T]{
|
|
|
|
forType: forType,
|
|
|
|
storage: map[Entity]T{},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (cs *ComponentStorage[T]) ComponentType() ComponentType {
|
|
|
|
return cs.forType
|
|
|
|
}
|
|
|
|
|
2025-06-26 23:48:54 +03:00
|
|
|
func (cs *ComponentStorage[T]) Entities() iter.Seq[Entity] {
|
|
|
|
return maps.Keys(cs.storage)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (cs *ComponentStorage[T]) Query(query func(comp T) bool) iter.Seq[Entity] {
|
|
|
|
return func(yield func(Entity) bool) {
|
|
|
|
for k, v := range cs.storage {
|
|
|
|
if !query(v) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if !yield(k) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-06-25 20:14:07 +03:00
|
|
|
func (cs *ComponentStorage[T]) Set(e Entity, component T) {
|
|
|
|
cs.storage[e] = component
|
|
|
|
}
|
|
|
|
|
|
|
|
func (cs *ComponentStorage[T]) Get(e Entity) (component T, ok bool) {
|
|
|
|
component, ok = cs.storage[e]
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (cs *ComponentStorage[T]) Delete(e Entity) {
|
|
|
|
delete(cs.storage, e)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (cs *ComponentStorage[T]) All() map[Entity]T {
|
|
|
|
return cs.storage
|
|
|
|
}
|
|
|
|
|
|
|
|
type System struct {
|
|
|
|
name string
|
|
|
|
priority int
|
|
|
|
work func(world *World, delta time.Duration) (err error)
|
|
|
|
}
|
|
|
|
|
|
|
|
func CreateSystem(name string, priority int, work func(world *World, delta time.Duration) (err error)) *System {
|
|
|
|
return &System{
|
|
|
|
name: name,
|
|
|
|
priority: priority,
|
|
|
|
work: work,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *System) Priority() int {
|
|
|
|
return s.priority
|
|
|
|
}
|
|
|
|
|
2025-06-26 23:48:54 +03:00
|
|
|
func (s *System) Execute(world *World, delta time.Duration) {
|
2025-06-25 20:14:07 +03:00
|
|
|
err := s.work(world, delta)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
logging.Error("Error in system '", s.name, "': ", err.Error())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type World struct {
|
2025-06-26 23:48:54 +03:00
|
|
|
systems []*System
|
|
|
|
componentsByType map[ComponentType]any
|
|
|
|
resources map[Resource]any
|
2025-06-25 20:14:07 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
func CreateWorld() (world *World) {
|
|
|
|
world = &World{
|
2025-06-26 23:48:54 +03:00
|
|
|
systems: []*System{},
|
|
|
|
componentsByType: map[ComponentType]any{},
|
|
|
|
resources: map[Resource]any{},
|
2025-06-25 20:14:07 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (w *World) Tick(delta time.Duration) {
|
|
|
|
for _, s := range w.systems {
|
2025-06-26 23:48:54 +03:00
|
|
|
s.Execute(w, delta)
|
2025-06-25 20:14:07 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func DeleteEntity(world *World, entity Entity) {
|
|
|
|
for _, s := range world.componentsByType {
|
2025-06-26 23:48:54 +03:00
|
|
|
storage := s.(*ComponentStorage[Component])
|
2025-06-25 20:14:07 +03:00
|
|
|
|
2025-06-26 23:48:54 +03:00
|
|
|
storage.Delete(entity)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func DeleteEntities(world *World, entities ...Entity) {
|
|
|
|
for _, e := range entities {
|
|
|
|
DeleteEntity(world, e)
|
2025-06-25 20:14:07 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func SetResource(world *World, r Resource, val any) {
|
|
|
|
world.resources[r] = val
|
|
|
|
}
|
|
|
|
|
|
|
|
func GetResource[T any](world *World, r Resource) (res T, err error) {
|
|
|
|
val, ok := world.resources[r]
|
|
|
|
|
|
|
|
if !ok {
|
|
|
|
err = newECSError("Resource '", r, "' not found.")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
res, ok = val.(T)
|
|
|
|
|
|
|
|
if !ok {
|
|
|
|
err = newECSError("Incompatible type for resource '", r, "'")
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func RemoveResource(world *World, r Resource) {
|
|
|
|
delete(world.resources, r)
|
|
|
|
}
|
|
|
|
|
2025-06-26 23:48:54 +03:00
|
|
|
func registerComponent[T Component](world *World, compType ComponentType) {
|
|
|
|
if _, ok := world.componentsByType[compType]; ok {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2025-06-25 20:14:07 +03:00
|
|
|
world.componentsByType[compType] = CreateComponentStorage[T](compType)
|
|
|
|
}
|
|
|
|
|
|
|
|
func SetComponent[T Component](world *World, entity Entity, component T) {
|
2025-06-26 23:48:54 +03:00
|
|
|
registerComponent[T](world, component.Type())
|
2025-06-25 20:14:07 +03:00
|
|
|
|
2025-06-26 23:48:54 +03:00
|
|
|
compStorage := world.componentsByType[component.Type()].(*ComponentStorage[T])
|
2025-06-25 20:14:07 +03:00
|
|
|
|
2025-06-26 23:48:54 +03:00
|
|
|
compStorage.Set(entity, component)
|
2025-06-25 20:14:07 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
func GetComponent[T Component](world *World, entity Entity) (component T, exists bool) {
|
|
|
|
storage := GetComponentStorage[T](world)
|
|
|
|
|
|
|
|
return storage.Get(entity)
|
|
|
|
}
|
|
|
|
|
|
|
|
func DeleteComponent[T Component](world *World, entity Entity) {
|
|
|
|
storage := GetComponentStorage[T](world)
|
|
|
|
|
|
|
|
storage.Delete(entity)
|
|
|
|
}
|
|
|
|
|
|
|
|
func GetComponentStorage[T Component](world *World) (compStorage *ComponentStorage[T]) {
|
|
|
|
var zero T
|
|
|
|
|
2025-06-26 23:48:54 +03:00
|
|
|
compType := zero.Type()
|
|
|
|
|
|
|
|
registerComponent[T](world, compType)
|
|
|
|
|
|
|
|
return world.componentsByType[compType].(*ComponentStorage[T])
|
|
|
|
}
|
|
|
|
|
|
|
|
func IterateEntitiesWithComponent[T Component](world *World) iter.Seq[Entity] {
|
|
|
|
storage := GetComponentStorage[T](world)
|
|
|
|
|
|
|
|
return storage.Entities()
|
|
|
|
}
|
|
|
|
|
|
|
|
func QueryEntitiesWithComponent[T Component](world *World, query func(comp T) bool) iter.Seq[Entity] {
|
|
|
|
storage := GetComponentStorage[T](world)
|
|
|
|
|
|
|
|
return storage.Query(query)
|
|
|
|
}
|
|
|
|
|
|
|
|
func FindEntitiesWithComponents(world *World, componentTypes ...ComponentType) (entities []Entity) {
|
|
|
|
entities = []Entity{}
|
|
|
|
|
|
|
|
isFirst := true
|
|
|
|
|
|
|
|
for _, compType := range componentTypes {
|
|
|
|
// If we've gone through at least one component, and we have an empty result already, return it
|
|
|
|
if !isFirst && len(entities) == 0 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
storage, ok := world.componentsByType[compType].(*ComponentStorage[Component])
|
|
|
|
|
|
|
|
// If we can't find the storage for this component, then it hasn't been used yet.
|
|
|
|
// Therefore, no entity could have all components requested. Return empty.
|
|
|
|
if !ok {
|
|
|
|
return []Entity{}
|
|
|
|
}
|
|
|
|
|
|
|
|
// For the first component, simply add all entities to the array
|
|
|
|
if isFirst {
|
|
|
|
for entity := range storage.Entities() {
|
|
|
|
entities = append(entities, entity)
|
|
|
|
}
|
|
|
|
|
|
|
|
isFirst = false
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// For later components, intersect
|
|
|
|
entities = util.IntersectSliceWithIterator(entities, storage.Entities())
|
|
|
|
}
|
|
|
|
|
|
|
|
return entities
|
2025-06-25 20:14:07 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
func RegisterSystem(world *World, s *System) {
|
|
|
|
world.systems = append(world.systems, s)
|
|
|
|
slices.SortFunc(
|
|
|
|
world.systems,
|
|
|
|
func(a, b *System) int {
|
|
|
|
return a.priority - b.priority
|
|
|
|
},
|
|
|
|
)
|
|
|
|
}
|
2025-06-26 23:48:54 +03:00
|
|
|
|
|
|
|
func RegisterSystems(world *World, systems ...*System) {
|
|
|
|
for _, s := range systems {
|
|
|
|
RegisterSystem(world, s)
|
|
|
|
}
|
|
|
|
}
|