Giter Club home page Giter Club logo

star-entity's Introduction

star-entity

An open-source entity-component-system, written in D. star-entity offers component management, entity creation, event delivery, and system management.

This framework is essentially a D port of EntityX by Alec Thomas, with some features from Ashley, managed by Badlogic Games.

Overview

The framework is modeled after the Entity-Component-System (ECS) architecture, a form of decomposition that decouples logic and data, and using composition instead of inheritance to allow greater flexibility and modular functionality.

Essentially, data is condensed into a component, a simple data class, and an Entity is simply an aggregate of these components. Systems encapsulates logic and operates upon a specific subset of entities, namely those with specific components. Events allow for system interaction without tight coupling.

As an example: a game might have players with health, sword, speed, position, bounds and sprite, and walls would have position, bounds, and sprite.

The graphics system would need only the position and sprite components, whereas the physics might require the position and bounds.

If the player collided with the wall, the physics system might emit a collision event.

The article Evolve your Hierarchy offers a great introduction and overview of ECS frameworks and how they can make your code more modular, more extensible, and simpler.

Building

This project uses the DUB build system, found here.

To build the project, simply run in the top-level directory

dub build --build=release

To use this project as a dependency, add the latest version (see Releases) to your dub.json:

"dependencies": {
    "star-entity": "~>1.0.8"
}

Usage

Some example code to implement the aforementioned physics system:

Entities

star.entity.Entity wraps on opaque index (uint) that is used to add, remove, or retrieve components in its corresponding star.entity.EntityManager.

Creating an entity is done by

import star.entity;

auto engine = new Engine;
auto entity = engine.entities.create();

The entity is destroyed by

entity.destroy();

Implementation details:

  • The entity wraps an index (uint) and a tag (uint).
  • Entity acts as a handle, meaning that multiple Entities may refer to the same entity.
  • Entity.invalidate() is used to invalidate the handle, meaning it can no longer be used. The data, however, is still intact and is still accessible.
  • Entity.destroy() is used to invalidate all handles and deallocate the data, freeing the index for reuse by a new entity.
  • Entity.valid() should always be used to check validity before usage.
  • Destruction is done by incrementing the tag; thus making all current Entities tags unequal and invalid.

Components

Components should be designed to hold data, and have few methods (if any).
At the moment, they must be implemented as classes (for internal storage), but in the future I hope to implement templates properly to enable using POD structs.

Creation

Continuing our previous example of a physics system:

class Position
{
    this(double x, double y) { this.x = x; this.y = y; }
    double x, y;
}

class Velocity
{
    this(double x, double y) { this.x = x; this.y = y; }
    double x, y;
}

class Gravity
{
    this(double accel) { this.accel = accel; }
    double accel;
}

Assignment

To associate these components with an entity, call Entity.add(C)(C component):

entity.add(new Position(1.0, 2.0));
entity.add(new Velocity(15.0, -2.0));
entity.add(new Gravity(-9.8));

Querying

To access all entities with specific components, use EntityManager.entities!(Components...)():

foreach(entity; engine.entities.entities!(Position, Velocity))
{
    // Do work with entities containing Position and Velocity components
}

To access a specific entity's component, use Entity.component!(C)():

auto velocity = entity.component!Velocity();

Systems

Systems implement logic and behavior.
They must implement the star.system.System interface (configure() and update())

Continuing our physics example, let's implement a movement and gravity system:

class MovementSystem : System
{
    void configure(EventManager events) { }
    void update(EntityManager entities, EventManager events, double dt)
    {
        foreach(entity; entities.entities!(Position, Velocity)())
        {
            auto position = entity.component!Position();
            auto velocity = entity.component!Velocity();
            position.x += velocity.x * dt;
            position.y += velocity.y * dt;
        }
    }
}

class GravitySystem : System
{
    void configure(EventManager events) { }
    void update(EntityManager entities, EventManager events, double dt)
    {
        foreach(entity; entities.entities!(Velocity, Gravity)())
        {
            auto gravity = entity.component!gravity();
            auto velocity = entity.component!Velocity();
            auto accel = gravity.accel * dt;
            if (antigravity)
            {
                accel = -accel;
            }
            velocity.y += accel;
        }
    }
private:
    bool antigravity = false;
}

Adding them to the system manager is quite simple:

engine.systems.add(new MovementSystem);
engine.systems.add(new GravitySystem);

Events

Events are objects (structs or classes) that indicate something has occured, e.g. a collision, button press, mouse event, etc.
Instead of setting component flags, events offer a simple way of notifying other classes of infrequent data, using callbacks.

Event types

Events can be either structs or classes. No interfaces or class extension necessary.

struct Collision
{
    Entity first, second;
}

Event emission

Our collision system will emit a Collision object if two objects collide.
(Ignore the slow algorithm below without any of that fancy "spatial partitioning". This is just an example.)

class CollisionSystem : System
{
    void configure(EventManager events) { }
    void update(EntityManager entities, EventManager events, double dt)
    {
        foreach(first; entities.entities!(Position))
        {
            foreach(second; entities.entites!(Position)())
            {
                if (collides(first, second))
                {
                    events.emit(Collision {first, second});
                }
            }
        }
    }
}

Event subscription

Classes intending to receive specific events should implement the Receiver(E) interface, for events of type E.

class DebugSystem : System, Receiver!Collision
{
    void configure(EventManager events)
    {
        events.subscribe!Collision(this);
    }

    void update(EntityManager entities, EventManager events, double dt) { }

    void receive(E)(E event) pure nothrow if (is(E : Collision))
    {
        try
        {
            debug writefln("Entities collided: %s, %s", event.first.id, event.second.id);
        }
        catch (Throwable o)
        {
        }
    }
}

<sidenote>
For those of you who've made it so far: should pure nothrow be enforced upon the receive callback?
</sidenote>

A few events are emitted by the star-entity library:

  • EntityCreatedEvent
  • EntityDestroyedEvent
  • ComponentAddedEvent(C)
  • ComponentRemovedEvent(C)

Engine

The engine ties everything together. It allows you to perform everything listed above, and manage your own game / input loop.

while (true)
{
    engine.update(0.02);
}

License

This code is licensed under the MIT License. See LICENSE for the full text.

star-entity's People

Contributors

coreyonprem avatar jameslzhu avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

Forkers

cschram sepheus

star-entity's Issues

Problem with destroying entities

I tried running this code:

import star.entity;

class Foo {
  int foo;
  this(int f) {
    this.foo = f;
  }
}
void main()
{
  auto eng = new Engine;
  auto ent = eng.entities.create();
  ent.add(new Foo(1));
  ent.destroy();
}

And got the following error:

$ dub
Target star-entity 1.0.6 is up to date. Use --force to rebuild.
Building star-test ~master configuration "application", build type debug.
Compiling using dmd...
Linking...
Running ./star-test 
core.exception.AssertError@../../../.dub/packages/star-entity-1.0.6/source/star/entity/entity.d(1002): Assertion failure
----------------
./star-test() [0x808f83d]
./star-test(const(void function()) star.entity.entity.EntityManager.__invariant1+0xc4) [0x808e3d4]
./star-test(const(void function()) star.entity.entity.EntityManager.__invariant+0x11) [0x808ed01]
./star-test(void invariant._d_invariant(Object)+0x16) [0x8093416]
./star-test(pure nothrow @safe void star.entity.entity.EntityManager.destroy(star.entity.entity.ID)+0x166) [0x808e0a6]
./star-test(pure nothrow @safe void star.entity.entity.Entity.destroy()+0x4c) [0x808d47c]
./star-test(_Dmain+0x51) [0x8084849]
./star-test(_D2rt6dmain211_d_run_mainUiPPaPUAAaZiZ6runAllMFZ9__lambda1MFZv+0x12) [0x809339a]
./star-test(void rt.dmain2._d_run_main(int, char**, extern (C) int function(char[][])*).tryExec(scope void delegate())+0x18) [0x8093310]
./star-test(void rt.dmain2._d_run_main(int, char**, extern (C) int function(char[][])*).runAll()+0x27) [0x809335f]
./star-test(void rt.dmain2._d_run_main(int, char**, extern (C) int function(char[][])*).tryExec(scope void delegate())+0x18) [0x8093310]
./star-test(_d_run_main+0x117) [0x80932a7]
./star-test(main+0x14) [0x808c3bc]
/lib/i386-linux-gnu/libc.so.6(__libc_start_main+0xf3) [0xb74d3a83]
Error executing command run: Program exited with code 1

Building in release mode just makes the program crash, so it's not the invariant that's a problem. Destroying an entity apparently kills the program.

Also, note that the error only happens if a component has been added to an entity(but even if the entity being destroyed has no components).

Am I using Entity.destroy() wrong, or what?

EDIT: I should note that, when building in release mode, the program only crashes if you try to do eng.entities.entities!(Foo).

Querying entities breaks when no entities have a component

When querying the EntityManager for entities that have a particular component before any entity has been created with that component an assertion fails:

core.exception.AssertError@../../../.dub/packages/star-entity-1.0.7/source/star/entity/entity.d(924): Assertion failure

It's essential that this does not fail, but rather returns an empty list; for instance, in cases where a MovementSystem runs before any entities have actually been created.

A possible solution would be to check EntityManager.hasType() on every component supplied to EntityManager.entities() before doing anything else, and returning an empty list if hasType returns false.

Dub test compilation errors

Platform: Windows 7 x64
dmd: DMD32 D Compiler v2.066.0
dub: 0.9.22

Runing dub test at a package that depends on the star-entity generates following:

C:\dev\d\tempwerk>dub test
Generating test runner configuration '__test__library__' for 'library' (library).
Building star-entity 1.0.5 configuration "library", build type unittest.
Running dmd...
..\..\..\Users\NCrashed\AppData\Roaming\dub\packages\star-entity-1.0.5\source\star\entity\entity.d(884): Error: cannot
 implicitly convert expression (type + 1LU) of type ulong to uint
..\..\..\Users\NCrashed\AppData\Roaming\dub\packages\star-entity-1.0.5\source\star\entity\entity.d(701): Error: templa
te instance star.entity.entity.EntityManager.accomodateComponent!(Test) error instantiating
..\..\..\Users\NCrashed\AppData\Roaming\dub\packages\star-entity-1.0.5\source\star\entity\entity.d(160):        instan
tiated from here: addComponent!(Test)
..\..\..\Users\NCrashed\AppData\Roaming\dub\packages\star-entity-1.0.5\source\star\entity\entity.d(378):        instan
tiated from here: add!(Test)
..\..\..\Users\NCrashed\AppData\Roaming\dub\packages\star-entity-1.0.5\source\star\entity\entity.d(856): Error: cannot
 implicitly convert expression (this.type()) of type ulong to uint
..\..\..\Users\NCrashed\AppData\Roaming\dub\packages\star-entity-1.0.5\source\star\entity\entity.d(702): Error: templa
te instance star.entity.entity.EntityManager.setComponent!(Test) error instantiating
..\..\..\Users\NCrashed\AppData\Roaming\dub\packages\star-entity-1.0.5\source\star\entity\entity.d(160):        instan
tiated from here: addComponent!(Test)
..\..\..\Users\NCrashed\AppData\Roaming\dub\packages\star-entity-1.0.5\source\star\entity\entity.d(378):        instan
tiated from here: add!(Test)
..\..\..\Users\NCrashed\AppData\Roaming\dub\packages\star-entity-1.0.5\source\star\entity\entity.d(862): Error: cannot
 implicitly convert expression (this.type()) of type ulong to uint
..\..\..\Users\NCrashed\AppData\Roaming\dub\packages\star-entity-1.0.5\source\star\entity\entity.d(703): Error: templa
te instance star.entity.entity.EntityManager.setMask!(Test) error instantiating
..\..\..\Users\NCrashed\AppData\Roaming\dub\packages\star-entity-1.0.5\source\star\entity\entity.d(160):        instan
tiated from here: addComponent!(Test)
..\..\..\Users\NCrashed\AppData\Roaming\dub\packages\star-entity-1.0.5\source\star\entity\entity.d(378):        instan
tiated from here: add!(Test)
..\..\..\Users\NCrashed\AppData\Roaming\dub\packages\star-entity-1.0.5\source\star\entity\entity.d(742): Error: cannot
 implicitly convert expression (this.type()) of type ulong to uint
..\..\..\Users\NCrashed\AppData\Roaming\dub\packages\star-entity-1.0.5\source\star\entity\entity.d(731): Error: templa
te instance star.entity.entity.EntityManager.component!(Test) error instantiating
..\..\..\Users\NCrashed\AppData\Roaming\dub\packages\star-entity-1.0.5\source\star\entity\entity.d(697):        instan
tiated from here: hasComponent!(Test)
..\..\..\Users\NCrashed\AppData\Roaming\dub\packages\star-entity-1.0.5\source\star\entity\entity.d(160):        instan
tiated from here: addComponent!(Test)
..\..\..\Users\NCrashed\AppData\Roaming\dub\packages\star-entity-1.0.5\source\star\entity\entity.d(378):        instan
tiated from here: add!(Test)
..\..\..\Users\NCrashed\AppData\Roaming\dub\packages\star-entity-1.0.5\source\star\entity\entity.d(884): Error: cannot
 implicitly convert expression (type + 1LU) of type ulong to uint
..\..\..\Users\NCrashed\AppData\Roaming\dub\packages\star-entity-1.0.5\source\star\entity\entity.d(701): Error: templa
te instance star.entity.entity.EntityManager.accomodateComponent!(Position) error instantiating
..\..\..\Users\NCrashed\AppData\Roaming\dub\packages\star-entity-1.0.5\source\star\entity\entity.d(160):        instan
tiated from here: addComponent!(Position)
..\..\..\Users\NCrashed\AppData\Roaming\dub\packages\star-entity-1.0.5\source\star\entity\entity.d(447):        instan
tiated from here: add!(Position)
..\..\..\Users\NCrashed\AppData\Roaming\dub\packages\star-entity-1.0.5\source\star\entity\entity.d(856): Error: cannot
 implicitly convert expression (this.type()) of type ulong to uint
..\..\..\Users\NCrashed\AppData\Roaming\dub\packages\star-entity-1.0.5\source\star\entity\entity.d(702): Error: templa
te instance star.entity.entity.EntityManager.setComponent!(Position) error instantiating
..\..\..\Users\NCrashed\AppData\Roaming\dub\packages\star-entity-1.0.5\source\star\entity\entity.d(160):        instan
tiated from here: addComponent!(Position)
..\..\..\Users\NCrashed\AppData\Roaming\dub\packages\star-entity-1.0.5\source\star\entity\entity.d(447):        instan
tiated from here: add!(Position)
..\..\..\Users\NCrashed\AppData\Roaming\dub\packages\star-entity-1.0.5\source\star\entity\entity.d(862): Error: cannot
 implicitly convert expression (this.type()) of type ulong to uint
..\..\..\Users\NCrashed\AppData\Roaming\dub\packages\star-entity-1.0.5\source\star\entity\entity.d(703): Error: templa
te instance star.entity.entity.EntityManager.setMask!(Position) error instantiating
..\..\..\Users\NCrashed\AppData\Roaming\dub\packages\star-entity-1.0.5\source\star\entity\entity.d(160):        instan
tiated from here: addComponent!(Position)
..\..\..\Users\NCrashed\AppData\Roaming\dub\packages\star-entity-1.0.5\source\star\entity\entity.d(447):        instan
tiated from here: add!(Position)
..\..\..\Users\NCrashed\AppData\Roaming\dub\packages\star-entity-1.0.5\source\star\entity\entity.d(742): Error: cannot
 implicitly convert expression (this.type()) of type ulong to uint
..\..\..\Users\NCrashed\AppData\Roaming\dub\packages\star-entity-1.0.5\source\star\entity\entity.d(731): Error: templa
te instance star.entity.entity.EntityManager.component!(Position) error instantiating
..\..\..\Users\NCrashed\AppData\Roaming\dub\packages\star-entity-1.0.5\source\star\entity\entity.d(697):        instan
tiated from here: hasComponent!(Position)
..\..\..\Users\NCrashed\AppData\Roaming\dub\packages\star-entity-1.0.5\source\star\entity\entity.d(160):        instan
tiated from here: addComponent!(Position)
..\..\..\Users\NCrashed\AppData\Roaming\dub\packages\star-entity-1.0.5\source\star\entity\entity.d(447):        instan
tiated from here: add!(Position)
..\..\..\Users\NCrashed\AppData\Roaming\dub\packages\star-entity-1.0.5\source\star\entity\entity.d(884): Error: cannot
 implicitly convert expression (type + 1LU) of type ulong to uint
..\..\..\Users\NCrashed\AppData\Roaming\dub\packages\star-entity-1.0.5\source\star\entity\entity.d(701): Error: templa
te instance star.entity.entity.EntityManager.accomodateComponent!(Velocity) error instantiating
..\..\..\Users\NCrashed\AppData\Roaming\dub\packages\star-entity-1.0.5\source\star\entity\entity.d(160):        instan
tiated from here: addComponent!(Velocity)
..\..\..\Users\NCrashed\AppData\Roaming\dub\packages\star-entity-1.0.5\source\star\entity\entity.d(448):        instan
tiated from here: add!(Velocity)
..\..\..\Users\NCrashed\AppData\Roaming\dub\packages\star-entity-1.0.5\source\star\entity\entity.d(856): Error: cannot
 implicitly convert expression (this.type()) of type ulong to uint
..\..\..\Users\NCrashed\AppData\Roaming\dub\packages\star-entity-1.0.5\source\star\entity\entity.d(702): Error: templa
te instance star.entity.entity.EntityManager.setComponent!(Velocity) error instantiating
..\..\..\Users\NCrashed\AppData\Roaming\dub\packages\star-entity-1.0.5\source\star\entity\entity.d(160):        instan
tiated from here: addComponent!(Velocity)
..\..\..\Users\NCrashed\AppData\Roaming\dub\packages\star-entity-1.0.5\source\star\entity\entity.d(448):        instan
tiated from here: add!(Velocity)
..\..\..\Users\NCrashed\AppData\Roaming\dub\packages\star-entity-1.0.5\source\star\entity\entity.d(862): Error: cannot
 implicitly convert expression (this.type()) of type ulong to uint
FAIL ..\..\..\Users\NCrashed\AppData\Roaming\dub\packages\star-entity-1.0.5\.dub\build\library-unittest-windows-x86-dm
d_2066-636256E26935E5611654D36935405E62\ star-entity staticLibrary
Error executing command test: dmd failed with exit code 1.

Can't build with the latest DMD (2.067.1)

Subj. "dub build" spits this:

source/star/entity/entity.d(70): Error: undefined identifier 'conv'

It seems that std.conv isn't imported for some reason. Adding

import std.conv : to;

at the beginning of entity.d seems to solve the issue, hovewer I'm not sure that this is an optimal way, so I don't make a PR for now.

Component storage

Currently, components are limited to being classes; the EntityManager defines a member

Object[][] _components;

enabling it to store a generic object deriving from Object (essentially, classes, not structs).

However, this conflicts with the cache-friendly layout of having contiguous data, instead containing references to objects allocated on the heap rather than on the stack; this nullifies the speed performance boost enabled by ECS.

The idea is to reimplement the _components member variable as an object pool, allowing space to be reused and contiguous in memory.

multiple components?

how to use multiple components? in the readme and in the entity manager, there's a function only to get a single component. is it possible to get a list of all matching components?

Entity as a struct

hi, sorry if my question is stupid,

but if I understand correctly the code, a new Entity object is instanciated each time front() function is called. Isn't that bad for the GC ? maybe we can use a struct instead of class for Entity ?

Assemblages

As specified by T-Machine, Assemblages ought to be implemented to designate pre-made entities (component groupings), allowing for oft-used component combinations to be stored in databases later.

i.e. assemblage = template for an entity, with a list of component IDs.

System cast problem

I have a system:

class GraphicsSystem : System
{
//....
}

And tries to:

systems.system!GraphicsSystem

Got:

source\star\entity\system.d(64): Error: cannot implicitly convert expression (*sys) of type star.entity.system.System to epsylon.systems.graphics.GraphicsSystem
    /// Return the specified system.
    S system(S)() pure nothrow @safe
    {
        auto sys = (S.classinfo.name in _systems);
        if (sys)
        {
            return *sys; // <-- would excplicit cast fix this?
        }
        else
        {
            return null;
        }
    }

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.