Re: Is there a design pattern for holding the same pointers in many arrays?
On 07/11/10 06:49, Hakusa@gmail.com wrote:
Is there a design pattern for holding the same pointers in many
arrays? The obvious answer is to use a smart pointer, but i am and
there's more to it.
I'm making a real-time graphical application (video game) and i want a
list of every graphical object, a list of every circular object,
square object, every physical object, etc. Many applications even have
so-called buckets, which ensure that certain objects are updated
before others.
Every time a new list is added, code complexity grows. If i want to
create an object of type X, i currently have a function similar to:
template< typename T >
T* spawn() {
T* x = new T;
graphicalObjects.push_back( std::shared_ptr<T>(t) );
return x;
}
and it works fine, assuming there is only one list. If i have many
lists, how can this spawn function figure out what lists to put it in?
I suppose many would answer that it depends on how class X is defined,
but that is liable to change based on the solution to this problem.
The only restraint is that i have a base class, B, and everything
inherits from that.
The other question is how to handle removing an object from each list
it's in.
Currently, my one-list solution for shape-detection is to have a pure
virtual function, B::shape_data(), that is defined by a parent of X
like SquareData or CircleData, though this is not a parent of B. i.e.:
struct Shape {};
struct CircleData : Shape { float radius; };
struct B { virtual Shape& shape_data() };
struct X : B, CircleData {
X() { radius = SOME_CONST; }
Shape& shape_data() { return *this; }
};
Obviously an inheritence v composition argument can be made here, but
i don't like either. If you're familiar with the double-dispatch
pattern, perhaps it will make more sense why Shape cannot be a part of
B. (B would have to know about every child. It's easier for Shape to
know about every one of its children than for B to know of its.) Of
course, this is only a solution for shape detection, not the buckets
or any other sort of feature detection. And having multiple lists
would make this unnecessary (as there'd be no double dispatch).
In conclusion, is there a design pattern for this? Or is there a way
to do what i'm already doing, but better? I thought i could make an
object that stores a way of identifying an object in every list, but
if i can't do that in a functional C way, how can i in an OOP C++ way?
PS: The specific application is open-source, so a link to the source
can be supplied if needed. However, the source does not demonstrate
the problem, only an extensive work-a-round. I apologize if i could
have explained the problem with more brevity.
Your problem is a typical problem in game programming. One has many
"entities" that have different properties: some of them can be drawn in
the screen, some of them interact with the phisics engine, some of them
interact with the player, etc... and one would like to have a common
interface for all those heterogenous entities as much as possible.
Most of this properties are orthogonal and can be easily composed. The
problem here is that when using composition the writer of a concrete
entity has to expose the interface of its sub-parts by hand. For
example, one would have to write:
template< typename T >
T* spawn ();
template<>
ConcreteEntity* spawn ()
{
ConcreteEntity* x = new T;
graphicalObjects.push_back( std::shared_ptr<T>(t->shape ()) );
physicalObjects.push_back( std::shared_ptr<T>(t->physics ()) );
return x;
}
For this game [1] (Free Software, you can check the source code) I used
a different approach based on mix-ins. In that approach, the interface
*and* its implementation is /extended/ by the sub-parts of an entity
automatically. Sadly, that is only possible in languages supporting
proper multiple inheritance -- and by proper, I mean with a mechanism
for linearizing the inheritance tree to be able to call "super"
preserving dependencies. That is possible in Python [2], like in the
example application, or some Lisp dialects.
There are some limitations in static typing that make such multiple
inheritance approach hard to implement in a language like C++. However,
I think that by adding some constraints it can be possible --it is one
of my long term projects that I never find time to work on to implement
such mechanism using template metaprogramming. I suspect that many of
the issues with "virtual inheritance" in C++ could be solved by taking
that into account.
Back to your original question, I propose the following approach, though
not perfect:
a) Separate interface from implementation. Interfaces have an empty
implementation until the lowest classes in the hierarchy -- concrete
entities. They can be easily combined via virtual inheritance, etc..
For example, you could have a SpatialEntity (that adds set_position,
get_position), a DynamicEntity : SpatialEntity that adds (set_speed,
get_speed), a GraphicalEntity : SpatialEntity that adds (draw) and so
on. Then you have the implementation in different objects that you
integrate in the interface manually. For example:
class Bullet : public virtual GraphicalEntity
, public virtual DynamicEntity
, public enable_shared_from_this <Bullet>
{
public:
void set_position (vector3 pos) {
_phis->setPos (pos); // Maybe you have to adapt pos it.
_ogre->setPosition (pos);
}
void register (EntityManager* e) {
e->register_phisical (shared_from_this ());
e->register_graphical (shared_from_this ());
}
private:
OdePhisics* _phis;
OgreNode* _ogre;
// You can also try to make constructors and the register function
// private and friend to EntityManager to ensure that all the
// entities are properly registered.
};
class EntityManager
{
// Note that if lifetime of entities is controlled by EntityManager
// you can think of getting rid of shared_ptr. You can also get rid
// of the spawn method and pass the EntityManager to the constructor
// of all concrete entities...
template<>
shared_ptr<T> spawn ()
{
shared_ptr<T> ent = make_shared<T> ();
ent->register (this);
return ent;
}
}
This is only a proposal. As you can see, this works well when part of
the implementation of your entity compononents is defined by third party
libraries. In other cases you can add more behaviour to the interfaces,
etc...
I hope this helped a bit,
JP
PS: I would be interested in reading your source code :D
[1] https://savannah.nongnu.org/projects/pigeoncide/
[2] http://www.python.org/download/releases/2.3/mro/
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]