kinshi

package module
v0.0.0-...-5c1ad3d Latest Latest
Warning

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

Go to latest
Published: Mar 17, 2021 License: MIT Imports: 8 Imported by: 0

README

kinshi

Documentation

While working on the architecture of a 7DRL game I noticed there wasn't any small and dynamic ECS implementations in go that fitted my bill. I came up with my own small solution, which I dubbed "kinshin" (禁止, japanese for 'forbidden'). Why forbidden? Because this ECS is build on reflection magic and should probably not be used in any serious projects. You lose a lot of compile time checks that way and it probably isn't great performance wise, but for my use case for a small turn based roguelike that doesn't matter.

Get It

go get github.com/BigJk/kinshi

Example

package main

import (
	"fmt"
	"github.com/BigJk/kinshi"
)

// Components
type Health struct {
	Value int
	Max   int
}

type Pos struct {
	X float64
	Y float64
}

type Velocity struct {
	X float64
	Y float64
}

// Entity
type Unit struct {
	kinshi.BaseEntity // Required for entities
	Velocity
	Health
	Pos
}

func main() {
	ecs := kinshi.New()

	// Insert some entities
	for i := 0; i < 100; i++ {
		_, _ = ecs.AddEntity(&Unit{
			Velocity: Velocity{},
			Health: Health{
				Value: 100,
				Max:   100,
			},
			Pos: Pos{
				X: 5,
				Y: 10,
			},
		})
	}

	// Iterate over all entities that contain a pos and velocity component
	for _, ent := range ecs.Iterate(Pos{}, Velocity{}) {
		// Access the data of the wanted components that the entity contains
		_ = ent.View(func(p *Pos, v *Velocity) {
			fmt.Printf("[ent=%d] x=%.2f y=%.2f vx=%.2f vy=%.2f\n", ent.GetEntity().ID(), p.X, p.Y, v.X, v.Y)

			// p and v point directly to the components that are inside the entity!
		})
	}
  
	// Iterate over all entities that are exactly a unit.
	// Other entities with the same components are NOT contained
	// in this query.
	for _, ent := range ecs.IterateSpecific(Unit{}) {
		// Instead of getting the component data you can also access the entity type directly
		_ = ent.ViewSpecific(func(u *Unit) {
			fmt.Printf("[ent=%d] x=%.2f y=%.2f vx=%.2f vy=%.2f\n", ent.GetEntity().ID(), u.Pos.X, u.Pos.Y, u.Velocity.X, u.Velocity.Y)

			// u points directly to the entity!
		})
	}
}
Dynamic Entity

You can choose to either use "static" entities that have fixed pre-defined components just like in the above example or you can choose to use dynamic entities. You can even mix and match.


type DynamicUnit struct {
	kinshi.BaseDynamicEntity // Required for dynamic entities
	Health                   // Fixed static component
}

func addDynamicEntity(ecs *kinshi.ECS) {
	dynu := DynamicUnit{
		Health: Health{
			Value: 100,
			Max:   100,
		},
	}

	dynu.SetComponent(Pos{
		X: 25,
		Y: 20,
	})

	dynu.SetComponent(Velocity{
		X: 25,
		Y: 20,
	})

	ecs.AddEntity(&dynu)
}
Systems

The way you handle systems is not part of kinshi. A system can be as simple as a function that takes a pointer to a ECS instance, some additional game state and performs some modifications on the entities and game state. The simplest system for adding the velocity to the entities position could look like that:

func SystemMovement(ecs *kinshi.ECS) {
	for _, ent := range ecs.Iterate(Pos{}, Velocity{}) {
		ent.View(func(p *Pos, v *Velocity) {
			p.X += v.X
			p.Y += v.Y
		})
	}
}

Documentation

Index

Constants

View Source
const (
	EntityNone = EntityID(0)
)

Variables

View Source
var (
	ErrNotFound      = errors.New("not found")
	ErrNoID          = errors.New("not id")
	ErrAlreadyExists = errors.New("already exists")
)

Functions

This section is empty.

Types

type BaseDynamicEntity

type BaseDynamicEntity struct {
	BaseEntity
	sync.Mutex
	// contains filtered or unexported fields
}

BaseDynamicEntity is the base implementation of the DynamicEntity interface and should be embedded into your own structs to make it a dynamic entity.

func (*BaseDynamicEntity) GetComponent

func (b *BaseDynamicEntity) GetComponent(t string) (interface{}, error)

GetComponent tries to fetch a component by name.

func (*BaseDynamicEntity) GetComponents

func (b *BaseDynamicEntity) GetComponents() []interface{}

GetComponents returns a slice with all the component instances as interface{}.

func (*BaseDynamicEntity) HasComponent

func (b *BaseDynamicEntity) HasComponent(t interface{}) error

HasComponent checks if the entity has a certain component. If t is a string it will check if a component is present by name. If t is a struct or a pointer to a struct the name of the type will be used to check if the component is present.

func (*BaseDynamicEntity) RemoveComponent

func (b *BaseDynamicEntity) RemoveComponent(c interface{}) error

RemoveComponent removes a component of the type c.

func (*BaseDynamicEntity) SetComponent

func (b *BaseDynamicEntity) SetComponent(c interface{}) error

SetComponents sets or adds a component with the data of c.

type BaseEntity

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

BaseEntity is the base implementation of the Entity interface and should be embedded into your own structs to make it a entity.

func (*BaseEntity) ID

func (b *BaseEntity) ID() EntityID

ID returns the assigned id of the entity

func (*BaseEntity) SetID

func (b *BaseEntity) SetID(id EntityID)

SetID sets the id of the entity. This should not be used by a user as it is managed by the ECS.

type DynamicEntity

type DynamicEntity interface {
	Entity
	SetComponent(interface{}) error
	RemoveComponent(interface{}) error
	GetComponent(string) (interface{}, error)
	HasComponent(interface{}) error
	GetComponents() []interface{}
}

DynamicEntity is a special entity with the option to dynamically add and remove components.

type ECS

type ECS struct {
	sync.RWMutex
	// contains filtered or unexported fields
}

func New

func New() *ECS

New creates a new instance of a ECS

func (*ECS) Access

func (ecs *ECS) Access(ent Entity) *EntityWrap

Access creates a EntityWrap for a given Entity so that the data of the Entity can be accessed in a convenient way.

func (*ECS) AddEntity

func (ecs *ECS) AddEntity(ent Entity) (EntityID, error)

AddEntity adds a Entity to the ECS storage and returns the assigned EntityID.

func (*ECS) Get

func (ecs *ECS) Get(id EntityID) (*EntityWrap, error)

Get fetches a Entity by id.

func (*ECS) Iterate

func (ecs *ECS) Iterate(types ...interface{}) EntityIterator

Iterate searches for entities that contain all the given types and returns a iterator that can be range'd over.

For example you want to get fetch all entities containing a Pos{} and Velocity{} component:

for _, ew := range ecs.Iterate(Pos{}, Velocity{}) {
    // Work with the EntityWrap
}

func (*ECS) IterateID

func (ecs *ECS) IterateID(ids ...EntityID) EntityIterator

IterateID returns a iterator that can be range'd over for the given Entity ids.

func (*ECS) IterateSpecific

func (ecs *ECS) IterateSpecific(t interface{}) EntityIterator

IterateSpecific searches for entities of a named type and returns a iterator that can be range'd over.

For example you want to get fetch all entities that are of the Player Entity type:

for _, ew := range ecs.IterateSpecific(Player{}) {
    // Work with the EntityWrap
}

func (*ECS) Marshal

func (ecs *ECS) Marshal(writer io.Writer) error

Marshal encodes all entities into JSON.

func (*ECS) MustGet

func (ecs *ECS) MustGet(id EntityID) *EntityWrap

MustGet fetches a Entity by id but won't return a error if not found.

func (*ECS) RegisterComponent

func (ecs *ECS) RegisterComponent(c interface{})

RegisterComponent caches information about components this is needed if you want to serialize dynamic entities as the reflection information needs to be available before the unmarshal.

func (*ECS) RegisterEntity

func (ecs *ECS) RegisterEntity(ent Entity)

RegisterEntity caches information about a entity.

func (*ECS) RemoveEntity

func (ecs *ECS) RemoveEntity(ent Entity) error

RemoveEntity removes a Entity from the ECS storage.

func (*ECS) SetRoutineCount

func (ecs *ECS) SetRoutineCount(n int)

SetRoutineCount sets the number of go routines that are allowed to spawn to parallelize searches over the entities.

func (*ECS) Unmarshal

func (ecs *ECS) Unmarshal(reader io.Reader) error

Unmarshal reads a JSON encoded ECS snapshot and loads all the entities from it. The inner storage will be overwritten so all entities that have been added before will be deleted.

Important: If you want to serialize dynamic entities you need to register all possible components with RegisterComponent() before!

type Entity

type Entity interface {
	ID() EntityID
	SetID(EntityID)
}

Entity represents the basic form of a entity.

type EntityID

type EntityID uint64

type EntityIterator

type EntityIterator []*EntityWrap

func (EntityIterator) Count

func (it EntityIterator) Count() int

Count returns the number of found entities.

type EntityWrap

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

EntityWrap is a wrapper for Entity that provides functions to get a view into the Entity components.

func (*EntityWrap) GetEntity

func (ew *EntityWrap) GetEntity() Entity

GetEntity returns the wrapped Entity.

func (*EntityWrap) Valid

func (ew *EntityWrap) Valid() bool

Valid checks if the wrapped Entity is valid (and present).

func (*EntityWrap) View

func (ew *EntityWrap) View(fn interface{}) error

View calls fn with pointer to requested components. If you change any data it will directly modify the Entity data. The pointer that the fn functions is called with are pointing straight to the components.

For example you want to get a view on the Pos{} and Velocity{} struct:

ew.View(func(p *Pos, v *Velocity) {
	p.X += v.X
	p.Y += v.Y
})

func (*EntityWrap) ViewSpecific

func (ew *EntityWrap) ViewSpecific(fn interface{}) error

ViewSpecific calls fn with pointer to the specific requested struct. Its like fetching a named Entity. Changes to the struct data directly applies to the Entity.

For example you want to get a view on the Player{} Entity struct:

ew.ViewSpecific(func(p *Player) {
    fmt.Println(p.Name)
})

Jump to

Keyboard shortcuts

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