+++ to secure your transactions use the Bitcoin Mixer Service +++

 

Test For Parallel Processing Of Components

I've written a small test application to test the idea of entity systems in combination with parallel processing of entities. As an example I've selected a simple nbody simulation using a brute force approach.

Implementation

Component: base class for a component; empty struct
Entity: keeps a list of all components that belong to this entity. An Entity has NO id, the address of the entity object in the main memory is used instead.
EntitySystem: keeps a list of all entities with a specific component

Implementation Details

typedef int FamilyId;
struct EntitySystem; 

struct Component {
};

struct Entity {
   static EntitySystem *entitySystem;
   Entity();
   template<typename Type> Type *getAs();
   std::map<FamilyId, Component*> mComponents;
};
EntitySystem *Entity::entitySystem = 0;
struct EntitySystem {
   EntitySystem() {
      Entity::entitySystem = this;
   }
   template<typename T> T *getComponent(Entity *e) {
      return (T*)e->mComponents[T::familyId];
   }
   template<typename T> void getEntities(std::vector<Entity*> &result) {
      auto iterPair = mComponentStore.equal_range(T::familyId);
      for(auto iter = iterPair.first; iter != iterPair.second; ++iter) {
         result.push_back(iter->second);
      }
   }
   template<typename T> void addComponent(Entity *e, T* comp) {
      mComponentStore.insert(std::pair<FamilyId, Entity*>(T::familyId, e));
      e->mComponents.insert(std::pair<FamilyId, Component*>(T::familyId, comp));
   }
protected:
   std::multimap<FamilyId, Entity*> mComponentStore;
};

Entity::Entity() {
}

template<typename Type> Type *Entity::getAs() {
   return entitySystem->getComponent<Type>(this);
}

Reasoning for this Design

When you work with an entity often you need more than one component of an entity, with the components stored in the entity directly you have only one level of indirection to get a pointer to the component. And to get a list of entities with a specific component you can query the entity system.
Also it's much faster to keep the components in the entities, otherwise you have to query every time the entity system with your entity and the needed component and this is getting really slow (3.8s needed if components are in the entity system vs 0.92s if components are referenced in the entities), look at this webpage for the description of the benchmark.

Parallel Processing

This is nothing special with this type of design. In the example at the frame start I'm building a vector of all entities with a specific familyId and split the processing over the available cores (via nulstein or better intel threading building blocks). It's as straightforward as it sound. You only have to be sure to write to a component only from one thread, so be careful how your access patterns are spread over the cores. In my example only the temporary positions are written, and only one time in one parallel loop (in the nbody system you have to use during one simulation step the original position and not a mix of new and old positions!). Ofcourse it would be faster to toggle the position components and only update an index which is the old position and the new one, but for simplicity I copy the temporary position to the normal position component.

      std::vector<Entity*> entitiesWithPosition;
      entitySystem.getEntities<CompPosition3D>(entitiesWithPosition); 

      NBodyIntegrationStep integrationStep(entitiesWithPosition, (float)0.001);
      bOk = ParallelFor(&integrationStep, ParallelRange(0, entitiesWithPosition.size(), 20));
      //the integration step uses the following components: Position3D, TemporaryPosition3D and Velocity

      NBodyCopyTempPos copyTempPos(entitiesWithPosition);
      bOk = ParallelFor(&copyTempPos, ParallelRange(0, entitiesWithPosition.size(), 20));
      //following components are used: TemporaryPosition3D and Position3D

      Project3Dto2D project3Dto2D(entitiesWithPosition);
      bOk = ParallelFor(&project3Dto2D, ParallelRange(0, entitiesWithPosition.size(), 20));
      //the following components are used: Position3D and Position2D

Improvements

The entity system could keep a list of entities with a specific family id, in our example it would be much faster to insert a vector with pointers to entities with a position3d component into the entity system instead of generating this list every frame.

Source Code

C++

The complete nbody sample with sources and binary(release build) can be found here.
The example program uses nulstein and pixeltoaster.