• entries
10
51
• views
41819

C++ Port of Artemis Entity Component System Framework (In Progress)

Followers 0

17216 views

This month is officially "wrap my head around Entity/Component Systems" month. I stepped away from coding for a bit and spent time researching different designs for handling game objects. Most interestingly, my search brought me to the Artemis Entity System Framework. Written in Java, the system is the best overall design I have seen so far (that's not saying much)--even if it is written in Java...just kidding. At any rate, I really liked what I saw so I used it as a reference for my system written in C++. In a way, it's really more like a port, without the reliance on RTTI (or whatever that class identification business is in Java). The architecture is virtually the same, but I took the liberty of writing things a bit differently to reflect the language differences.

Initially I considered using an std::map to relate entities to their component lists, but I really liked that they used a vector in Artemis to store all the entities. An O(1) lookup time is much better than O(log n) when you're dealing with several lookups per entity per frame. Like in Artemis, I opted to use a separate vector with removed entity indices. Whenever an entity is added, the system checks that vector first to see if there is a hole in the main vector. This reduces fragmentation and covers the case where the user removes several entities before adding one.

Unlike Artemis, I used an event system to decouple the entity manager and entity systems. The entity manager fires an event whenever a component is added, allowing the entity systems to check that the entity still has all of the required components. I figured that my game will use an event manager, so it made sense to integrate one now. Also, other subsystems could be notified whenever an entity is created/destroyed or a component is inserted/removed from a specific entity. The entity systems themselves contain a std::set< Entity* > data structure that holds all of the active entities. When it receives one of the previously mentioned events, it can check the entity to see if it should be added to or removed from the set.

Here's what I have so far in my main.cpp test program:

// Create the event manager and entity managerevtMgr = new SGF::EventManager();entMgr = new SGF::EntityManager( evtMgr );test = new SGF::TestSystem(evtMgr, entMgr);// Create an entitySGF::Entity *e = entMgr->CreateEntity();SGF::Transform *trans = new SGF::Transform();entMgr->InsertComponent(e, trans);while( !wnd.HasQuit() ){ wnd.PumpMessages(); ID3D11DeviceContext *dc = graphics->GetImmDeviceContext(); IDXGISwapChain *sc = wnd.GetSwapChain(); { wnd.SetAsRenderTarget(dc); dc->ClearDepthStencilView(wnd.GetDSView(), 0, 1.0f, 0); dc->ClearRenderTargetView(wnd.GetRTView(), D3DXCOLOR(0.0f,0.0f,1.0f,1.0f)); sc->Present(1, 0); } // This loops through any entities that match the bit flags for the component type. In this case, all it wants is a Transform component. test->Process();}entMgr->DestroyEntity(e);delete test;delete entMgr;delete evtMgr;

Here's the source for my TestSystem class:

// TestSystem.h ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////#ifndef __TEST_SYSTEM_H__#define __TEST_SYSTEM_H__#include "EntityProcessingSystem.h"namespace SGF{ class TestSystem : public EntityProcessingSystem { public: TestSystem( EventManager *eventManager, EntityManager *entityManager ); virtual ~TestSystem(); protected: static unsigned int TypeBits; virtual void ProcessEntity( EntityManager *manager, Entity *e ); };};#endif// TestSystem.cpp ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////#include "TestSystem.h"#include "EntityManager.h"#include "ComponentMapper.h"#include "Transform.h"#include using namespace SGF;unsigned int TestSystem::TypeBits = (1 << CT_TRANSFORM);TestSystem::TestSystem( EventManager *eventManager, EntityManager *entityManager ): EntityProcessingSystem(eventManager, entityManager, TypeBits){}TestSystem::~TestSystem() {}void TestSystem::ProcessEntity( EntityManager *manager, Entity *e ){ // Allows you to map the list as a specific component type. ComponentMapper tmap = manager->GetComponentList(e, CT_TRANSFORM); // We can index a specific component of a specific type. assert( 0 && tmap[0]->mat._11 );}

If you look closely at the last line, you'll notice that the component system supports multiple components per type. This is another difference from the Artemis framework, which only allows one per type. The reason I chose to diverge from their design in that respect is because I can think of at least a few key cases where it would be really nice to have multiple components per type. For instance, if I have an articulate vehicle entity, I might have the chassis connected to the wheels. With this system, I could have four joint components--each one connected to a wheel.

At any rate. If anyone is interested in the code, I'd be happy to provide it. It's essentially just a port of Artemis with a couple nuanced changes and additions.

EDIT: I got sidetracked and I haven't made much progress since this post. I decided I would just post what I have: http://cse.taylor.edu/~zbethel/ArtemisPortCpp.zip

If you have any questions, feel free to ask.

0
Followers 0

Are you using the bit-Identifier approach? if so, are you ok working within the 64 system/component-type limitation? Also, I'm interested in the speed of the event updates vs the cascading updates.
0

Here's what the EntitySystem event code looks like:

[CODE]
bool EntitySystem::HandleEvent( IEventData const &evt )
{
EventType type = evt.GetType();
switch( type )
{
case ET_INSERT_COMPONENT:
case ET_REMOVE_COMPONENT:
{
const EntityEvent &data = static_cast<const EntityEvent&>( evt );
Entity *e = data.e;

// Does this entity meet our requirements?
if( ( e->_typeBits & _typeBits ) == _typeBits )
_actives.insert(e);
else
_actives.erase(e);

break;
}
}

return false;
}
[/CODE]

Yes, it does use bit identifiers.
0

interesting. Though, would you be able to remove the bit-check entirely if you moved the bit check into the initialization phase and then just registered the listener to a static event thrower in the type at that point? That way systems would receive events only from the types they care about. Or would that not work / be too much overhead?
0

It could be done that way also. In my case, I don't foresee needing anywhere close to 64 component types, so I'm not worried about it. If I did happen to hit that restriction though, I could do something like what you mentioned.
0

If you don't want to have to worry about the 64 bit limitation in the future maybe you could use std::bitset and be done with it?
0

I could do that, however I am trying to curb my instinct to do the more complex solution when a simple one makes sense for my application. If I know that I won't go over 64 components, why bother using a data structure that is more flexible, but will slow things down? Just a thought. If I were doing a general port that I planned on releasing as a "do it all" system, you're right, a bitset might be the best solution. Thanks for the suggestion!
0

It is just day before yesterday I found Artemis Framework and it is yesterday I have decided to port it into C++. I have not started yet. It would be very nice if you could share your code.
Thank you!
0

I'd really like to use this in cocos2d-x to make a game for iOS but it looks like its dependent on DirectX. Do you have an implementation that doesn't have any dependencies?
0

Hi Zach,

I've recently been playing with my own entity component system and came up against the issue of multiple components of the same type per entity - I've ended up getting around it by having different entities (in your case, for wheels) where each wheel would have an AttachmentPoint with a relative position and a reference to the 'car' entity id. That way, a separate system can handle everything with AttachmentPoint and translate accordingly. What are your thoughts about this approach?
0

Hey, sorry for not responding earlier. I haven't checked my blog in a little while.

I'm sure you could work with that approach. I've thought extensively about going that route. The use case that keeps bothering me though is that of an articulate physics object. If you have a ragdoll dozens of joints and body parts, are you going to represent that as dozens of individual entities all referencing each other? Although you could do it that way, you're going to need a higher level 'super entity' to manage all the dependencies. I like to think about entities as full prefab objects that I would add to a game editor. Take for instance, the Forge editor in Halo. Your character is an entity, a warthog is an entity, etc. The warthog, for instance, has four wheels and a turret that I imagine are governed by their own unique transform and mesh components. If you represent all of those as entities, you suddenly have an explosion of entities in your game editor. You would need a higher level entity to manage those.

The nice thing about handling interconnected parts of a game object as components rather than sub-entities is that you can kill the entire object with the entity. Like, when the warthog blows up, you might break the constraints holding the wheels onto the vehicle, and then let the wheels bounce around. They're still part of the same 'entity' (the warthog), and when the warthog disappears after a certain amount of time, the wheels magically disappear with it.

Also, I lean more towards giving each component a direct pointer to it's dependencies. For instance, your model component needs transform component to orient it. That transform component could be controlled by the RigidBody component. So essentially, your model just needs a constant reference to the transform, while the rigid body needs a writable pointer.

There is no perfect way to handle this problem. If you've figured out ways to solve these problems with your method I'd love to hear about it!
0

Create an account

Register a new account

• 10
• 12
• 21
• 11
• 28