package ecs import ( "slices" "time" "github.com/gdamore/tcell/v2" ) /* ===================== ECSError ===================== */ type ECSError struct { err string } func ecsError(err string) *ECSError { return &ECSError{ err: err, } } func (e *ECSError) Error() string { return e.err } /* ==================================================== */ /* ================== ComponentType =================== */ type ComponentType uint64 func TypeFrom(powerOf2 uint64) (ComponentType, *ECSError) { if powerOf2 > 63 { return 0, ecsError("Failure: Provided number is too high ( component types must be represented by a power of 2, up to 63 )") } t := uint64(0) for i := range powerOf2 { t *= i } return ComponentType(t), nil } /* ==================================================== */ /* ====================== Types ======================= */ type SystemOrder uint8 type EntityId uint64 type ComponentMask uint64 /* ==================================================== */ /* ================== ComponentMask =================== */ func MaskOf(ids ...ComponentType) ComponentMask { mask := uint64(0) for _, id := range ids { mask |= uint64(id) } return ComponentMask(mask) } func (c ComponentMask) CombinedWithMask(mask ComponentMask) ComponentMask { return ComponentMask(uint64(c) | uint64(mask)) } func (c ComponentMask) CombinedWithType(id ComponentType) ComponentMask { return ComponentMask(uint64(c) | uint64(id)) } func (c ComponentMask) Contains(id ComponentType) bool { return (uint64(id) & uint64(c)) == uint64(id) } func (c ComponentMask) ContainsMultiple(ids ComponentMask) bool { return (uint64(ids) & uint64(c)) == uint64(ids) } /* ==================================================== */ /* ===================== System ======================= */ type System interface { Name() string Order() int Input(world *World, e tcell.EventKey) Tick(world *World, deltaTime int64) } /* ==================================================== */ /* =================== Component ====================== */ type Component interface { Type() ComponentType } /* ==================================================== */ /* ================== BasicEntity ===================== */ type BasicEntity struct { id EntityId containedComponents ComponentMask components map[ComponentType]Component } func CreateRandomEntityId() EntityId { return EntityId(time.Now().UnixNano()) } func createEntity(components ...Component) *BasicEntity { ent := &BasicEntity{ id: CreateRandomEntityId(), components: make(map[ComponentType]Component, 0), } for _, c := range components { ent.components[c.Type()] = c } return ent } func (ent *BasicEntity) Id() EntityId { return ent.id } func (ent *BasicEntity) ContainedComponents() ComponentMask { return ComponentMask(ent.containedComponents) } func (ent *BasicEntity) addComponent(c Component) { ent.containedComponents = ent.containedComponents.CombinedWithType(c.Type()) ent.components[c.Type()] = c } func (ent *BasicEntity) AllComponents() []Component { vals := make([]Component, len(ent.components)) for _, v := range ent.components { vals = append(vals, v) } return vals } func (ent *BasicEntity) QueryComponents(componentIds ...ComponentType) (components []Component, err *ECSError) { comps := make([]Component, len(componentIds)) for _, id := range componentIds { comp := ent.components[id] if comp == nil { return nil, ecsError("Failure: Entity does not contain all of requested types") } comps = append(comps, comp) } return comps, nil } func (ent *BasicEntity) ContainsComponents(mask ComponentMask) bool { return ent.containedComponents.ContainsMultiple(mask) } func (ent *BasicEntity) FetchComponent(id ComponentType) (component Component, err *ECSError) { comp := ent.components[id] if comp == nil { return nil, ecsError("Failure: Entity does not contain requested component") } return comp, nil } /* ==================================================== */ /* ==================== World ========================= */ type World struct { registeredComponentTypes ComponentMask registeredComponentNames map[ComponentType]string entities map[EntityId]*BasicEntity components map[ComponentType][]Component systems []System } func CreateWorld() *World { return &World{ entities: make(map[EntityId]*BasicEntity, 0), systems: make([]System, 0), components: make(map[ComponentType][]Component, 0), } } func (w *World) QueryComponents(componentIds ...ComponentType) (components map[ComponentType][]Component, err *ECSError) { if !w.registeredComponentTypes.ContainsMultiple(MaskOf(componentIds...)) { return nil, ecsError("Failure: One of the provided queries component types has not been registered") } comps := make(map[ComponentType][]Component, 0) for _, id := range componentIds { comp := w.components[id] // No components of the requested type exist, that's ok, add an empty slice for that type if comp == nil { comps[id] = make([]Component, 0) continue } comps[id] = comp } return comps, nil } func (w *World) RegisterComponentType(t ComponentType, name string) (err *ECSError) { if w.registeredComponentTypes.Contains(t) { return ecsError("Failure: ComponentType conflict, another component already exists with that type number") } w.registeredComponentTypes = w.registeredComponentTypes.CombinedWithType(t) w.registeredComponentNames[t] = name return nil } func (w *World) FindEntitiesWithComponents(comps ComponentMask) []*BasicEntity { ents := make([]*BasicEntity, 16) for _, v := range w.entities { if v.ContainsComponents(comps) { ents = append(ents, v) } } return ents } func (w *World) AddComponentToEntity(ent *BasicEntity, comp Component) (modifiedEntity *BasicEntity, err *ECSError) { if !w.registeredComponentTypes.Contains(comp.Type()) { return nil, ecsError("Failure: Attempting to add unknown component to an entity.") } if ent.ContainsComponents(ComponentMask(comp.Type())) { return nil, ecsError("Failure: Entity already contains component") } ent.addComponent(comp) if w.components[comp.Type()] == nil { w.components[comp.Type()] = make([]Component, 0) } w.components[comp.Type()] = append(w.components[comp.Type()], comp) return ent, nil } func (w *World) AddSystem(s System) (err *ECSError) { w.systems = append(w.systems, s) slices.SortFunc(w.systems, func(a System, b System) int { return a.Order() - b.Order() }) return nil } func (w *World) CreateEntity(comps ...Component) *BasicEntity { ent := createEntity(comps...) w.entities[ent.Id()] = ent return ent } func (w *World) FindEntityById(id EntityId) (entity *BasicEntity, err *ECSError) { ent := w.entities[id] if ent == nil { return nil, ecsError("Failure: No entity with request id exists") } return ent, nil } func (w *World) RemoveEntity(id EntityId) { delete(w.entities, id) } func (w *World) Tick(dt int64) { for _, s := range w.systems { s.Tick(w, dt) } } func (w *World) Input(e tcell.EventKey) { for _, s := range w.systems { s.Input(w, e) } } /* ==================================================== */