EntityMonkey, a simple Entity System for JME

Hello,

This is my current ‘free time’ project. After reading some nice articles about entity systems i tought it would be nice to have a render thread independent entity management system. Since this all is also new land for me i am always open to suggestions…



Currently i have implemented a simple system which can handle ~ 10000 moving entitys at 100FPS in single thread mode. Each entity has 4 components attached, and in total i have 3 Systems running on the entitymanager thread, and 1 System at the render thread. The overhead for creating new threads is bigger than the execution time for these simple systems. In the future each EntitySystem can decide if it will evaluate the entities paralell or sequential.



If someone wants to try the system: the libaray can be downloaded here: when-money-makes-money.com

A simple how to extend the system can be found in post #2

A simple testcase can be found in post #3.





If there is public interest i will keep the library and this thread updated with new stuff.



So how it works?

Entity: an Entity is a simple object identified by it’s own uuid. It offers functions to add different components.

Component: a component is used for storing data in an entity. It never includes any logic or calculations.

Examples:

[java]

public class PositionComponent extends EntityComponent{

Vector3f position;



public PositionComponent() {

this.position=new Vector3f();

}



public Vector3f getPosition() {

return position;

}



public void setPosition(Vector3f position) {

this.position = position;

}



}



public class NonPhysicMovementComponent extends EntityComponent{

Vector3f movement;



public NonPhysicMovementComponent() {

this.movement=new Vector3f();

}



public Vector3f getMovement() {

return movement;

}



public void setMovement(Vector3f position) {

this.movement = position;

}



}

[/java]



System: A Entity system is used to apply game logic depending on the components of an entity.

Example:

[java]public class NonPhysicMovementSystem extends EntitySystem{



public NonPhysicMovementSystem(EntityMonkey entityMonkey) {

super(new Class[]{PositionComponent.class,NonPhysicMovementComponent.class}, entityMonkey);

}



@Override

public void evaluate(Entity e, double delta) {

((PositionComponent)e.getComponent(PositionComponent.class)).setPosition(((PositionComponent)e.getComponent(PositionComponent.class)).getPosition().add(((NonPhysicMovementComponent)e.getComponent(NonPhysicMovementComponent.class)).getMovement().mult((float)delta)));

}



}

[/java]

The constructor of the EntitySystem requires an array of Classes (The components an entity uses) and a reference to the main class.

During the updateloop the function evaluate(Entitye, double delta) is called for each entity which has the components needed.



The main class (EntityMonkey) offers functions for creating new Entities and for adding/removing EntitySystems to the EntityUpdateLoop, or the RenderUpdateLoop. Trough this design i have a completely thread independet EntitySystem. The whole gamelogic can update at 10000FPS while the Renderloop can get limited to only 30FPS.

Of course this requires that all renderthread depending EntitySystems get’s attached to the RenderUpdateLoop.

En example of such a system would be:

[java]

public class NonPhysicMovmentUpdateRendererSystem extends EntitySystem{



public NonPhysicMovmentUpdateRendererSystem(EntityMonkey entityMonkey) {

super(new Class[]{SpatialComponent.class,PositionComponent.class}, entityMonkey);

}



@Override

public void evaluate(Entity e, double delta) {

//System.out.println(e.getUuid()+": Position:"+((SpatialComponent)e.getComponent(SpatialComponent.class)).getSpatial().getLocalTranslation().toString());

((SpatialComponent)e.getComponent(SpatialComponent.class)).getSpatial().setLocalTranslation(((PositionComponent)e.getComponent(PositionComponent.class)).getPosition().clone());

}



}

[/java]

5 Likes

There are currently 3 different classes you might extend:

  1. UuidGenerator:

    [java]

    public abstract class EntityUuidGenerator {

    public abstract String generateNewUuid();

    public abstract Entity onUuidCollision(Entity e);

    }

    [/java]

    As default a simple SHA1(System.nanoTime()) is used. If you want something else, make your own class, extend EntityUuidGenerator and implement the two function. onUuidCollision is called when there already exists an entity with the uuid generated with generateNewUuid();


  2. Component, if you want to add a component to your entities you have to write a class which extends EntityComponent. An entity can hold only one component for each type. (i.e: Only one PositionComponent.java is allowed per component)

    [java]

    public abstract class EntityComponent implements Comparable{



    @Override

    public int compareTo(Object o) {

    return(this.getClass().getName().compareTo(o.getClass().getName()));

    }



    }

    [/java]


  3. EntitySystem: If you want to include logic, extend EntitySystem. The constructor requires an array of component classes, and a reference to the entityManager.

    The manager automatically calls the evaluate function for each entity which has the needed components.

    [java]

    public abstract class EntitySystem {

    Class[] dependingComponents;

    EntityMonkey entityMonkey;



    public EntitySystem(Class[] dependingComponents, EntityMonkey entityMonkey) {

    this.dependingComponents = dependingComponents;

    this.entityMonkey = entityMonkey;

    }



    public Class[] getDependingComponents() {

    return dependingComponents;

    }



    public abstract void evaluate(Entity e,double delta);

    }

    [/java]





    Setting up the system:

    Simply attach the EntityMonkey to your appStates.

    If you create new Enities with: EntityMonkey.createNewEntity() the entity gets automatically added to the used entities.

    Attach new EntitySystems with one of the two methods:

    [java]

    EntityMonkey.addEntitySystemToRenderThread(EntitySystem es); //Adds a new system which gets evaluated on the RenderThread

    EntityMonkey.addEntitySystemToIndependentThread(EntitySystem es); //Adds a new system to the independent EntityThread. NOTE: you cannot make any changes to the scenegraph from any system in thos pool

    [/java]
1 Like

The simple/ugly testcase



[java]

package mygame;



import EntityMonkey.Default.Components.NonPhysicMovementComponent;

import EntityMonkey.Default.Components.PositionComponent;

import EntityMonkey.Default.Components.RandomTimeComponent;

import EntityMonkey.Default.Components.SpatialComponent;

import EntityMonkey.Default.Systems.NonPhysicMovementDirectionChangerSystem;

import EntityMonkey.Default.Systems.NonPhysicMovementSystem;

import EntityMonkey.Default.Systems.NonPhysicMovmentUpdateRendererSystem;

import EntityMonkey.EntityMonkey;

import EntityMonkey.Model.Entity;

import com.jme3.app.Application;

import com.jme3.app.SimpleApplication;

import com.jme3.app.state.AbstractAppState;

import com.jme3.app.state.AppStateManager;

import com.jme3.font.BitmapFont;

import com.jme3.font.BitmapText;

import com.jme3.input.KeyInput;

import com.jme3.input.controls.ActionListener;

import com.jme3.input.controls.KeyTrigger;

import com.jme3.light.DirectionalLight;

import com.jme3.material.Material;

import com.jme3.math.ColorRGBA;

import com.jme3.math.FastMath;

import com.jme3.math.Vector3f;

import com.jme3.renderer.RenderManager;

import com.jme3.scene.Geometry;

import com.jme3.scene.Node;

import com.jme3.scene.shape.Sphere;

import com.jme3.util.TangentBinormalGenerator;



/**

  • test
  • @author normenhansen

    /

    public class Main extends SimpleApplication {



    public static void main(String[] args) {

    Main app = new Main();

    app.start();

    }



    @Override

    public void simpleInitApp() {

    /
    * A white, directional light source /

    DirectionalLight sun = new DirectionalLight();

    sun.setDirection((new Vector3f(-0.5f, -0.5f, -0.5f)).normalize());

    sun.setColor(ColorRGBA.White);

    rootNode.addLight(sun);



    this.flyCam.setMoveSpeed(100f);



    this.stateManager.attach(new EntitySpawner());

    }



    @Override

    public void simpleUpdate(float tpf) {

    //TODO: add update code

    }



    @Override

    public void simpleRender(RenderManager rm) {

    //TODO: add render code

    }



    public class EntitySpawner extends AbstractAppState implements ActionListener{



    private SimpleApplication app;

    EntityMonkey entityManager;

    Node rock_shiny = new Node();

    BitmapFont defaultFont;

    BitmapText debugText;



    @Override

    public void initialize(AppStateManager stateManager, Application app) {

    super.initialize(stateManager, app);

    this.app = (SimpleApplication) app; // cast to a more specific class

    entityManager = new EntityMonkey();

    entityManager.setUseMultiThreading(true);

    entityManager.start();

    this.app.getStateManager().attach(this.entityManager);

    entityManager.addEntitySystemToIndependentThread(new NonPhysicMovementDirectionChangerSystem(entityManager));

    entityManager.addEntitySystemToIndependentThread(new NonPhysicMovementSystem(entityManager));

    entityManager.addEntitySystemToRenderThread(new NonPhysicMovmentUpdateRendererSystem(entityManager));





    defaultFont = app.getAssetManager().loadFont("Interface/Fonts/Default.fnt");

    this.debugText = new BitmapText(defaultFont, false);

    this.debugText.setSize(defaultFont.getCharSet().getRenderedSize() / 3);



    this.debugText.setLocalTranslation(10f, this.app.getCamera().getHeight() * 0.9f, 0f);



    this.debugText.setLocalScale(4f);

    this.debugText.setColor(ColorRGBA.White);

    this.app.getGuiNode().attachChild(this.debugText);

    /
    * Illuminated bumpy rock with shiny effect.
  • Uses Texture from jme3-test-data library! Needs light source! */

    Sphere rock = new Sphere(32, 32, 2f);

    rock_shiny.attachChild(new Geometry("Shiny rock", rock));

    rock.setTextureMode(Sphere.TextureMode.Projected); // better quality on spheres

    TangentBinormalGenerator.generate(rock); // for lighting effect

    Material mat_shiny = new Material(app.getAssetManager(), "Common/MatDefs/Light/Lighting.j3md");

    // mat_shiny.setTexture("DiffuseMap", app.getAssetManager().loadTexture("Textures/Terrain/Pond/Pond.png"));

    // mat_shiny.setTexture("NormalMap", app.getAssetManager().loadTexture("Textures/Terrain/Pond/Pond_normal.png"));

    //mat_shiny.setTexture("GlowMap", assetManager.loadTexture("Textures/glowmap.png")); // requires flow filter!

    mat_shiny.setBoolean("UseMaterialColors", true); // needed for shininess

    mat_shiny.setColor("Specular", ColorRGBA.White); // needed for shininess

    mat_shiny.setColor("Diffuse", ColorRGBA.White); // needed for shininess

    mat_shiny.setFloat("Shininess", 5f); // shininess from 1-128

    rock_shiny.setMaterial(mat_shiny);



    this.app.getInputManager().addListener(this,new String[]{ "ThreadingTypeChange"});

    this.app.getInputManager().addMapping("ThreadingTypeChange", new KeyTrigger(KeyInput.KEY_T));



    }

    float timeLeft = 0;

    String text;



    @Override

    public void update(float tpf) {

    text = "EntitySystem:n";

    text = text + "FPS: " + (int) this.entityManager.FPS() + "n";

    text = text + "EntityCount: " + this.entityManager.getEntityCount()+"n";

    text = text + "Multithreaded?:"+this.entityManager.isUseMultiThreading();

    this.debugText.setText(text);

    timeLeft -= tpf;

    //if (timeLeft < 0) {

    timeLeft = FastMath.nextRandomFloat() * 0.2f;

    Entity e = this.entityManager.createNewEntity();

    e.addComponent(new SpatialComponent((Node) this.rock_shiny.clone()));

    e.addComponent(new PositionComponent());

    e.addComponent(new NonPhysicMovementComponent());

    e.addComponent(new RandomTimeComponent());

    this.app.getRootNode().attachChild(((SpatialComponent) e.getComponent(SpatialComponent.class)).getSpatial());

    //}

    }



    public void onAction(String name, boolean isPressed, float tpf) {

    if(name.equals("ThreadingTypeChange")&&!isPressed){

    this.entityManager.setUseMultiThreading(!this.entityManager.isUseMultiThreading());

    }

    }

    }

    }



    [/java]
2 Likes

oh snap :slight_smile: i’ve been meaning to look at ES for a while, everyone is jizzing all over them atm, thanks for this!

Why would you want to run the simulation at 1000fps but display at only 30? Unless you are running simulations or similar I don’t see why you would ever want to calculate something that isn’t for display.



Have you considered using a bitfield for the list of required components? If you did that then checking whether each system is interested in each entity becomes a simple components & system.requiredComponents != 0.

e.addComponent(new SpatialComponent((Node) this.rock_shiny.clone()));

That is exactly the same mistake i made.

Maybe you should read this:
http://hub.jmonkeyengine.org/groups/development-discussion-jme3/forum/topic/entitysystem-how-to-represent-entities-in-the-scenegraph

Theres more in that thread then the initial question, e.g. there also said that it is a good idea fpor your components to be immutable, which yours weren't.

Interesting…

@zarch said:
Have you considered using a bitfield for the list of required components? If you did that then checking whether each system is interested in each entity becomes a simple components & system.requiredComponents != 0.


Using a bitfield would indeed incread the performance of the system. I am not yet sure how to implement it nicely to have the possibility to add new components at runtime without having to change the already loaded components.

@zarch said:
Why would you want to run the simulation at 1000fps but display at only 30? Unless you are running simulations or similar I don’t see why you would ever want to calculate something that isn’t for display.


A Simple scenario which comes into my mind is a network layer. Maybe the user uses vsync so the graphics part is limited to ~60fps. With the current design the network would still send sync messages at a higher rate.

However, the design was not chooses to allow a much higher game logic,(this is more a side effect) but to:
a) have a thread save game logic.
b) force yourself as a programmer to keep data and logic stricly in two different places.
@polygnome said:
That is exactly the same mistake i made.

Maybe you should read this:
http://hub.jmonkeyengine.org/groups/development-discussion-jme3/forum/topic/entitysystem-how-to-represent-entities-in-the-scenegraph

Theres more in that thread then the initial question, e.g. there also said that it is a good idea fpor your components to be immutable, which yours weren't.


Sorry, i don't see a mistake in this. Each entity should have it's own id. Why allowing multiple graphical parts to share the same id? If you want to add child geometries to an entity you could simple implement the container pattern in a component.
[java]
public class EntityHasChilds extend EntityComponent{
Entity parent;
List<Entity> childs;
}
[/java]

Then you could use a simple EntitySystem which requires the EntityHasChilds component:
[java]
public class NonPhysicMovementSystem extends EntitySystem{

public NonPhysicMovementSystem(EntityMonkey entityMonkey) {
super(new Class[]{EntityHasChilds.class}, entityMonkey);
}

@Override
public void evaluate(Entity e, double delta) {
for(Entity eChild:e.getComponent(EntityHasChilds.class).getChilds()){
e.getComponent(SpatialComponent.class).getNode().attachChilds(eChilds.getComponent(SpatialComponents.class).getNode());
}
}

}
[/java]

Trough this you can even unlimited nested child entities, whitout changing one line of code in the logic....

If you see a different problem please specify...

I’d be tempted to connect the game updates to the framerate - after all if system performance drops you might want to drop simulation accuracy as well as framerate to try and keep things flowing.



Most networking systems wouldn’t try and run at 1000fps either. 1000 sync messages per client per second is going to flood your network.



For the bitfield, you just need a unique integer id per class:

[java]

int nextId = 0;

Map<Class, integer> classIds = new HashMap<Class, Integer>();



getClassId(Class class) {

Integer id = classIds.get(class);

if (id == null) {

id = nextId++;

classIds.put(class, id);

}

return id;

}



setComponent(Object comp) {

id = getClassId(comp.getClass);

…etc

}

[/java]

Obviously you need to think about thread safety on this a bit as well somehow.



Generic magic on getComponent will also let you get rid of all the casting on calls to it. Something like:



[java]public <T> T getComponent(Class<T> clazz) {

}

[/java]

@zzuegg said:
Sorry, i don't see a mistake in this. Each entity should have it's own id. Why allowing multiple graphical parts to share the same id? If you want to add child geometries to an entity you could simple implement the container pattern in a component.

Did you at least read the thread i linked?

You should *not* use any object that is not data in your ES. A Node, or Spatial, is not raw data. therefore, it simply can not be part of a component.

And yes, ofcourse you can have several entities which will contribute to a whole object in the scenegraph later. Why not?

I was tempted to do the same thing you did, but it felt wrong the whole time. So i opened that thread i linked above to ask how others did it. And got pretty good answers.
@polygnome said:
Did you at least read the thread i linked?

You should *not* use any object that is not data in your ES. A Node, or Spatial, is not raw data. therefore, it simply can not be part of a component.

And yes, ofcourse you can have several entities which will contribute to a whole object in the scenegraph later. Why not?

I was tempted to do the same thing you did, but it felt wrong the whole time. So i opened that thread i linked above to ask how others did it. And got pretty good answers.


Yes, i did. Well, you are right that a Node/Spatial is not raw data. However, as said, the default Components where only included in the lib to start a quick test, nobody limits you to to use a component of the type:
[java]
public class SpatialDataComponent extends EntityComponent{
String assetFileToLoad;
String nodeId= Entity.getUuid();
}
[/java]

for the link between entity's and the scenegraph instead of the default SpatialComponent.

Settable in the Component, already failed to make it properly :wink:

Ok I guess its time to put this here at least once, we compiled this list of “entity system rules” in the core team some time.


  1. Entities are only an id value
  • Ideally entities are just a “long” id, a class may exist that allows simple access to a specific entities components, e.g. new Entity(1000).getComponent(MyComponent.class) would grab the MyComponent from entity 1000.


  1. Components are only data
  • Components only contain data, and absolutely no code, not even helper methods. Components are best implemented as classes with fields and only getters and one constructor that sets all values. If this becomes cumbersome, you have an indicator that you should either separate the component data into multiple components or even entities. Each time you need data you get a component from an entity and get an immutable copy. To change the component, you create a new component that you set on the entity. This brings “free” thread safety for asynchronous / threaded access and messaging.


  1. Systems can be anything and use the entities components as their data.
  • Systems can introduce other programming paradigms “under the hood”, as long as they interact with the “ES parts” of the application only via components.


  1. One component per type per entity
  • this isn’t required by the ES per se and it could be convenient sometimes though not doing it gives a much cleaner result and forces one to do the right separation into more components and entities at the right times.


  1. Don’t stick system-specific data into a Component, abstract it to a general level or store it completely in the system
  • e.g. one might feel like storing a Spatial inside a VisualComponent or something, instead the system should handle these based on components that only contain generally compatible data (e.g. a model name string).


  1. Use references instead of lists
  • Instead of having an InventoryComponent with a list of items in the inventory, have an InventoryItemComponent that only contains the container entity id and set it on the inventory item. This way a) any item can only be in one inventory at any time and b) you can easily get all items in a certain container by querying by the InventoryItemComponent.


  1. Never subclass Component classes. I went down that road and

    everything fell apart. It is more apparent when you put behaviour in

    the Components, but since behaviour should all be in a System, then

    there should be no need to subclass.



    8 ) Personally, I find it dangerous to pass entity IDs around as naked longs and be able to instantiate an Entity directly from that. I would argue that it is preferable that Entity objects (whatever convenience interface is defined) always come from the entity system. And that entity IDs be immutable opaque objects (that contain a long) such that one avoids the C/C++ ptr++ style accidents and hacks.



    … more to come I guess…



    ---- EXAMPLE ----

    Note that in this example rule 8 isn’t really obeyed. One might want to hide the longs completely and only make the Entity classes available and exchangeable.



    This is an example of a simple in-memory Entity database:

    [java]public final class Entities {



    private static long idx = 0;

    //TODO: DB

    private static ConcurrentHashMap<Long, ConcurrentHashMap<Class, Object>> components = new ConcurrentHashMap<Long, ConcurrentHashMap<Class, Object>>();



    public static Entity getNewEntity() {

    return new Entity(idx++);

    }



    public static <T extends Object> T getComponent(long entityId, Class<T> controlType) {

    ConcurrentHashMap<Class, Object> entityMap = components.get(entityId);

    if (entityMap == null) {

    return null;

    } else {

    return (T) entityMap.get(controlType);

    }

    }



    public static void setComponent(long entityId, Object component) {

    ConcurrentHashMap<Class, Object> entityMap = components.get(entityId);

    if (entityMap == null) {

    entityMap = new ConcurrentHashMap<Class, Object>();

    components.put(entityId, entityMap);

    }

    entityMap.put(component.getClass(), component);

    }



    public static void clearComponent(long entityId, Class componentClass) {

    ConcurrentHashMap<Class, Object> entityMap = components.get(entityId);

    if (entityMap == null) {

    return;

    }

    entityMap.remove(componentClass);

    }



    public static List<Entity> getEntities(Class component) {

    LinkedList<Entity> list = new LinkedList<Entity>();

    for (Iterator<Entry<Long, ConcurrentHashMap<Class, Object>>> it = components.entrySet().iterator(); it.hasNext():wink: {

    Entry<Long, ConcurrentHashMap<Class, Object>> entry = it.next();

    if (entry.getValue().containsKey(component)) {

    list.add(new Entity(entry.getKey()));

    }

    }

    return list;

    }



    public static List<Entity> getEntities(Object component) {

    LinkedList<Entity> list = new LinkedList<Entity>();

    for (Iterator<Entry<Long, ConcurrentHashMap<Class, Object>>> it = components.entrySet().iterator(); it.hasNext():wink: {

    Entry<Long, ConcurrentHashMap<Class, Object>> entry = it.next();

    if (entry.getValue().containsKey(component.getClass())) {

    Object curComponent = entry.getValue().get(component.getClass());

    if (curComponent.equals(component)) {

    list.add(new Entity(entry.getKey()));

    }

    }

    }

    return list;

    }

    }[/java]



    And the corresponding Entity class for access to the components:

    [java]public final class Entity {



    private long id;



    public Entity(long id) {

    this.id = id;

    }



    public <T extends Object> T getComponent(Class<T> controlType) {

    return Entities.getComponent(id, controlType);

    }



    public void setComponent(Object comp) {

    Entities.setComponent(id, comp);

    }



    public void clearComponent(Class componentType){

    Entities.clearComponent(id, componentType);

    }



    public long getId() {

    return id;

    }



    @Override

    public boolean equals(Object o) {

    if (o instanceof Entity) {

    Entity entity = (Entity) o;

    return entity.getId() == id;

    }

    return super.equals(o);

    }



    @Override

    public int hashCode() {

    return (int) id;

    }

    }

    [/java]



    A Component looks like this:

    [java]public final class PositionComponent {



    private Vector3f location;

    private Quaternion rotation;

    private long lastUpdate;



    public PositionComponent(Vector3f location, Quaternion rotation, long lastUpdate) {

    this.location = location.clone();

    this.rotation = rotation.clone();

    this.lastUpdate = lastUpdate;

    }



    public Vector3f getLocation() {

    return location;

    }



    public Quaternion getRotation() {

    return rotation;

    }



    public long getLastUpdate() {

    return lastUpdate;

    }

    }[/java]



    And a System using the whole shebang like this (in this case its a Control which only needs to be created once along with its spatial and then handles everything on its own):

    [java]public final class EntityControl extends AbstractControl {



    private static final Logger logger = Logger.getLogger(EntityControl.class.getName());

    private static final Map<String, Spatial> models = new ConcurrentHashMap<String, Spatial>();

    private Entity entity;

    private AssetManager manager;

    private String currentModelName;

    private Spatial currentModel;

    private long lastLocationUpdate;

    private float updateTime;

    private List<AnimControl> animControls;

    private List<AnimChannel> animChannels;



    public EntityControl(Entity entity, AssetManager manager) {

    this.entity = entity;

    this.manager = manager;

    }



    @Override

    public void setSpatial(Spatial spatial) {

    super.setSpatial(spatial);

    }



    @Override

    protected void controlUpdate(float tpf) {

    if (entity == null) {

    return;

    }

    if (!updateVisualRep()) {

    return;

    }

    if (!updateLocation(tpf)) {

    return;

    }

    if (!updateAnimation()) {

    return;

    }

    }



    private boolean updateVisualRep() {

    VisualRepComponent visRep = entity.getComponent(VisualRepComponent.class);

    if (visRep != null) {

    if (currentModelName != null && currentModelName.equals(visRep.getAssetName())) {

    return true;

    } else {

    if (currentModel != null) {

    setAnimControls(null);

    currentModel.removeFromParent();

    }

    currentModelName = visRep.getAssetName();

    currentModel = manager.loadModel(currentModelName);

    setAnimControls(currentModel);

    ((Node) spatial).attachChild(currentModel);

    }

    } else {

    //dispose ourselves if the entity has no VisualRepComponent anymore…

    setAnimControls(null);

    spatial.removeFromParent();

    entity.clearComponent(InSceneComponent.class);

    return false;

    }

    return true;

    }



    private boolean updateLocation(float tpf) {

    PositionComponent position = entity.getComponent(PositionComponent.class);

    MovementComponent movement = entity.getComponent(MovementComponent.class);

    if (movement != null && position != null) {

    spatial.setLocalTranslation(position.getLocation());

    spatial.setLocalRotation(position.getRotation());



    if (position.getLastUpdate() == lastLocationUpdate) {

    //TODO: interpolate

    }

    } else if (position != null) {

    spatial.setLocalTranslation(position.getLocation());

    spatial.setLocalRotation(position.getRotation());

    }

    return true;

    }



    private boolean updateAnimation() {

    MovementComponent movement = entity.getComponent(MovementComponent.class);

    if (movement != null && movement.getMovement().length() > 0) {

    setAnimation(“walk”);

    } else {

    setAnimation(“idle”);

    }

    return true;

    }



    private void setAnimation(String name) {

    if (animChannels != null) {

    for (Iterator<AnimChannel> it = animChannels.iterator(); it.hasNext():wink: {

    AnimChannel animChannel = it.next();

    if (animChannel.getAnimationName() == null || !animChannel.getAnimationName().equals(name)) {

    animChannel.setAnim(name);

    logger.log(Level.INFO, “Setting anim {0}”, name);

    if (animChannel.getControl().getAnim(name) != null) {

    }

    }

    }

    }

    }



    private void setAnimControls(Spatial spatial) {

    if (spatial == null) {

    if (animControls != null) {

    for (Iterator<AnimControl> it = animControls.iterator(); it.hasNext():wink: {

    AnimControl animControl = it.next();

    animControl.clearChannels();

    }

    }

    animControls = null;

    animChannels = null;

    return;

    }

    SceneGraphVisitorAdapter visitor = new SceneGraphVisitorAdapter() {



    @Override

    public void visit(Geometry geom) {

    super.visit(geom);

    checkForAnimControl(geom);

    }



    @Override

    public void visit(Node geom) {

    super.visit(geom);

    checkForAnimControl(geom);

    }



    private void checkForAnimControl(Spatial geom) {

    AnimControl control = geom.getControl(AnimControl.class);

    if (control == null) {

    return;

    }

    if (animControls == null) {

    animControls = new LinkedList<AnimControl>();

    }

    if (animChannels == null) {

    animChannels = new LinkedList<AnimChannel>();

    }

    animControls.add(control);

    animChannels.add(control.createChannel());

    }

    };

    spatial.depthFirstTraversal(visitor);

    }



    @Override

    protected void controlRender(RenderManager rm, ViewPort vp) {

    }



    public Control cloneForSpatial(Spatial spatial) {

    throw new UnsupportedOperationException(“Not supported”);

    }

    }

    [/java]
6 Likes

I’m using Artemis. There is on reason why you should need to create another ES with Artemis. I have made 1 change on the source, and the rest was just adding systems and components.



I agree with every @normen 's advices. Just remember that Systems can change Component’s data, so if you have a primitive type, you have no other option unless put a setter to it. Like, if you have a position component, you can have three floats x y z, and you would need a setter for them. Unless you encapsulate within an object, like Vector3f, but in some cases you may prefer not to do so. The same is applied for rule 6, where sometimes you want to put component lists.

@normen said:
Settable in the Component, already failed to make it properly ;)
Ok I guess its time to put this here at least once, we compiled this list of "entity system rules" in the core team some time.

1) Entities are only an id value
- Ideally entities are just a "long" id, a class may exist that allows simple access to a specific entities components, e.g. new Entity(1000).getComponent(MyComponent.class) would grab the MyComponent from entity 1000.

2) Components are *only* data
- Components only contain data, and absolutely no code, not even helper methods. Components are best implemented as classes with fields and *only* getters and *one* constructor that sets all values. If this becomes cumbersome, you have an indicator that you should either separate the component data into multiple components or even entities. Each time you need data you get a component from an entity and get an immutable copy. To change the component, you create a new component that you set on the entity.

... more to come I guess..


Nice list, but i have some questions about the quoted above:
Why is a setter in a component so dangerous? I am thinking of components such as position and movement for example. For moving entities updating the position on each loop might be required, would you prefer overwriting the existing positionComponent instead of changing the coordinates in the existing positionComponent?

Regarding #1: My Entities are deifned as:
[java]
public class Entity {
String uuid;
Map<Class,EntityComponent> components=new ConcurrentHashMap<Class,EntityComponent>();
}
[/java]
You are saying that Entities should contain only the uuid, basically i should intoduce a componentmapper which manages all components for all entities instead of accessing the components trough the entity object?
[java]
ComponentMapper.getComponent(Component.class,EntityID);
instead of
EntityManager.getEntity(EntityID).getComponent(Component.class);
[/java]

Just one addition to @normen 's advice:



Entities are just a unique identifier. What you use as identifier is up to you. It can be a long. But, Java already has a built in unique identifier, its called UUID.

The benefit of that is, that it is immutable. So you don’t have to worry about yor entity id being manipulated when passing it around. And, with 128bit, it is twice as long as long.



So i really think using UUIDs is good for an entity system.



The other thing you should consider is NOT using bit fields. It’s just ugly. I remember even a google tech talk about not using bit fields. Maybe i can link it later. You could simply use a HashMap to store which entity has which component, that will give you O(1) lookup time.

However, your entity manager should be as generic as possible, so that you can use different implementations under the hood (maybe later you want to use an SQL layer, and look that up from SQL).

@zzuegg said:
Nice list, but i have some questions about the quoted above:
Why is a setter in a component so dangerous? I am thinking of components such as position and movement for example. For moving entities updating the position on each loop might be required, would you prefer overwriting the existing positionComponent instead of changing the coordinates in the existing positionComponent?

You are saying that Entities should contain only the uuid, basically i should intoduce a componentmapper which manages all components for all entities instead of accessing the components trough the entity object?

a) Because you break the easy to get free thread safety (updated the post above) its better to do it by replacing the object.
b) Updated the post above with examples. You can have entity objects but they shouldn't store any data locally, also because of the things mentioned in a).

Take a look at Artemis: The Artemis Framework: Entity and Component Game Design | Gamadu

It’s all done. You can then create your Systems extending Artemis’s Component and implement jME Control. Perfect solution =]

@shirkit said:
Take a look at Artemis: http://gamadu.com/artemis/
It's all done. You can then create your Systems extending Artemis's Component and implement jME Control. Perfect solution =]


Thank you, i know about artemis... However, for me this is an educational project.

Some links on entity systems I found usefull:

Entity Systems are the future of MMOG development – Part 1 – T-machine.org

and

Entity System 1: Java/Android – T-machine.org

and

http://entity-systems.wikidot.com/

and

Entity System: RDBMS-Beta (a new example with source) – T-machine.org