ecs

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 23, 2025 License: MIT Imports: 6 Imported by: 0

README

go-ecs

This is an archetype-based ECS framework implement in Golang.

Highly inspired by flex.

Performance

The goal is to maximize performance, no reflect everywhere, but also using a little to make life better.

Project Status

We are waiting Golang support "Generic Methods".
See: https://github.com/golang/go/issues/49085

一些个人看法:当前Golang语言的开发谷歌是众所周知的双标,社区提案基本很少通过,内部提的proposal却可以一路绿灯(甚至是那些社区提出过无数遍被否决过的)。 所以我认为Go短期内不可能支持泛型方法,除非某个谷歌内部员工写代码时需要用到这个特性,我们可以拭目以待。😁😁😁

Project Structure

The ecs implement is in /internal/core, exports its api on /api.go. It's because I want to keep private fields private, but allow /reflect package can access them.

Documentation

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func AddComp

func AddComp(w *World, e Entity, c Component)

AddComp adds the Component to Entity as a tag, without underlying content

func DelComp

func DelComp(w *World, e Entity, c Component)

DelComp removes the Component of an Entity. If the Entity doesn't have the Component, nothing will happen.

func DelEntity

func DelEntity(w *World, e Entity)

func GetComp

func GetComp[C any](w *World, e Entity, c Component) (data *C)

GetComp gets the data of a Component of an Entity. If the Entity doesn't have the Component, nil will be returned.

func HasComp

func HasComp(w *World, e Entity, c Component) bool

HasComp reports whether the Entity has the Component.

func SetComp

func SetComp[C any](w *World, e Entity, c Component, data C)

SetComp adds the Component and its content to Entity.

If the Entity already has the Component, the content will be overridden. If the Entity doesn't have the Component, the Component will be added.

This function panics if the type of data doesn't match others of the same Component.

func Type

func Type(w *World, e Entity, nameComp Component) string

Types

type Archetype

type Archetype struct {
	Types

	Comps []Storage
	// contains filtered or unexported fields
}

type ArchetypeEdge

type ArchetypeEdge struct {
	// contains filtered or unexported fields
}

type CachedQuery

type CachedQuery struct {
	// contains filtered or unexported fields
}

CachedQuery is cached filter

func (*CachedQuery) Free

func (q *CachedQuery) Free(w *World)

func (*CachedQuery) Iter

func (q *CachedQuery) Iter(yield func(enitty Entity, data []any) bool)

func (*CachedQuery) Run

func (q *CachedQuery) Run(h func(entities []Entity, data []any))

type Component

type Component Entity

A Component is a type of which instances can be added and removed to entities. Each component can be added only once to an entity.

--flecs.dev

func NewComponent

func NewComponent(w *World) (c Component)

NewComponent creates a new Component in the World. The data type associated with the Component will be bind when the first data is set.

type ComponentMeta

type ComponentMeta struct {
	Component
	// This stores the reflect.Type of *Table[T],
	// which T is the type of component's corresponding data.
	// We need this because, when creating new archetypes,
	// we need to create new Storage for the Components.
	TableType reflect.Type
}

type Entity

type Entity uint64

An Entity is a unique thing in the world, and is represented by a 64-bit id. Entities can be created and deleted. If an entity is deleted, it is no longer considered "alive".

A world can contain up to 4 billion alive entities. Entity identifiers contain a few bits that make it possible to check whether an entity is alive or not.

--flecs.dev

Example (Basic)
type (
	Position struct{ x, y float64 }
	Walking  struct{}
)

w := ecs.NewWorld()

name := ecs.NewComponent(w)
ecs.SetComp(w, ecs.Entity(name), name, "Name")

position := ecs.NewComponent(w)
ecs.SetComp(w, ecs.Entity(position), name, "Position")

walking := ecs.NewComponent(w)
ecs.SetComp(w, ecs.Entity(walking), name, "Walking")

// Create an entity with name Bob
bob := ecs.NewEntity(w)
ecs.SetComp(w, bob, name, "Bob")

// The set operation finds or creates a component, and sets it.
ecs.SetComp(w, bob, position, Position{10, 20})
// The add operation adds a component without setting a value. This is
// useful for tags, or when adding a component with its default value.
ecs.SetComp(w, bob, walking, Walking{})

// Get the value for the Position component
pos := ecs.GetComp[Position](w, bob, position)
fmt.Printf("{%f, %f}\n", pos.x, pos.y)

// Overwrite the value of the Position component
ecs.SetComp(w, bob, position, Position{20, 30})

// Create another named entity
alice := ecs.NewEntity(w)
ecs.SetComp(w, alice, name, "Alice")
ecs.SetComp(w, alice, position, Position{10, 20})
ecs.SetComp(w, alice, walking, Walking{})

// Print all the Components the entity has. This will output:
//    Position, Walking, (Identifier,Name)
fmt.Printf("[%s]\n", ecs.Type(w, alice, name))
// Iterate all entities with Position
ecs.QueryAll(position).Run(w, func(entities []ecs.Entity, data []any) {
	p := *data[0].(*[]Position)
	for i, e := range entities {
		entityName := ecs.GetComp[string](w, e, name)
		fmt.Printf("%s: {%f, %f}\n", *entityName, p[i].x, p[i].y)
	}
})
// DelComp tag
ecs.DelComp(w, alice, walking)
Output:

{10.000000, 20.000000}
[Name, Position, Walking]
Bob: {20.000000, 30.000000}
Alice: {10.000000, 20.000000}

func NewEntity

func NewEntity(w *World) (e Entity)

NewEntity creates a new Entity in the World, without any Components.

type EntityRecord

type EntityRecord struct {
	AT  *Archetype
	Row int
}

type Filter

type Filter func(*World, *Archetype, *[]int) bool

func QueryAll

func QueryAll(comps ...Component) Filter
Example
w := ecs.NewWorld()

// Create 10 entities.
var entities [10]ecs.Entity
for i := range entities {
	entities[i] = ecs.NewEntity(w)
}

// Create 2 Components.
c1 := ecs.NewComponent(w)
c2 := ecs.NewComponent(w)

// Add Components to entities.
for i, e := range entities[:5] {
	ecs.SetComp(w, e, c1, i)
}
for i, e := range entities[3:7] {
	ecs.SetComp(w, e, c2, i+3)
}

// Current layout:
//
// entity:[0 1 2 3 4 5 6 7 8 9]
// c1:    [0 1 2 3 4          ]
// c2:    [      3 4 5 6      ]
// c1&c2: [      3 4          ]

// CachedQuery all entities which have both c1 and c2.
ecs.QueryAll(c1, c2).Run(w, func(entities []ecs.Entity, data []any) {
	// The type of the data's element is `Table[T]`,
	// which can be converted to `[]T` only after type assertion.
	fmt.Println(*data[0].(*[]int))
})
Output:

[3 4]
Example (Iter)
w := ecs.NewWorld()

// Create 10 entities.
var entities [10]ecs.Entity
for i := range entities {
	entities[i] = ecs.NewEntity(w)
}

// Create 2 Components.
c1 := ecs.NewComponent(w)
c2 := ecs.NewComponent(w)

// Add Components to entities.
for i, e := range entities[:5] {
	ecs.SetComp(w, e, c1, i)
}
for i, e := range entities[3:7] {
	ecs.SetComp(w, e, c2, i+3)
}

// Current layout:
//
// entity:[0 1 2 3 4 5 6 7 8 9]
// c1:    [0 1 2 3 4          ]
// c2:    [      3 4 5 6      ]
// c1&c2: [      3 4          ]

// CachedQuery all entities which have both c1 and c2.
for entity, components := range ecs.QueryAll(c1, c2).Iter(w) {
	// The type of the data's element is `Table[T]`,
	// which can be converted to `[]T` only after type assertion.
	fmt.Println(entity, components)
}
Output:

3 [3 3]
4 [4 4]

func QueryAny

func QueryAny(comps ...Component) Filter
Example
w := ecs.NewWorld()

// Create 10 entities.
var entities [10]ecs.Entity
for i := range entities {
	entities[i] = ecs.NewEntity(w)
}

// Create 2 Components.
c1 := ecs.NewComponent(w)
c2 := ecs.NewComponent(w)

// Add Components to entities.
for i, e := range entities[:5] {
	ecs.SetComp(w, e, c1, int32(i))
}
for i, e := range entities[3:7] {
	ecs.SetComp(w, e, c2, int64(i+3))
}

// Current layout:
//
// entity:[0 1 2 3 4 5 6 7 8 9]
// c1:    [0 1 2 3 4          ]
// c2:    [      3 4 5 6      ]
// c1&c2: [      3 4          ]

// CachedQuery all entities which have c1 or c2.
var results []string
ecs.QueryAny(c1, c2).Run(w, func(entities []ecs.Entity, data []any) {
	// The type of the data's element is `Table[T]`,
	// which can be converted to `[]T` only after type assertion.
	var sb strings.Builder
	fmt.Fprintf(&sb, "%v:", entities)
	if data[0] != nil {
		fmt.Fprintf(&sb, " c1: [%v]", *data[0].(*[]int32))
	}
	if data[1] != nil {
		fmt.Fprintf(&sb, " c2: [%v]", *data[1].(*[]int64))
	}
	results = append(results, sb.String())
})

sort.Strings(results)
fmt.Print(strings.Join(results, "\n"))
Output:

[0 1 2]: c1: [[0 1 2]]
[3 4]: c1: [[3 4]] c2: [[3 4]]
[5 6]: c2: [[5 6]]
Example (Cache_iter)
w := ecs.NewWorld()

// Create 10 entities.
var entities [10]ecs.Entity
for i := range entities {
	entities[i] = ecs.NewEntity(w)
}

// Create 2 Components.
c1 := ecs.NewComponent(w)
c2 := ecs.NewComponent(w)

// Cache query
query := ecs.QueryAny(c1, c2).Cache(w)

// Add Components to entities.
for i, e := range entities[:5] {
	ecs.SetComp(w, e, c1, int32(i))
}
for i, e := range entities[3:7] {
	ecs.SetComp(w, e, c2, int64(i+3))
}

// Current layout:
//
// entity:[0 1 2 3 4 5 6 7 8 9]
// c1:    [0 1 2 3 4          ]
// c2:    [      3 4 5 6      ]
// c1&c2: [      3 4          ]

// CachedQuery all entities which have c1 or c2.
var results []string
for entity, data := range query.Iter {
	// The type of the data's element is `Table[T]`,
	// which can be converted to `[]T` only after type assertion.
	results = append(results, fmt.Sprintf("e%v: [c1: %v c2: %v]", entity, data[0], data[1]))
}

sort.Strings(results)
fmt.Print(strings.Join(results, "\n"))
Output:

e0: [c1: 0 c2: <nil>]
e1: [c1: 1 c2: <nil>]
e2: [c1: 2 c2: <nil>]
e3: [c1: 3 c2: 3]
e4: [c1: 4 c2: 4]
e5: [c1: <nil> c2: 5]
e6: [c1: <nil> c2: 6]
Example (Iter)
w := ecs.NewWorld()

// Create 10 entities.
var entities [10]ecs.Entity
for i := range entities {
	entities[i] = ecs.NewEntity(w)
}

// Create 2 Components.
c1 := ecs.NewComponent(w)
c2 := ecs.NewComponent(w)

// Add Components to entities.
for i, e := range entities[:5] {
	ecs.SetComp(w, e, c1, int32(i))
}
for i, e := range entities[3:7] {
	ecs.SetComp(w, e, c2, int64(i+3))
}

// Current layout:
//
// entity:[0 1 2 3 4 5 6 7 8 9]
// c1:    [0 1 2 3 4          ]
// c2:    [      3 4 5 6      ]
// c1&c2: [      3 4          ]

// CachedQuery all entities which have c1 or c2.
var results []string
for entity, data := range ecs.QueryAny(c1, c2).Iter(w) {
	// The type of the data's element is `Table[T]`,
	// which can be converted to `[]T` only after type assertion.
	results = append(results, fmt.Sprintf("e%v: [c1: %v c2: %v]", entity, data[0], data[1]))
}

sort.Strings(results)
fmt.Print(strings.Join(results, "\n"))
Output:

e0: [c1: 0 c2: <nil>]
e1: [c1: 1 c2: <nil>]
e2: [c1: 2 c2: <nil>]
e3: [c1: 3 c2: 3]
e4: [c1: 4 c2: 4]
e5: [c1: <nil> c2: 5]
e6: [c1: <nil> c2: 6]

func (Filter) Cache

func (f Filter) Cache(w *World) (q *CachedQuery)

func (Filter) Iter

func (f Filter) Iter(w *World) iter.Seq2[Entity, []any]

func (Filter) Run

func (f Filter) Run(w *World, h func(entities []Entity, data []any))

type IDManager

type IDManager struct {
	NextID   uint64
	Freelist []uint64
}

The IDManager is an internal structure which is used to generate/recycle entity IDs.

type Storage

type Storage interface {
	Get(i int) any
	// contains filtered or unexported methods
}

type Table

type Table[C any] []C

func (*Table[C]) Get

func (c *Table[C]) Get(i int) any

type Types

type Types []ComponentMeta

Types is list of Components. It's sorted and able to be hashed. Allowing us to find the archetype by the hash of its type.

type World

type World struct {
	IDManager

	// The default archetype for newly created entities, which contains no Components.
	Zero *Archetype

	// All entities in the World, including Components.
	// Records their archetype's pointer and the index of the Comps belonging to the entity.
	Entities map[Entity]*EntityRecord

	// All archetypes in the World.
	// The key of the map is the hash of the archetype's Types.
	// And the value is the archetype's pointer.
	Archetypes map[uint64]*Archetype

	// This field stores maps for each component.
	// Each map contains a list of archetypes that have the component.
	// And the component's corresponding Storage index in the archetype.
	//
	// We can check if an archetype has a component by looking up the map.
	//
	// For any Component c and archetype a:
	//	col, ok := Components[c][a]
	// If ok == true, then archetype a has component c, otherwise it doesn't.
	// And if col == -1, archetype a has component c but doesn't contain any data,
	// otherwise the col is the index of the component's Storage in the archetype.
	Components map[Component]map[*Archetype]int

	// For high performance, we cache the queries.
	// But these caches will get outdated when new archetypes are created.
	// We register all queries created here, and update them when new archetypes are created.
	Queries Table[*CachedQuery]
	// contains filtered or unexported fields
}

The World is the container for all ECS data. It stores the entities and their Components, does queries and runs systems.

--flecs.dev

func NewWorld

func NewWorld() (w *World)

NewWorld creates a new empty World, with the default Components.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL