ecs

package
v0.0.0-...-c33989e Latest Latest
Warning

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

Go to latest
Published: Jun 26, 2025 License: MIT Imports: 6 Imported by: 0

Documentation

Overview

Package ecs provides ECS (Entity Component System) implementation.

The package provides effiecient way for systems to subscribe to relevant component combinations for entities and run update loops on only those entities.

Typesafety

ECS package provides typesafe GetComponent, AddComponent and RemoveComponent functions with golang generics. These should work with any user provided types as long as they are first registered.

See GetComponent, AddComponent and RemoveComponent examples for more information.

Changes during updates

Additions and removals of components and entities are done lazily. This also provides ecs system more performant way to handle deletions and do cleanup on larger batches.

First one is to provide consistent view of entities' components during update cycle of multiple systems. All RemoveComponent, AddComponent and RemoveEntity calls will have their effect visible only on the next update cycle. Changes to the component values themselves are always immediately visble to the other systems.

Please see RegisterSystem (UpdateBarrier) example.

Index

Examples

Constants

View Source
const (
	Delete oplogKind = iota
	Add
)

Variables

This section is empty.

Functions

func AddComponent

func AddComponent[T any](w *World, e EntityID, c T)

AddComponent adds component of type T to the Entity.

Calling this on non-existent entity or on entity that already has the same component will panic.

Example
package main

import (
	"fmt"

	"github.com/MatiasLyyra/mengine/ecs"
)

type Player struct {
	X, Y int
}

func main() {
	w := ecs.New()
	ecs.RegisterComponent[Player](w)
	player := ecs.NewEntity(w)
	ecs.AddComponent(w, player, Player{X: 200, Y: 400})
	// Note: Init must be called before added components can be queries
	w.Init()
	fmt.Printf("Player: %v\n", ecs.DebugComponent[Player](w, player))
}
Output:
Player: {X:200 Y:400}

func DebugComponent

func DebugComponent[T any](w *World, e EntityID) string

DebugComponent returns debug print of component T on entity.

func GetComponent

func GetComponent[T any](w *World, e EntityID) *T

GetComponent returns component of type T attached to the entity.

If the entity does not have the component, GetComponent will return nil.

Example
package main

import (
	"fmt"

	"github.com/MatiasLyyra/mengine/ecs"
)

type Player struct {
	X, Y int
}

type Inventory struct {
	Food int
}

func main() {
	w := ecs.New()
	ecs.RegisterComponent[Player](w)
	ecs.RegisterComponent[Inventory](w)
	player := ecs.NewEntity(w)
	ecs.AddComponent(w, player, Player{X: 200, Y: 400})
	w.Init()

	playerComponent := ecs.GetComponent[Player](w, player)
	playerComponent.X += 20
	playerComponent.Y /= 2
	fmt.Printf("Player: %v\n", ecs.DebugComponent[Player](w, player))
}
Output:
Player: {X:220 Y:200}

func GetSingleton

func GetSingleton[T any](w *World) *T

GetSingleton returns previously registered singleton value.

Calling this on type T that has not been registered, will panic.

func HasComponent

func HasComponent[T any](w *World, e EntityID) bool

func MustGetComponent

func MustGetComponent[T any](w *World, e EntityID) *T

MustGetComponent will return non-nil component of type T attached to the entity.

Call to this will panic, if the the component does not exist on the entity.

func RegisterComponent

func RegisterComponent[T any](w *World)

RegisterComponent registers new component of type T.

This needs to be called before component can be used.

func RegisterInitSystem

func RegisterInitSystem(w *World, s InitSystem)

RegisterInitSystem register new InitSystem to the ecs world.

func RegisterSingleton

func RegisterSingleton[T any](w *World, singleton *T)

RegisterSingleton registers singleton value to the ecs world.

Calling this multiple times with same T will overwrite the previous value.

func RegisterSystem

func RegisterSystem[T SystemType](w *World, s T, sig Signature)

RegisterSystem registers new Update system the ecs world.

Example
package main

import (
	"fmt"

	"github.com/MatiasLyyra/mengine/ecs"
)

type Player struct {
	X, Y int
}

type Inventory struct {
	Food int
}

type PlayerUpdateSystem struct{}

func (PlayerUpdateSystem) Update(us ecs.UpdateState) {
	for _, e := range us.Entities {
		player := ecs.GetComponent[Player](us.World, e)
		inventory := ecs.GetComponent[Inventory](us.World, e)
		fmt.Printf("Player: %+v\n", player)
		fmt.Printf("Inventory: %+v\n", inventory)
	}
}

func main() {
	w := ecs.New()
	ecs.RegisterComponent[Player](w)
	ecs.RegisterComponent[Inventory](w)
	// player1 entity will not get printed, as it lacks the necessary Inventory component for the system
	player1 := ecs.NewEntity(w)
	ecs.AddComponent(w, player1, Player{X: 200, Y: 400})
	player2 := ecs.NewEntity(w)
	ecs.AddComponent(w, player2, Player{X: 300, Y: 500})
	ecs.AddComponent(w, player2, Inventory{Food: 6})
	ecs.RegisterSystem(w, PlayerUpdateSystem{}, ecs.Sig[Player](w)|ecs.Sig[Inventory](w))
	w.Init()
	w.RunUpdate(0)
}
Output:
Player: &{X:300 Y:500}
Inventory: &{Food:6}
Example (UpdateBarrier)

This demonstrates more in-depth interactions with multiple systems that add and delete components during an update.

In the example, PrintPlayerSystem will only trigger on the second update cycle. This is because component changes are only applied on the next update cycle.

package main

import (
	"fmt"

	"github.com/MatiasLyyra/mengine/ecs"
)

type Player struct {
	X, Y int
}

type Inventory struct {
	Food int
}

type ModifyPlayerSystem struct{}

func (ModifyPlayerSystem) Update(us ecs.UpdateState) {
	for _, e := range us.Entities {
		if !ecs.HasComponent[Inventory](us.World, e) {
			ecs.AddComponent(us.World, e, Inventory{Food: 10})
		}
		fmt.Printf("ModifyPlayerSystem.Update Player: %+v\n", ecs.GetComponent[Player](us.World, e))
		fmt.Printf("ModifyPlayerSystem.Update Inventory: %+v\n", ecs.GetComponent[Inventory](us.World, e))
	}
}

type PrintPlayerSystem struct{}

func (PrintPlayerSystem) Update(us ecs.UpdateState) {
	for _, e := range us.Entities {
		fmt.Printf("PrintPlayerSystem.Update Player: %+v\n", ecs.GetComponent[Player](us.World, e))
		fmt.Printf("PrintPlayerSystem.Update Inventory: %+v\n", ecs.GetComponent[Inventory](us.World, e))
	}
}

func main() {
	w := ecs.New()
	ecs.RegisterComponent[Player](w)
	ecs.RegisterComponent[Inventory](w)
	player1 := ecs.NewEntity(w)
	ecs.AddComponent(w, player1, Player{X: 200, Y: 400})
	ecs.RegisterSystem(w, ModifyPlayerSystem{}, ecs.Sig[Player](w))
	ecs.RegisterSystem(w, PrintPlayerSystem{}, ecs.Sig[Player](w)|ecs.Sig[Inventory](w))
	w.Init()
	// First update cycle
	w.RunUpdate(0)
	// Second update cycle
	w.RunUpdate(0)
}
Output:
ModifyPlayerSystem.Update Player: &{X:200 Y:400}
ModifyPlayerSystem.Update Inventory: <nil>
ModifyPlayerSystem.Update Player: &{X:200 Y:400}
ModifyPlayerSystem.Update Inventory: &{Food:10}
PrintPlayerSystem.Update Player: &{X:200 Y:400}
PrintPlayerSystem.Update Inventory: &{Food:10}

func RemoveComponent

func RemoveComponent[T any](w *World, e EntityID)

RemoveComponent removes component of type T from the Entity.

Calling this on non-existent entity or on entity that does not have the component will panic.

Example
package main

import (
	"fmt"

	"github.com/MatiasLyyra/mengine/ecs"
)

type Player struct {
	X, Y int
}

type Inventory struct {
	Food int
}

func main() {
	w := ecs.New()
	ecs.RegisterComponent[Player](w)
	ecs.RegisterComponent[Inventory](w)
	player := ecs.NewEntity(w)
	ecs.AddComponent(w, player, Player{X: 200, Y: 400})
	ecs.AddComponent(w, player, Inventory{Food: 2})
	w.Init()
	fmt.Printf("Player: %v\n", ecs.DebugComponent[Player](w, player))
	fmt.Printf("Inventory: %v\n", ecs.DebugComponent[Inventory](w, player))
	ecs.RemoveComponent[Inventory](w, player)
	fmt.Printf("Inventory: %v\n", ecs.DebugComponent[Inventory](w, player))
}
Output:
Player: {X:200 Y:400}
Inventory: {Food:2}
Inventory: <nil>

func RemoveEntity

func RemoveEntity(w *World, e EntityID)

RemoveEntity removes the entity and all of its associated components.

func TypeID

func TypeID[T any](v T) uint64

Types

type EntityID

type EntityID uint64

func NewEntity

func NewEntity(w *World) EntityID

NewEntity returns new EntityID handle

type InitSystem

type InitSystem interface {
	Init(*World)
}

type Signature

type Signature uint64

func Sig

func Sig[T any](w *World) Signature

Sig will return Signature associated with component T.

func (Signature) Next

func (s Signature) Next() (Signature, bool)

type SystemType

type SystemType interface {
	Update(UpdateState)
}

type UpdateState

type UpdateState struct {
	World     *World
	Entities  []EntityID
	DeltaTime float32
}

type WindowSettings

type WindowSettings struct {
}

type World

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

func New

func New() *World

func (*World) Init

func (w *World) Init()

Init prepares ecs world and runs all of the init systems.

This should be called after all the initial components have been created, and before the first RunUpdate call.

func (*World) RunUpdate

func (w *World) RunUpdate(dt float32)

RunUpdate runs all of the Update systems and flushes all component additions or removals.

Jump to

Keyboard shortcuts

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