2024-05-16 00:06:13 +03:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
/* ==================================================== */
|
|
|
|
|
2024-05-16 00:36:41 +03:00
|
|
|
/* ================== ComponentType =================== */
|
2024-05-16 00:06:13 +03:00
|
|
|
|
|
|
|
type ComponentType uint64
|
|
|
|
|
2024-05-16 00:36:41 +03:00
|
|
|
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 ======================= */
|
|
|
|
|
2024-05-16 00:06:13 +03:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2024-05-16 00:36:41 +03:00
|
|
|
func (ent *BasicEntity) addComponent(c Component) {
|
|
|
|
ent.containedComponents = ent.containedComponents.CombinedWithType(c.Type())
|
2024-05-16 00:06:13 +03:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2024-05-16 00:36:41 +03:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2024-05-16 00:06:13 +03:00
|
|
|
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")
|
|
|
|
}
|
|
|
|
|
2024-05-16 00:36:41 +03:00
|
|
|
ent.addComponent(comp)
|
2024-05-16 00:06:13 +03:00
|
|
|
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* ==================================================== */
|