Dynamic Component Registration

General discussion pertaining to Elysian Shadows, ESTk, ESGamma, and the Adventures in Game Development series.

Moderators: News Mods, Elysian Shadows Team

Dynamic Component Registration

Postby Falco Girgis on Mon Apr 08, 2013 11:39 am

"So what happens if the Elysian Shadows Kickstarter fails?"
Image
User avatar
Falco Girgis
Elysian Shadows Team
Elysian Shadows Team
 
Posts: 10693
Joined: Thu May 20, 2004 3:04 pm
Location: Studio Vorbis, AL

Re: Dynamic Component Registration

Postby dandymcgee on Mon Apr 08, 2013 5:18 pm

Components are always interesting, this is a good brief intro article.
Falco Girgis wrote:It is imperative that I can broadcast my narcissistic commit strings to the Twitter! Tweet Tweet, bitches! :twisted:
User avatar
dandymcgee
ES Beta Backer
ES Beta Backer
 
Posts: 4911
Joined: Tue Apr 29, 2008 4:24 pm
Location: New Hampshire

Re: Dynamic Component Registration

Postby lelandbdean on Tue Apr 09, 2013 11:20 am

I needed this exact functionality in every single C++ game I've ever written (and ended up being pissed about after using the switch implementation or something equally hideous). This is beautiful. Absolutely freaking beautiful.

I've struggled since I started programming on how to make saving and loading components generic in C++. In Flash I can cheat and read a string as a type name or do something else magical and slow as Hell, but a C++ solution has been the most elusive fucking thing ever for me. I guess I should really read some more C/++ books! :x :worship:
lelandbdean
Chaos Rift Newbie
Chaos Rift Newbie
 
Posts: 19
Joined: Thu Mar 28, 2013 11:37 pm

Re: Dynamic Component Registration

Postby Falco Girgis on Tue Apr 09, 2013 11:40 am

Glad this could be of use to you!

This post is quite old now (I had only just discovered the power of static constructors and allocator abstractions). A few years into the future, we are taking a hybrid approach to this.

The components that are built into our engine are being statically registered, as their types are clearly known at compile-time, and this run-time polymorphic approach is kind of overkill. It also makes component access cleaner to access a specific component like "Entity::getCollider()" rather than having to say "Entity::getComponent("Collider") then having to static-cast back to the correct type, because you were returned a generic component type.

The dynamic registration method REALLY shines for user-registered components extending our engine, whose types are not known at compile-time. We are still using this approach for these scenarios.
"So what happens if the Elysian Shadows Kickstarter fails?"
Image
User avatar
Falco Girgis
Elysian Shadows Team
Elysian Shadows Team
 
Posts: 10693
Joined: Thu May 20, 2004 3:04 pm
Location: Studio Vorbis, AL

Re: Dynamic Component Registration

Postby short on Wed May 08, 2013 6:25 am

Hi Falco :),

The components that are built into our engine are being statically registered, as their types are clearly known at compile-time, and this run-time polymorphic approach is kind of overkill. It also makes component access cleaner to access a specific component like "Entity::getCollider()" rather than having to say "Entity::getComponent("Collider") then having to static-cast back to the correct type, because you were returned a generic component type.


Would you mind elaborating on the static approach? I'm currently writing a get_component(...) method for my entity class and I'm trying to decide how I want to do it.

What I have come up with seems similar to what you have outlined, but I'm curious what other pitfalls/triumphs you've braved that you may see with what I got:

entity_helpers.hpp
Code: Select all
#ifndef _ENTITY_HELPERS_HPP_
#define _ENTITY_HELPERS_HPP_
namespace rem
{
  // forward declarations
  class entity;
  struct input_component;
  struct movement_component;
  struct sprite_component;

namespace entity_helpers
{
  // function declarations
  input_component*    get_input_component(entity *const entity_ptr);
  movement_component* get_movement_component(entity *const entity_ptr);
  sprite_component*   get_sprite_component(entity *const entity_ptr);

  // templated function declarations
  template<typename T>
  T* get_component(entity *const entity_ptr);
}
}
#include "entity_helpers.inc"
#endif


entity_helpers.inc
Code: Select all
inline rem::input_component*
rem::entity_helpers::get_input_component(entity *const entity_ptr)
{
  return get_component<input_component>(entity_ptr);
}

inline rem::movement_component*
rem::entity_helpers::get_movement_component(entity *const entity_ptr)
{
  return get_component<movement_component>(entity_ptr);
}

inline rem::sprite_component*
rem::entity_helpers::get_sprite_component(entity *const entity_ptr)
{
  return get_component<sprite_component>(entity_ptr);
}

template<typename T>
T*
rem::entity_helpers::get_component(rem::entity *const entity_ptr)
{
  T *component_ptr = nullptr;

  const auto find_predicate = [=](const icomponent *const component_ptr) { return component_ptr->Component_Type== T::COMPONENT_TYPE; };
  const auto &components = entity_ptr->_components; // convenient alias

  const auto it = std::find_if(components.cbegin(), components.cend(), find_predicate);

  if(it != components.cend()) {
    component_ptr = static_cast<T*>(*it); // we found the component
  }
  return component_ptr;
}


Code: Select all
class entity
{
  // friend declarations
  template<typename T>
  friend T* entity_helpers::get_component(entity *const entity_ptr);

  // members
  std::vector<icomponent*> _components;

  // etc...
};


Each "static/compile time" component looks similar to this:

component_registry.hpp
Code: Select all
#ifndef _COMPONENT_REGISTRY_HPP_
#define _COMPONENT_REGISTRY_HPP_

namespace rem
{
  enum COMPONENT_REGISTRY_TYPE {
    COMPONENT_TYPE_INPUT = 0,
    COMPONENT_TYPE_MOVEMENT,
    COMPONENT_TYPE_SPRITE,
  };
}

#endif


input_component.hpp
Code: Select all
struct input_component :
    public icomponent
  {
    // static members
    static const auto COMPONENT_TYPE = COMPONENT_TYPE_INPUT;

    // constructors
    input_component(void) : icomponent(COMPONENT_TYPE) { }
  };


Each component defines a static/const integer COMPONENT_TYPE that get_component() uses. I think I have something similar to what you guys are doing / going for.

Every time I define a new "static/compile time" component I have to add a value to the component_registry enumeration, and define a static const integer in the component class equal to the enum so the get_component() function will work.

If I understood the article correctly, for every "static/compile time" component you define there is a method like "getCollider() you also define. Am I understanding your definition of "statically registered"?

(It's super late in the morning, I apologize if anything doesn't make logical sense :lol: )
My github repository contains the project I am currently working on,
link: https://github.com/bjadamson
User avatar
short
ES Beta Backer
ES Beta Backer
 
Posts: 569
Joined: Thu Apr 30, 2009 3:22 am
Location: Oregon, US

Re: Dynamic Component Registration

Postby Falco Girgis on Wed May 08, 2013 3:21 pm

Yeah, that is more or less how we're handling static components. A few things to note...

Faster Component Retrieval
By the looks of those components, the entity can only have one of each type attached at once, correct? If that is the case, you can give each entity a static array of component pointers and use the enumeration from each component type to lookup into this table, rather than iterating through a vector. That reduces your access time complexity from O(n) to O(1). I'm sure these will be accessed a shitload too, so that will probably do some good. You can also use your template argument in the get_component to static_cast<> back to the derived class before returning the component, so that will be pretty cute. I might even steal that. ;)

Stylistic Bitching
You have lots of namespaces and embedded namespaces... Are these really serving any organizational purpose? Is there even a chance of name conflict among symbols from entities, components, and their helpers? In my experience, having a shitload of different namespaces, and especially embedded namespaces, becomes a goddamn nightmare pretty quickly. But this is completely stylistic.

I notice this code is also fairly un-object oriented. I have read many articles on the entity/component design that suggest completely decoupling your data from your methods in an almost C-manner as you have done. They treat properties like a "database" that is accessed by other functions. That's cute and all, but in my experience, it only makes your code ugly as shit (and in many cases slower). It does not seem to work well in a game engine environment. It would be a whole lot simpler to say Entity.getComponent() than it is to say get_input_component(entity). That even looks like C code.

What happens when your components actually do need to encapsulate data? When there is internal data that is calculated from user-modifiable fields, but should never be modified directly? Or what happens when you just want a nice user-facing accessor method to do something? SpriteComponent::setColor(r, g, b, a) as opposed to setting every property individually? I recommend saying fuck the entity/component "purist" method and go for a much more down-to-earth, realistic approach using a traditional OO paradigm.
"So what happens if the Elysian Shadows Kickstarter fails?"
Image
User avatar
Falco Girgis
Elysian Shadows Team
Elysian Shadows Team
 
Posts: 10693
Joined: Thu May 20, 2004 3:04 pm
Location: Studio Vorbis, AL

Re: Dynamic Component Registration

Postby short on Wed May 08, 2013 5:08 pm

Before I respond to anything "stylistic" I'm confused by what you meant:

You can also use your template argument in the get_component to static_cast<> back to the derived class before returning the component, so that will be pretty cute. I might even steal that.


I think I wrote it that way, the static_cast<> is already there. That is unless I am completely misunderstanding what you meant :lol:

Code: Select all
//===----------------------------------------------------------------------===//
//
// Searches the entity's components for the component of type T. Returns a pointer
// to the component or a nullptr.
//
//===----------------------------------------------------------------------===//
template<typename T>
T*
rem::entity_helpers::get_component(rem::entity *const entity_ptr)
{
   T *component_ptr = nullptr;

   if(entity_ptr == nullptr) { // a null entity has no components
     return component_ptr;
   }

  const auto find_predicate = [=](const icomponent *const component_ptr) { return component_ptr->Component_Type == T::COMPONENT_TYPE; };
  const auto &components = entity_ptr->_components; // convenient alias

  const auto it = std::find_if(components.cbegin(), components.cend(), find_predicate);

  if(it != components.cend()) {
    component_ptr = static_cast<T*>(*it); // we found the component
  }
  return component_ptr;
}


I'll definitely post back the O(1) solution you outlined. That's going to totally help :)
My github repository contains the project I am currently working on,
link: https://github.com/bjadamson
User avatar
short
ES Beta Backer
ES Beta Backer
 
Posts: 569
Joined: Thu Apr 30, 2009 3:22 am
Location: Oregon, US

Re: Dynamic Component Registration

Postby Falco Girgis on Wed May 08, 2013 5:30 pm

short wrote:Before I respond to anything "stylistic" I'm confused by what you meant:

You can also use your template argument in the get_component to static_cast<> back to the derived class before returning the component, so that will be pretty cute. I might even steal that.


I think I wrote it that way, the static_cast<> is already there. That is unless I am completely misunderstanding what you meant :lol:
Doh. You're right.
"So what happens if the Elysian Shadows Kickstarter fails?"
Image
User avatar
Falco Girgis
Elysian Shadows Team
Elysian Shadows Team
 
Posts: 10693
Joined: Thu May 20, 2004 3:04 pm
Location: Studio Vorbis, AL


Return to Elysian Shadows Discussion

Who is online

Users browsing this forum: No registered users and 1 guest

cron