ecs

package module
v0.0.0-...-a3ab96b Latest Latest
Warning

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

Go to latest
Published: Mar 7, 2021 License: MIT Imports: 2 Imported by: 2

README

LecsGO - Simple Entity Component System framework powered by Golang

Framework name is consonant with phrase "Let's GO", but with "ecs" acronym for "EntityComponentSystem".

Important! Don't forget to use DEBUG (default) builds for development and RELEASE (with -tags RELEASE) builds in production: all internal sanitize checks works only in DEBUG builds and eleminated for performance reasons in RELEASE.

Important! Ecs core is not goroutine-friendly and will never be! If you need multithread-processing - you should implement it on your side as part of ecs-system.

Important! In development stage, not recommended for production use!

Socials

discord Go Report Card

Installation

go get github.com/leopotam/go-ecs

Main parts of ecs

Component

Container for user data without / with small logic inside:

type WeaponComponent struct {
    Ammo int
    GunName string
}

Important! Don't forget to manually init all fields for each new component - they will be reset to default values on recycling to pool.

Entity

Сontainer for components. Implemented as ecs.Entity for addressing internal data. You can add / remove / request components on entity through world api:

// Creates new entity in world context.
entity := world.NewEntity ()

// GetXX returns exist component on entity or nil.
var c1 Component1
var c2 Component2
c1 = world.GetComponent1 (entity) // nil.
c2 = world.GetComponent1 (entity) // nil.

// SetXX returns exist component on entity or creates new one.
c1 = world.SetComponent1 (entity) // not nil.
c2 = world.SetComponent1 (entity) // not nil.

// DelXX removes component from entity if exist.
// If it was last component - entity will be destroyed automatically.
world.DelComponent2 (entity)

// Destroy() removes all components on entity and destroy it.
entity.Destroy ();

Important! Entities can't be alive without components, you will get panic in DEBUG build if create entity and forget to add any component on it.

System

Сontainer for logic for processing filtered entities. System struct should be compatible with ecs.System and one / many iterface types as ecs.PreInitSystem, ecs.InitSystem, ecs.RunSystem, ecs.DestroySystem, ecs.PostDestroySystem, or you will get panic in DEBUG build:

type MySystsem struct {}

// Compatibility with ecs.System interface.
func (s *MySystem) SystemTypes() ecs.SystemType {
	// System should returns bitmask of compatible types.
	return ecs.PreInitSystemType |
		ecs.InitSystemType |
		ecs.RunSystemType |
		ecs.DestroySystemType |
		ecs.PostDestroySystemType
}

func (s *MySystem) PreInit (systems *ecs.Systems) {
	// Will be called once during ecs.Systems.Init() call and before InitSystem.Init().
}

func (s *MySystem) Init (systems *ecs.Systems) {
	// Will be called once during ecs.Systems.Init() call and after PreInitSystem.PreInit().
}

func (s *MySystem) Run (systems *ecs.Systems) {
	// Will be called on each ecs.Systems.Run() call.
}

func (s *MySystem) Destroy (systems *ecs.Systems) {
	// Will be called once during Systems.Destroy() call and before PostDestroySystem.PostDestroy().
}

func (s *MySystem) PostDestroy (systems *ecs.Systems) {
	// Will be called once during Systems.Destroy() call and after DestroySystem.Destroy().
}

Special classes

World

Root level container for all entities / components, works like isolated environment.

Important: You should'nt touch ecs.World directly, but generate your custom world with required components, filters, etc. Check "Api generation" section for this.

Important: Do not forget to call Destroy() method on your world when instance will not be used anymore.

Filter

Container for keeping links to constraints-filtered entities. Any entity will be added to filter or removed from it based on components list and filter constraints. Constrains can be 2 types:

  • Include - "included" component should be attached to entity.
  • Exclude - "excluded" component should not be attached to entity.

Name of filter and component constraints can be declared during "Api generation".

Inside systems you can iterate over filtered entities in this way:

struct Unit struct {
	Health float32
}
// Filter declared in world scheme as "Units(Unit)".
func (s *MySystem) Run(systems *ecs.Systems) {
	world := systems.World("MyWorldName").(*MyWorld)
	for _, entity := world.Units().Entities() {
		unit := world.GetUnit(entity)
		// GetUnitUnsafe(entity) can be used here
		// for performance reason due to Unit 100%
		// present on entity.
		fmt.Printf("user health: %v", unit.Health)
	}
}

Important: If you know that filter entities list will be changed inside loop body (on add or remove component from constraint lists) filter should be locked before loop and unlocked after:

func (s *MySystem) Run(systems *ecs.Systems) {
	world := systems.World("MyWorldName").(*MyWorld)
	for _, entity := world.Units().EntitiesWithLock() {
		unit := world.GetUnit(entity)
		fmt.Printf("user health: %v", unit.Health)
	}
	world.Units.Unlock()
	// locked filter will be updated right after last Unlock() call.
}

Systems

Container for systems, and shared data between them. It's main entry point for registration and execution for systems:

// Create world with reserved space for 100 entities.
world := NewGame1World(128)
// Create logic group for systems.
systems := ecs.NewSystems(nil)
systems.
	// Register system.
	Add(&EnvironmentInitSystem{}).
	Add(&UnitInitSystem{}).
	Add(&UnitMoveSystem{}).
	// etc for other systems.

	// Init all registered systems.
	Init()
// ...
// Update loop.
systems.Run()
// ...
// Destroy all registered systems.
systems.Destroy()
// Destroy all entities.
world.Destroy()

Data sharing

Systems can be used as storage for shared user data and world instances:

// Shared data.
type SharedData struct {
	ClientID int
}

// world name key for keeping inside ecs.Systems.
const MyWorldName = "MyWorld"

// startup init.
shared := SharedData{ClientID: 1234567}
world := NewMyWorld(128)
// Keep SharedData instance inside ecs.Systems instance.
systems := ecs.NewSystems(&shared)
systems.
	// Keep world instance inside ecs.Systems instance.
	.SetWorld(&world)
	Add(&LogInitSystem{}).
	Init()

// System.
func (s *LogInitSystem) Init(systems *ecs.Systems) {
	// Get access to SharedData.
	shared := systems.Shared().(*SharedData)
	fmt.Printf("ClientID: %d", shared.ClientID)
	// Get access to MyWorld instance.
	world := systems.World(MyWorldName).(*MyWorld)
	e := world.NewEntity()
	// ...
}

Api generation

For working with custom components and filters you should create custom world type. It can be done with creating world scheme description:

package game1

import "github.com/leopotam/go-ecs"

// C1 component.
type C1 struct {
	ID int
}

// C2 component.
type C2 struct {
	ID int
}

// C3 component.
type C3 struct {
	ID int
}

// game1WorldInfo is proto-interface for api generation.
// Type can be private - will not be used after generation.
type game1WorldInfo interface {
	// Only one private method should exist and should
	// return all component types that this world will contain.
	components() (
		C1,
		C2,
		C3)

	// Each public method describe filter name (you can name it as you want).
	WithC1(c1 C1)
	// In-parameters works as "Include" components constraint.
	WithC1C2(c1 C1, c2 C2)
	// Out-parameters works as "Exclude" components constraint.
	WithC1WithoutC2(c1 C1) C2
	WithC1WithoutC2C3(c1 C1) (C2, C3)
}

// Game1World is user world type that will be used later, should be public.
type Game1World struct {
    // It should contains private "world" field with type "*ecs.World" - it's important!
    // Meta tag should contains name of proto-interface type in form `ecs:"ProtoInterfaceTypeName"`
	world *ecs.World `ecs:"game1WorldInfo"`
}

// Game1WorldName is optional world name that can be used
// later as key in Systems worlds storage.
const Game1WorldName = "Game1"

// IMPORTANT! Next "go:generate" line should be added at any line of file where world type + world proto-interface placed.

//go:generate go run github.com/leopotam/go-ecs/cmd/world-gen

Then you can run go generate ./... shell command from root of project and api-file will be generated.

License

The software released under the terms of the MIT license.

No support or any guarantees, no personal help.

Documentation

Index

Constants

View Source
const DEBUG = true

DEBUG mode constant.

Variables

This section is empty.

Functions

This section is empty.

Types

type BitSet

type BitSet []chunkType

BitSet is collection of bits.

func NewBitSet

func NewBitSet(cap uint16) BitSet

NewBitSet creates new BitSet instance.

func (*BitSet) Clear

func (s *BitSet) Clear()

Clear sets all bits to 0.

func (*BitSet) Get

func (s *BitSet) Get(i uint16) bool

Get returns true if the given bit is set, false if it is cleared.

func (*BitSet) Set

func (s *BitSet) Set(i uint16)

Set ensures that the given bit is set in the BitSet.

func (*BitSet) Unset

func (s *BitSet) Unset(i uint16)

Unset ensures that the given bit is cleared (not set) in the BitSet.

type ComponentPool

type ComponentPool interface {
	New()
	Recycle(idx uint32)
}

ComponentPool - interface for all user component pools.

type CustomWorld

type CustomWorld interface {
	NewEntity() Entity
	Destroy()
	DelEntity(entity Entity)
	PackEntity(entity Entity) PackedEntity
	UnpackEntity(packedEntity PackedEntity) (Entity, bool)
	InternalWorld() *World
}

CustomWorld - interface for all user worlds.

type DestroySystem

type DestroySystem interface {
	Destroy(systems *Systems)
}

DestroySystem - interface for Destroy() systems.

type Entity

type Entity = uint32

Entity - ID of container with data, cant be cached somehow, use PackedEntity instead!

type EntityData

type EntityData struct {
	Gen     int16
	BitMask BitSet
	Mask    []uint16
}

EntityData - container for keeping internal entity data.

type Filter

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

Filter - container for keeping constraints-filtered entities.

func NewFilter

func NewFilter(include []uint16, exclude []uint16, capacity uint32) *Filter

NewFilter returns new instance of Filter.

func (*Filter) Count

func (f *Filter) Count() uint32

Count returns count of filtered entities.

func (*Filter) Entities

func (f *Filter) Entities() []Entity

Entities returns filtered entities.

func (*Filter) EntitiesWithLock

func (f *Filter) EntitiesWithLock() []Entity

EntitiesWithLock increases counter for protect filter from changes and returns filtered entities.

func (*Filter) Unlock

func (f *Filter) Unlock()

Unlock decreases lock counter and update filter if not locked.

type InitSystem

type InitSystem interface {
	Init(systems *Systems)
}

InitSystem - interface for Init() systems.

type PackedEntity

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

PackedEntity - packed version of Entity, useful for saving as cached data somewhere.

type PostDestroySystem

type PostDestroySystem interface {
	PostDestroy(systems *Systems)
}

PostDestroySystem - interface for PostDestroy() systems.

type PreInitSystem

type PreInitSystem interface {
	PreInit(systems *Systems)
}

PreInitSystem - interface for PreInit() systems.

type RunSystem

type RunSystem interface {
	Run(systems *Systems)
}

RunSystem - interface for Run() systems.

type System

type System interface {
	SystemTypes() SystemType
}

System - base interface for all systems.

type SystemType

type SystemType uint8

SystemType - bit flags for supported types definition.

const (
	// PreInitSystemType declares PreInitSystem support.
	PreInitSystemType SystemType = 1 << iota
	// InitSystemType declares InitSystem support.
	InitSystemType
	// RunSystemType declares RunSystem support.
	RunSystemType
	// DestroySystemType declares DestroySystem support.
	DestroySystemType
	// PostDestroySystemType declares PostDestroySystem support.
	PostDestroySystemType
)

type Systems

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

Systems - container for systems, worlds, shared data.

func NewSystems

func NewSystems(shared interface{}) *Systems

NewSystems returns new instance of Systems.

func (*Systems) Add

func (s *Systems) Add(system System) *Systems

Add registers user system based on SystemType flags. System should implements interface for all requested types.

func (*Systems) Destroy

func (s *Systems) Destroy()

Destroy processes DestroySystem / PostDestroySystem systems execution.

func (*Systems) Init

func (s *Systems) Init()

Init processes PreInitSystem / InitSystem systems execution.

func (*Systems) Run

func (s *Systems) Run()

Run processes RunSystem systems execution.

func (*Systems) SetWorld

func (s *Systems) SetWorld(key string, world CustomWorld) *Systems

SetWorld saves instance of user world to use later inside systems.

func (*Systems) Shared

func (s *Systems) Shared() interface{}

Shared returns optional shared user data.

func (*Systems) World

func (s *Systems) World(key string) CustomWorld

World returns instance of user world saved with SetWorld().

type World

type World struct {
	Pools []ComponentPool

	Entities []EntityData
	// contains filtered or unexported fields
}

World - container for all data.

func NewWorld

func NewWorld(entitiesCount uint32, pools []ComponentPool, filters []Filter) *World

NewWorld returns new instance of World.

func (*World) DelEntity

func (w *World) DelEntity(entity Entity)

DelEntity removes exist entity from world. All attached components will be removed first.

func (*World) Destroy

func (w *World) Destroy()

Destroy processes cleanup of data inside world.

func (*World) Filter

func (w *World) Filter(idx int) *Filter

Filter returns registered filter by index.

func (*World) NewEntity

func (w *World) NewEntity() Entity

NewEntity creates and returns new entity inside world.

func (*World) PackEntity

func (w *World) PackEntity(entity Entity) PackedEntity

PackEntity packs Entity to save outside from world.

func (*World) UnpackEntity

func (w *World) UnpackEntity(packedEntity PackedEntity) (Entity, bool)

UnpackEntity tries to unpack data to Entity, returns unpacked entity and success of operation.

func (*World) UpdateFilters

func (w *World) UpdateFilters(e Entity, componentType uint16, add bool)

UpdateFilters updates all compatible with requested component filters.

Directories

Path Synopsis
cmd

Jump to

Keyboard shortcuts

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