engoengine / ecs Goto Github PK
View Code? Open in Web Editor NEWA Go-implementation of the Entity-Component-System paradigm
License: MIT License
A Go-implementation of the Entity-Component-System paradigm
License: MIT License
Migration of EngoEngine/engo#104
TL;DR: we want to do this:
e := NewEntity([]string{"RenderSystem"}) // Create template entity
// do things to entity
es := e.Duplicate(20) // fill a group with 20 different e's
w.Add(es)
Just taking a walk through the code-base to familiarize myself and I wanted to better understand why the System
interface's Update
method takes a float32
instead of a time.Duration
.
type System interface {
Update(dt float32)
Remove(e BasicEntity)
}
Off-handedly, I don't suppose that being more idiomatic to Go necessarily buys you anything extra but you would probably expect game-developers coming from more classic C/C++ environments to expect a double
which would be a equivalent to float64
(See Game Programming Patterns: Passing Time of which you're probably already familiar, but this is reference for others who might stumble upon this question later along their own game-dev education).
If it was a "just cuz" decision, that's totally valid. I'm really just curious.
I'm going through the "Automatically add entities to systems" section in the readme, and I'm stuck at this part in the example:
type Myable interface {
ecs.BasicFace
AFace
}
I do not have the ecs.BasicFace
interface, and therefore cannot move on to the next step which calls obj.GetBasicEntity()
. Has this been changed and the readme not updated?
Lets get some documentation going here!
Sometimes a system needs to keep track of more than one type of entity at a time. For example, a move system that tracks "Moveable" entities with a Space component, and "MoveAction" entities that contain information on where to move next.
I think it would be advantageous if World.AddSystemInterface
could interpret arrays/slices of interfaces as well as individual ones.
like so:
package main
import (
"github.com/EngoEngine/ecs"
"github.com/EngoEngine/engo/common"
)
type moveAction struct {
*ecs.BasicEntity
target *ecs.BasicEntity
*common.SpaceComponent
}
type moveEntity struct {
*ecs.BasicEntity
*common.SpaceComponent
}
type MoveSystem struct {
actions moveAction
entities moveEntity
}
type MoveActionable interface {
common.BasicFace
common.SpaceFace
Target() *ecs.basicEntity
}
type Moveable interface {
common.BasicFace
common.SpaceFace
}
func main() {
w := new(ecs.World)
m := new(MoveSystem)
var moveable *Moveable
var moveActionable *MoveActionable
w.AddSystemInterface(m, []interface{}{moveable, moveActionable}, nil)
}
As far as I'm aware, this would not be a breaking change.
Let me know what you think!
So, I was told by some people at the slack-chat, that we aren't using idiomatic Go. There may be collisions between the Type() string
methods, and we're wasting performance by doing constant lookups.
They said: why don't you use reflect
? Well, reflect
is horrible so I don't want to use it.
But the idea is simple: get rid of the global []Entity
, which stores every Entity
. Instead, add the user-made entities to the appropriate systems,
Usage would be something like:
type MyEntity struct {
BasicEntity
SpaceComponent
RenderComponent
}
func main() {
// There'd be some kind of global list of systems, which contain every system, so we can loop over them
var systems []BasicSystem
// Each system we create, we add to that global list of systems
RS := &RenderSystem{}
systems = append(systems, RS)
// Then we can define initialize our own custom-made entity struct
e := &MyEntity{}
// Add the appropriate component-references to the RenderSystem - this way you know for sure which components the render-systems requires
RS.Add(&e.BasicEntity, &e.SpaceComponent, &e.RenderComponent)
// And now we loop
for _, sys := range systems {
sys.Update(0.25)
}
}
Upsides;
Component
function takes 91ns per lookup, the ComponentFast
about 18ns, and this approach takes 0ns. For something that might happen thousands / millions of times a second, this is nice.Downsides:
Scene
s? They would have to start using other references.This change is huge, but might also be worth it. I'd love a discussion on this, and maybe a fix for the downside? @paked, @everyone?
Hi,
thanks for all your work, having a lot of fun here using ecs & engo :)
One of the most boring, mistakable, and kind of dirty stuff is the registering process of a system.
Usually, we go to a code like that:
rsys := &common.RenderSystem{}
var r *common.Renderable
var notr *common.NotRenderable
w.AddSystemInterface(rsys, r, notr)
I would like to add a method to World in order to simplify this process (before, just an idea, can PR a complete work):
// didn't check the code, just the idea
type SomeSystem() {}
func(*SomeSystem) Add(basic *ecs.BasicEntity, space *common.SpaceComponent, component *BulletComponent) {}
func(*SomeSystem) Remove(basic ecs.BasicEntity) {}
func(s *SomeSystem) AutoRegister(w *ecs.World) {
var able *SomeSystemAble
var notAble *NotSomeSystemAble
w.AddSystemInterface(s, able, notAble)
}
type Registerable interface {
AutoRegister(w *ecs.World)
}
func (w *ecs.World) AutoRegisterSystem(r Registerable) *ecs.World {
r.AutoRegister(w)
}
func (w *ecs.World) AutoRegisterSystem(r Registerable) *ecs.World {
w.append(w.toBeRegistered, r)
}
world.AutoRegisterSystem(&SomeSystem{})
The idea is to delegate the registration process to the system... this can be done in multiple ways like above. Another interesting way can be done by using directly the method "AutoRegister" on the system but I think it's better to define an interface and delegate the registration to World.
This is what I currently do (with a decorator but the idea is here).
Feedback appreciated
Would be nice to have a test for every possible aspect, to ensure it's stable.
I noticed the exclusion interface only excluded entities if all those components are present on an entity.
The behaviour I assumed was if one or all of the components on the exclude interface are on an entity it will exclude the entity.
Is the current behaviour the expected behaviour?
type NotMovementFace interface {
GetDropComponent() *components.DropComponent
GetTargetComponent() *components.TargetComponent
}
type NotMoveable interface {
ecs.BasicFace
NotMovementFace
}
// Will be excluded
type Enemy struct {
ecs.BasicEntity
SpaceComponent
VelocityComponent
TargetComponent
DropComponent
}
// Wil not be excluded
type Bullet struct {
ecs.BasicEntity
SpaceComponent
VelocityComponent
DropComponent
}
In the example above I would expect any entities with either drop. target or both get excluded?
ecs_tests
, to keep from poluting the ecs package namespace with test only structures.
Its a good idea to have a CONTRIBUTING.md file, (and possibly an authors file as well).
To minimize the public API to the core concepts of ECS, I propose we unexport the Systems type. It may be considered an internal detail of the ecs
package, and should users of ecs
want to use a similar priority queue outside of World
, the implementation of the sort.Sort
interface is trivial.
The Systems
type is only used by the private systems
field of the World
structure, and the Systems
method on World
returns []System
rather than Systems
. Therefore we may easily unexport it, without breaking too many users. And, obviously the time to make changes to core packages like ecs
is now since #13 was just merged, and all users have to update their code anyways.
From world.go
type World struct {
systems Systems
}
// Systems returns a list of Systems
func (w *World) Systems() []System {
return w.systems
}
Once we have the tests described in #4, we could add some kind of build-system (Travis? Something else?) which ensures that we're not breaking the code as we're progressing.
This isn't possible for engo
because that depends on a lot of CGO and libraries, but this repository has no such dependencies, and is perfectly suited for it.
The README has a small error in one of the code examples, were type
is used instead of func
.
type (m *MySystem) Add(basic ecs.BasicEntity, a *ComponentA) { /* Add stuff goes here */ }
type (m *MySystem) Remove(basic ecs.BasicEntity) { /* Remove stuff here */ }
type (m *MySystem) Update(dt float32) { /* Update stuff here */ }
Should be changed to:
func (m *MySystem) Add(basic ecs.BasicEntity, a *ComponentA) { /* Add stuff goes here */ }
func (m *MySystem) Remove(basic ecs.BasicEntity) { /* Remove stuff here */ }
func (m *MySystem) Update(dt float32) { /* Update stuff here */ }
So you won't have to create entirely new interfaces for each system! You could just have one interface for each component, and add that to a slice of interfaces at the add. Might be easier, I'll have to try it to see! :P
If I want to be able to easily look up an entity by its ID, I would need to easily store purely based on ID; one solution to this is:
type Identifier interface {
ID() uint64
}
// Registry is an example data structure where this would be useful
// it could implement the basic Add/Remove functionality that is so common
type Registry struct {
entities map[uint64]Identifier
entitiesRWMutex sync.RWMutex
}
The benefit of this is I can register something in one system, and the dynamically
check to see if whatever that entity was implements a specific interface at run time.
this means I don't need to store as many 'half implemented' subsets of the entities, and
i will have less duplicated code. The only thing I need to store in the system itself
would be the ID, and the information specific to the system.
maybe just me, but I am not able to use ecs.BasicFace
to extend an interface.
Harley Laue noted at the slack-chat, that our ecs.NewEntity
function takes a []string
as an argument.
We could easily change this to ...string
, without changing much to the ecs.NewEntity
method, it wouldn't change anything to the Entity
type, and it would make the syntax look way nicer.
e := ecs.NewEntity([]string{"RenderSystem", "MouseSystem"})
would become
e := ecs.NewEntity("RenderSystem", "MouseSystem")
instead of TravisCI
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.