Related
This was inspired by a comment to my other question here:
How do you "not repeat yourself" when giving a class an accessible "name" in C++?
nvoight: "RTTI is bad because it's a hint you are not doing good OOP. Doing your own homebrew RTTI does not make it better OOP, it just means you are reinventing the wheel on top of bad OOP."
So what is the "good OOP" solution here? The problem is this. The program is in C++, so there are also C++ specific details mentioned below. I have a "component" class (actually, a struct), which is subclassed into a number of different derived classes containing different kinds of component data. It's part of an "entity component system" design for a game. I'm wondering about the storage of the components. In particular, the current storage system has:
a "component manager" which stores an array, actually a hash map, of a single type of component. The hash map allows for lookup of a component by the entity ID of the entity it belongs to. This component manager is a template which inherits from a base, and the template parameter is the type of component to manage.
a full storage pack which is a collection of these component managers, implemented as an array of pointers to the component manager base class. This has methods to insert and extract an entity (on insertion, the components are taken out and put into the managers, on removal, they are extracted and collected into a new entity object), as well as ones to add new component managers, so if we want to add a new component type to the game, all we have to do is put another command to insert a component manager for it.
It's the full storage pack that prompted this. In particular, it offers no way of accessing a particular type of component. All the components are stored as base class pointers with no type information. What I thought of was using some kind of RTTI and storing the component managers in a map which maps type names and thus allows for lookup and then the proper downcasting of the base class pointer to the appropriate derived class (the user would call a template member on the entity storage pool to do this).
But if this RTTI means bad OOP, what would be the correct way to design this system so no RTTI is required?
Disclaimer/resources: my BCS thesis was about the design and implementation of a C++14 library for compile-time Entity-Component-System pattern generation. You can find the library here on GitHub.
This answer is meant to give you a broad overview of some techniques/ideas you can apply to implement the Entity-Component-System pattern depending on whether or not component/system types are known at compile-time.
If you want to see implementation details, I suggest you to check out my library (linked above) for an entirely compile-time based approach. diana is a very nice C library that can give you an idea of a run-time based approach.
You have several approaches, depending on the scope/scale of your project and on the nature of your entities/components/systems.
All component types and system types are known at compile-time.
This is the case analyzed in my BCS thesis - what you can do is use advanced metaprogramming techniques (e.g. using Boost.Hana) to put all component types and system types in compile-time lists and create data structures that link everything together at compile time. Pseudocode example:
namespace c
{
struct position { vec2f _v };
struct velocity { vec2f _v };
struct acceleration { vec2f _v };
struct render { sprite _s; };
}
constexpr auto component_types = type_list
{
component_type<c::position>,
component_type<c::velocity>,
component_type<c::acceleration>,
component_type<c::render>
};
After defining your components, you can define your systems and tell them "what components to use":
namespace s
{
struct movement
{
template <typename TData>
void process(TData& data, float ft)
{
data.for_entities([&](auto eid)
{
auto& p = data.get(eid, component_type<c::position>)._v;
auto& v = data.get(eid, component_type<c::velocity>)._v;
auto& a = data.get(eid, component_type<c::acceleration>)._v;
v += a * ft;
p += v * ft;
});
}
};
struct render
{
template <typename TData>
void process(TData& data)
{
data.for_entities([&](auto eid)
{
auto& p = data.get(eid, component_type<c::position>)._v;
auto& s = data.get(eid, component_type<c::render>)._s;
s.set_position(p);
some_context::draw(s);
});
}
};
}
constexpr auto system_types = type_list
{
system_type<s::movement,
uses
(
component_type<c::position>,
component_type<c::velocity>,
component_type<c::acceleration>
)>,
system_type<s::render,
uses
(
component_type<c::render>
)>
};
All that's left is using some sort of context object and lambda overloading to visit the systems and call their processing methods:
ctx.visit_systems(
[ft](auto& data, s::movement& s)
{
s.process(data, ft);
},
[](auto& data, s::render& s)
{
s.process(data);
});
You can use all the compile-time knowledge to generate appropriate data structures for components and systems inside the context object.
This is the approach I used in my thesis and library - I talked about it at C++Now 2016: "Implementation of a multithreaded compile-time ECS in C++14".
All component types and systems types are known at run-time.
This is a completely different situation - you need to use some sort of type-erasure technique to dynamically deal with components and systems. A suitable solution is using a scripting language such as LUA to deal with system logic and/or component structure (a more efficient simple component definition language can also be handwritten, so that it maps one-to-one to C++ types or to your engine's types).
You need some sort of context object where you can register component types and system types at run-time. I suggest either using unique incrementing IDs or some sort of UUIDs to identify component/system types. After mapping system logic and component structures to IDs, you can pass those around in your ECS implementation to retrieve data and process entities. You can store component data in generic resizable buffers (or associative maps, for big containers) that can be modified at run-time thanks to component structure knowledge - here's an example of what I mean:
auto c_position_id = ctx.register_component_type("./c_position.txt");
// ...
auto context::register_component_type(const std::string& path)
{
auto& storage = this->component_storage.create_buffer();
auto file_contents = get_contents_from_path(path);
for_parsed_lines_in(file_contents, [&](auto line)
{
if(line.type == "int")
{
storage.append_data_definition(sizeof(int));
}
else if(line.type == "float")
{
storage.append_data_definition(sizeof(float));
}
});
return next_unique_component_type_id++;
}
Some component types and system types are known at compile-time, others are known at run-time.
Use approach (1), and create some sort of "bridge" component and system types that implements any type-erasure technique in order to access component structure or system logic at run-time. An std::map<runtime_system_id, std::function<...>> can work for run-time system logic processing. An std::unique_ptr<runtime_component_data> or an std::aligned_storage_t<some_reasonable_size> can work for run-time component structure.
To answer your question:
But if this RTTI means bad OOP, what would be the correct way to design this system so no RTTI is required?
You need a way of mapping types to values that you can use at run-time: RTTI is an appropriate way of doing that.
If you do not want to use RTTI and you still want to use polymorphic inheritance to define your component types, you need to implement a way to retrieve some sort of run-time type ID from a derived component type. Here's a primitive way of doing that:
namespace impl
{
auto get_next_type_id()
{
static std::size_t next_type_id{0};
return next_type_id++;
}
template <typename T>
struct type_id_storage
{
static const std::size_t id;
};
template <typename T>
const std::size_t type_id_storage<T>::id{get_next_type_id()};
}
template <typename T>
auto get_type_id()
{
return impl::type_id_storage<T>::id;
}
Explanation: get_next_type_id is a non-static function (shared between translation units) that stores a static incremental counter of type IDs. To retrieve the unique type ID that matches a specific component type you can call:
auto position_id = get_type_id<position_component>();
The get_type_id "public" function will retrieve the unique ID from the corresponding instantiation of impl::type_id_storage, that calls get_next_type_id() on construction, which in turn returns its current next_type_id counter value and increments it for the next type.
Particular care for this kind of approach needs to be taken to make sure it behaves correctly over multiple translation units and to avoid race conditions (in case your ECS is multithreaded). (More info here.)
Now, to solve your issue:
It's the full storage pack that prompted this. In particular, it offers no way of accessing a particular type of component.
// Executes `f` on every component of type `T`.
template <typename T, typename TF>
void storage_pack::for_components(TF&& f)
{
auto& data = this->_component_map[get_type_id<T>()];
for(component_base* cb : data)
{
f(static_cast<T&>(*cb));
}
}
You can see this pattern in use in my old and abandoned SSVEntitySystem library. You can see an RTTI-based approach in my old and outdated “Implementation of a component-based entity system in modern C++” CppCon 2015 talk.
Despite the good and long answer by #VittorioRomeo, I'd like to show another possible approach to the problem.
Basic concepts involved here are type erasure and double dispatching.
The one below is a minimal, working example:
#include <map>
#include <vector>
#include <cstddef>
#include <iostream>
#include <memory>
struct base_component {
static std::size_t next() noexcept {
static std::size_t v = 0;
return v++;
}
};
template<typename D>
struct component: base_component {
static std::size_t type() noexcept {
static const std::size_t t = base_component::next();
return t;
}
};
struct component_x: component<component_x> { };
struct component_y: component<component_y> { };
struct systems {
void elaborate(std::size_t id, component_x &) { std::cout << id << ": x" << std::endl; }
void elaborate(std::size_t id, component_y &) { std::cout << id << ": y" << std::endl; }
};
template<typename C>
struct component_manager {
std::map<std::size_t, C> id_component;
};
struct pack {
struct base_handler {
virtual void accept(systems *) = 0;
};
template<typename C>
struct handler: base_handler {
void accept(systems *s) {
for(auto &&el: manager.id_component) s->elaborate(el.first, el.second);
}
component_manager<C> manager;
};
template<typename C>
void add(std::size_t id) {
if(handlers.find(C::type()) == handlers.cend()) {
handlers[C::type()] = std::make_unique<handler<C>>();
}
handler<C> &h = static_cast<handler<C>&>(*handlers[C::type()].get());
h.manager.id_component[id] = C{};
}
template<typename C>
void walk(systems *s) {
if(handlers.find(C::type()) != handlers.cend()) {
handlers[C::type()]->accept(s);
}
}
private:
std::map<std::size_t, std::unique_ptr<base_handler>> handlers;
};
int main() {
pack coll;
coll.add<component_x>(1);
coll.add<component_y>(1);
coll.add<component_x>(2);
systems sys;
coll.walk<component_x>(&sys);
coll.walk<component_y>(&sys);
}
I tried to be true to the few points mentioned by the OP, so as to provide a solution that fits the real problem.
Let me know with a comment if the example is clear enough for itself or if a few more details are required to fully explain how and why it works actually.
If I understand correctly, you want a collection, such as a map, where the values are of different type, and you want to know what type is each value (so you can downcast it).
Now, a "good OOP" is a design which you don't need to downcast. You just call the mothods (which are common to the base class and the deriveratives) and the derived class performs a different operation than its parent for the same method.
If this is not the case, for example, where you need to use some other data from the child and thus you want to downcast, it means, in most cases, you didn't work hard enough on the design. I don't say it's always possible, but you need to design it in such a way the polymorphism is your only tool. That's a "good OOP".
Anyway, if you really need to downcast, you don't have to use RTTI. You can use a common field (string) in the base class, that marks the class type.
I just started learning C++ and I'm used to Java paradigms, so I'm not sure how this should be done:
I need to represent a vector of products of two different types: packaged and fresh food. They have some common fields with a single implementation (availability, re-stock quantity etc), but they have also different fields and functions with different return types.
I.E: fresh foods may have a boolean field needsRefrigeration, other
products may have an integer representing a category (food, cleaning,
bricolage, forniture...).
In Java I would create a Product object with the common fields and a PackagedProduct (extending Product) plus a FreshProduct (also extending Product) with their particular fields. Then I'd place every product in a vector and accessed as Product when I need the common fields, safely-casted (with instanceof) to the right class when I need to access the child's fields.
I know this is not the right way in C++ and I don't want to force java programming paradigms to C++.
I can imagine:
create all the functions required by all the cildren as virtual in the parent and add a field in the parent representing the type of the cild, so it can safely casted
create a wrapper object containing two different vectors, each one of the type of a child object and return the values in the correct order, eventually using a third int vector.
I think these solutions are really bad, and I'm almost sure there must be a better way, but I can't imagine it. Can you help me?
What's the right way to do this?
I need to represent a vector of products of two different types: packaged and fresh food.
Do you really need both types of product in the same vector? Can't you have two vectors?
std::vector<PackagedProduct> packaged;
std::vector<FreshProduct> fresh;
packaged.emplace_back(1, 2, 3);
fresh.emplace_back(4, 5, 6);
This will be by far the most efficient solution. (Less indirections keep the prefetcher happy.)
If you absolutely need both kinds of products in the same vector, you must use indirection:
std::vector<std::unique_ptr<Product>> products;
products.push_back(std::make_unique<PackagedProduct>(1, 2, 3));
products.push_back(std::make_unique<FreshProduct>(4, 5, 6));
Instead of checking the dynamic type at runtime and downcast, you should read up on virtual methods.
The basic idea is the same as in Java: Use inheritance to create a class hierarchy:
class Product { public: virtual ~Product(); ... };
class PackagedProduct : public Product { ... };
class FreshProduct : public Product { ... };
In Java, the vector (or list, container, ...) stores by-reference, not by-value. That's the crucial difference. In C++, this means using a smart pointer:
std::vector< std::shared_ptr< Product > > v;
v.push_back( std::make_shared< FreshProduct >( some args... ) );
You can use dynamic_pointer_cast once you retrieved the pointer back from the vector to check what object it is, but other options are available.
This is, of course, just a rough idea and you'll need to learn a lot about the details, shared_ptr, etc. but I hope you have enough keywords and ideas to google now :)
Create a Product object with the common fields and a PackagedProduct (extending Product) plus a FreshProduct (also extending Product) with their particular fields. Then store smart pointers to them in:
std::vector<std::unique_ptr<Product> > Vec;
If FredOverflow's suggestion of using two vectors doesn't suit your needs, an alternative could be to make a variant of the two types and keep a vector of variants. This is fairly easy to do using boost::variant. You can wrap the functionality you need in free functions or you can wrap the variant in a class. Here's an example
#include <boost/variant/variant.hpp>
#include <boost/variant/apply_visitor.hpp>
#include <boost/variant/static_visitor.hpp>
struct FreshProduct
{
double price;
bool needsRefrigeration;
};
struct PackagedProduct
{
double price;
};
struct VariantProduct
{
VariantProduct(const FreshProduct& p) : product(p) {}
VariantProduct(const PackagedProduct& p) : product(p) {}
double getPrice() const;
bool needsRefrigeration() const
{
struct helper : public boost::static_visitor<bool>
{
bool operator ()(const FreshProduct& product) const
{
return product.needsRefrigeration;
}
bool operator ()(...) const
{
return false;
}
};
return boost::apply_visitor(helper(), product);
}
private:
boost::variant<FreshProduct, PackagedProduct> product;
};
// in cpp
namespace {
struct GetPrice : public boost::static_visitor<double>
{
template <class T>
double operator ()(const T& product) const
{
return product.price;
}
};
} // anonymous namespace
double VariantProduct::getPrice() const
{
return boost::apply_visitor(GetPrice(), product);
}
The advantage of this approach is that you don't need to use dynamic allocation in order to keep a collection of fresh or packaged products. The downside is that it is not so easy to extend the types supported in the variant as when using inheritance.
My question is: (is the above|what is) the right way to create a non intrusive front-end?
I am explaining my problem with a simplified example.
I have a back-end implementing a binary tree:
// Back-end
struct Node
{
Label label;
Node* r, l;
};
I would like now to implement the front-end to print the tree graphically. So my idea is to extend the back-end with graphical properties by wrapping it :
// Front-end
struct Drawable
{
uint x, y;
};
class Visitor;
template <class T> struct GNode : public Drawable
{
T* wrapped;
template <class V> void accept(V& v); // v.visit(*this);
}
There is a problem now to create a visitor printing the binary tree:
struct Visitor
{
void visit(GNode<Node>& n)
{
// print the label and a circle around it: ok.
if (n.wrapped.l) // l is a Node, not a GNode, I can't use the visitor on it
// Problem: how to call this visitor on the node's left child?
// the same with n.wrapped.r
};
};
As explained in comments, the back-end does not use my extended class.
Writing GNode "is-a" Node is not a solution neither since I would have to put the accept() method in the Node class as virtual and override it in GNode but I can't modify the back-end. Then, someone could say too that there is no need to declare accept() in the back-end, downcasting Node* to GNode* would work. Yes it works, but it downcasts...
In my case, I have ~10 kinds of nodes (it is a graph), so I am looking for something elegant, flexible, with as few lines of code as possible (hence the wrapper template idea) :)
Thank you very much.
To absolutely disassociate code is impossible. They have to talk. If you really want to enforce decoupling to the maximal extent, some sort of IPC/RPC mechanism should be used and have two different programs.
That said -- I don't like visitor patterns.
You have a Graphical object, which is linked against a Behaving object. Maybe there are rules between behavior and the graphics, e.g., boundaries can't overlap.
You can do your entity relationship whatevers between the Graphicals and the Behaves, that's a biz logic question...
You will need some thungus that holds your drawing context (img, screen, buffer).
class DrawingThungus {
void queue_for_render(Graphical*);
void render();
};
Your Graphical will have either an inheritance or a composition relationship with behaves.
At any rate, they will have the interface needed to do Drawing.
//abstract base class class Graphical {
get_x();
get_y();
get_icon();
get_whatever();
};
If you are finding that your Render is becoming case-based depending on the kind of Graphical, I suggest pushing the cases over to the Graphical, and refactoring to have a get_primitives_list(), wherein the needed primitives are returned for Graphical to return (I am presuming that at some level, you have core primitives, lines, circles, arcs, labels, etc).
I have always found that OO analysis lends itself to wasting mental energy and should be done only enough for the task at hand. YAGNI is a tremendous principle.
If your wrapper class (GNode) didn't have to maintain any state across visits (i.e., it only had one field - the wrapped Node object), you could use a pointer or a reference to the wrapped object instead of a copy, and then you would be able to wrap any node at runtime.
But even if you do maintain state (the x,y coordinates), don't you really just infer it from the wrapped object? In that case, wouldn't it be better to separate your visited class from the inferred data? For instance, consider this implementation:
// This is an adapter pattern, so you might want to call it VisitorAdapter if you
// like naming classes after patterns.
template typename<T>
class VisitorAcceptor
{
private:
T& wrapped;
public:
VisitorAcceptor(T& obj)
{
wrapped = obj;
}
template <typename VisitorT>
void accept(VisitorT& v)
{
v.visit(wrapped);
}
};
struct GNode
{
uint x, y;
shared_ptr<GNode> l,r; // use your favourite smart pointer here
template <typename VisitorT>
void accept(VisitorT& v)
}
// You don't have to call a visitor implementation 'Visitor'. It's better to name
// it according to its function, which is, I guess, calculating X,Y coordinates.
{
shared_ptr<GNode> visit(Node& n)
{
shared_ptr<GNode> gnode = new GNode;
// calculate x,y
gnode->x = ...
gnode->y = ...
if (n.l)
gnode->l = VisitorAdapter(n.r).accept(*this);
if (n.r)
gnode->r = VisitorAdapter(n.l).accept(*this);
};
};
Now you can have a different visitor for drawing:
struct GNodeDrawer
{
void visit(GNode& gnode)
{
// print the label and a circle around it: ok.
if (n.r)
visit(n.l);
if (n.r)
visit(n.r);
};
};
Of course, if you don't need all the extensibility the visitor pattern offers, you can throw it away altogether and just walk the the tree recursively with XYCalculator.visit calling itself.
Personally, I would make a drawing class with overloaded functions (one for each node type) rather than trying to hook into the existing structure with some sort of complicated inheritance solution.
I finally found an "elegant" solution with the decorator design pattern.
This pattern is used to extend an object without changing its interface.
GNode decorates/extends Node:
template <class T> struct GNode : public T, public Drawable
{
virtual void accept(Visitor& v); // override Node::accept()
}
As you can see, it requires a little change in the back-end structure:
struct Node
{
Label label;
Node* r, l;
virtual void accept(Visitor& v);
};
That's it ! GNode is-a Node. We can now create a binary tree of GNodes and visit it thanks to the virtual method accept() in the back-end structure.
In the case when we absolutely follow my question, i.e. we can't modify the back-end and it doesn't have the virtual entry point presented above, we can add features to GNode mapping the Node it wraps to itself. So that a visitor visiting GNodes (that can only have access to its sons) can find the GNodes of its sons. Yes, this is the virtual keyword job with the above solution! But we never know if someone would be in this case for real.
As a conclusion to all this: the way you express a problem always influences the way to resolve it.
I'm working on a game engine component that handles events. What I'm trying to do is create a system that I can register new event types by name. The event manager will then hold a collection of event types and the factories to generate such an event type BUT the twist is that I want to make it used a pooling system such that I create an event, use it and then rather than deleting it, throw it into a list. Next time I create that event, rather than using the heap, I can just allocate from the pool.
SO given these hierarchy of event types...
struct TEvent
{
int nID;
int nTimeStamp;
};
struct TCollisionEvent : public TEvent
{
TEntity* pEntity1;
TEntity* pEntity2;
Vector3* pvecPoint;
};
I then created a smart factory which does this creation/recyling operation:
template <class BASE_CLASS>
class CSmartFactory
{
private:
typedef typename std::list<BASE_CLASS*> TBaseList;
typedef typename std::list<BASE_CLASS*>::iterator TBaseListItr;
TBaseList* m_plstPool;
public:
explicit CSmartFactory()
{
m_plstPool = NULL;
}
~CSmartFactory()
{
TBaseListItr itr;
if (m_plstPool)
{
for (itr = m_plstPool->begin(); itr != m_plstPool->end(); itr++)
{
BASE_CLASS* pEntity = *itr;
SAFE_DELETE(pEntity);
}
m_plstPool->clear();
SAFE_DELETE(m_plstPool);
}
}
bool Init(int nPoolSize)
{
bool bReturn = false;
do
{
m_plstPool = new TBaseList;
IwAssert(MAIN, m_plstPool);
while (nPoolSize--)
{
BASE_CLASS* pBaseObject = new BASE_CLASS;
IwAssert(MAIN, pBaseObject);
m_plstPool->push_back(pBaseObject);
}
bReturn = true;
} while(0);
return bReturn;
}
BASE_CLASS* Create()
{
BASE_CLASS* pBaseObject = NULL;
//
// grab a pre-made entity from the pool or allocate a new one
if (m_plstPool->size() > 0)
{
pBaseObject = m_plstPool->front();
m_plstPool->pop_front();
pBaseObject->Clear();
}
else
{
pBaseObject = new BASE_CLASS;
IwAssert(MAIN, pBaseObject);
}
return pBaseObject;
}
void Recycle(BASE_CLASS* pBaseObject)
{
m_plstPool->push_back(pBaseObject);
}
};
SO now I can do this:
CSmartFactory<TCollisionEvent>* pCollisionEventFactory = new CSmartFactory<TCollisionEvent>;
BUT what I want to do is have my event manager allow for dynamic event registration but that's where I run into my snag.
Ideally RegisterEvent will track the name and factory pointer in an stl::map or something but not quite sure how to get to that point. Maybe I've gone down the wrong path altogether.
This compiles
class TEventManager
{
public:
TEventManager();
~TEventManager();
bool RegisterEvent(char* pszEventName, CSmartFactory<TEvent>* pFactory);
};
Until you add
TEventManager::RegisterEvent("CollisionEvent", new CSmartFactory<TEntityCollisionEvent>);
So now I'm hopelessly trying to find a way to make this all work.
Anybody got some ideas here!?
Fred
I assume that you want to reuse events to avoid expensive heap malloc/free's?
I think the right answer here is not to convolute your code by writing your own structure for reusing objects, but to use a small-object allocator. As a start, it may be worth looking into boost::pool.
The two classes CSmartFactory<TEntityCollisionEvent> and CSmartFactory<TEvent> will be generated to something like
CSmartFactory_TEntityCollisionEvent
CSmartFactory_TEvent
They are actually two separate and unrelated classes. Trying to use them interchangeably would be unwise, although they behave the same (their type classes are polymorphic right).
Dynamic casting wont work, you could however try to use brute force casting:
TEventManager::RegisterEvent("CollisionEvent",
reinterpret_cast<CSmartFactory<TEvent>*>(new CSmartFactory<TEntityCollisionEvent>));
Warning: At your own risk! ;-)
OK so after a lot of head banging, I realized the solution is FAR simpler than what I was trying to pull off.
All the manager should care about is managing a TEvent*. Each TEvent has a unique hash value that makes it unique so when a new event is added both the string name and hash name of that even is stored. So from there I can add a pointer to any subclass so long as it's casted to TEvent.
I was making it FAR more complex than it needed to be.
I've got way too much information to work with, so for now I'll consider this question answered until I can sort it all out and decide on the final implementation! Thanks a ton gf and Simon Buchan. I wish I could accept both of your answers, since they're both definite possibilities!
Additional / Revised Conceptual Information as suggested:
What I am aiming to do;
I am making a game. In this game every object used is an instance of the DOBJ class. The TUR class extends the DOBJ class. The SHO class extends the TUR class.
Each TUR class has an array of SHO's stored in it's SHOARR array. Each SHO instance needs to be given a set of instructions.
I know for a fact I could make 1000's of different SHO classes that have their instructions set during construction.
However, considering I will have so many different acting SHO instances, I was interested in another way to pass a set of instructions. Through the contruction of the SHO would be the ideal.
The instructions I am attempting to pass to each SHO are simple if statements;
if(frame > 64) { rotation += 4; };
if(state == 0 && frame < 32) { xs = 12; ys = 12; state = 1; };
Original question
Migration from ActionScript3.0 to C++ is proving to be a trial indeed. Thanks to those who have answered my questions thus far and also to those who opened stackoverflow in the first place. Onto the question... (TL;DR near the bottom to get straight to the question)
I'm attempting to apply the same logic that I could apply in AS3.0 to my project in C++ and it's just not going very well.
In AS3.0 I was used to slapping any and every datatype into an Array. It made things pretty simple. Now that I've run into C++ dev, I realized that I can't exactly do that anymore.
So now I'm stuck with this problem of rewriting a little AI system in a new language, where the driving point of the system isn't even compatible!
Here's an example of a piece of the code I was writing in AS3.0;
AI[NUM][1]( OBJ, AI[NUM][2], AI[NUM][3] );
AI being an array, NUM being an integer, OBJ being an instance of a class.
This line obviously called the function in the second element of the first array in the main array with the arguments being a class in which to perform the function on, whatever was in the third element of the first array of the main array, and likewise the fourth element.
In this case;
AI[NUM][1] would be a function
AI[NUM][2] would be a variable
AI[NUM][3] would be a number
Generally, my AI was run on calling a function to change or compare the variable with a number.
An example would be;
CompareST( someObject, "x", 500 );
and return true if someObject's x variable was smaller than (ST) 500.
The AI array itself was just filled with arrays of calls similar to this.
Quite new to C++ I had no idea how to go about this, so I did a bit of searching and reading of many different websites and came to the conclusion that I should look into function pointers.
However, after reading a bit into them, I've come to the conclusion that it won't help me realize my goal. While it did help me call functions like I wanted to call them, it doesn't help me stack different datatypes into one large array of arrays.
TL;DR
EDIT++:
What I need for each object is a set of instructions to be checked every frame. However, for each instance of the class, the instructions have to be different.
I plan on having a LOT of different instances, so making a class for each one is unreasonable.
Thus, I needed a way to pass a set of instructions to each one through it's constructor and read + execute them at any time their think() function is called.
My ultimate goal (aside from finding out about a better way to go about this) would be to be able to have an array of function calls, like;
A[n][0]( O, A[n][1], A[n][2] );
Where;
O is the instance the function is altering
A[n][0] is a function (Equality or Comparison)
A[n][1] is the variable, eg; "x", O["x"] (or a pointer to that variable in the case of C++)
A[n][2] is the value to alter the variable by, or compare it to.
And I'm not sure how I would rewrite this into C++, or alter it to work in another way.
Aftermath / Additional Information
What I'm actually aiming to do is be able to give an object a set of instructions at the time of it's creation, through the constructor. For example upon creation give an object instructions to wait 64 frames, and then rotate in the opposite direction, would have been something like this;
t.AI = [ [ 1, AIF.CompareET, "STATE", 0, AIF.CompareGT, "FRAME", 64, 0, AIF.EqualityAT, "baseRotation", 180, AIF.EqualityET, "STATE", 1 ] ];
In pseudocode;
(The 1 in the array denotes how to read the rest of the array, in this case everything before the odd 0 [ The one that comes after 64 ] is a comparison. If any of those fail, anything after the 0 will not be looked at )
Compare STATE is equal to (ET) 0, if true
Compare FRAME is greather than (GT) 64, if true
Add 180 to (AT) baseRotation, Set STATE equal to 1
Sorry that this turned out really long. I hope it's understandable, and I'm not asking something stupidly difficult to explain.
You can store functions using function pointers or functors. Variant types though are not natively supported by C++, you have to use custom solutions there.
One possibility would be to use Boost.Any (or better, Boost.Variant if you only use a fixed set of types):
typedef void (*Function)(Object*, const std::string&, boost::any&);
std::vector<Function> functions;
Given some function:
void f(Object* obj, const std::string& name, boost::any& value) {
// ...
}
you could store and call it similar to your example:
functions.push_back(&f);
functions[0](obj, "x", boost::any(500));
To utilize a declarative syntax, there are three options that come to my mind:
you use a similar approach and have central "interpreter" function, e.g. based on a switch (don't forget to switch to integers or pointers-to-members instead of strings if you need performance)
you invent your own language and generate C++ code from description files
you compose function objects in a declarative way
To do composition, you could use Boost.Bind or something like custom objects that represent operations:
struct Operation {
virtual ~Operation() {}
virtual bool operator()(Object&) = 0;
};
template<class T>
struct GreaterThen : Operation {
typedef T Object::*Member;
Member member;
const T value;
CompareGT(Member member, const T& value) : member(member), value(value) {}
bool operator()(Object& obj) { return (obj.*member > value); }
};
template<class T>
struct SetTo : Operation {
typedef T Object::*member;
Member member;
const T value;
SetTo(Member member, const T& value) : member(member), value(value) {}
bool operator()(Object& obj) { obj.*member = value; return true; }
};
Now we can build operation lists:
typedef std::vector<Operation*> OpList;
OpList operation;
operations.push_back(new GreaterThen<int>(&Object::Frame, 64));
operations.push_back(new SetTo<int>(&Object::State, 1));
We can use helper functions to avoid having to specify the template types:
template<class T>
Operation* opGreaterThen(T Object::*mem, const T& val) {
return new GreaterThen<T>(mem, val);
}
Assuming a similar helper for SetTo and using Boost.Assign the above becomes:
OpList operations = boost::assign::list_of
(opGreaterThen(&Object::Frame, 64))
(opSetTo (&Object::State, 1));
Executing the operations becomes the following then:
OpList::iterator it = operation.begin();
for( ; it != operations.end(); ++it) {
Operation& op = *it; // just for readability
if(!op(someObject)) break; // stop if operation returns false
}
Wow.
Reading through that slowly suggests what you're trying to end up with is an array of function calls and you can choose a different function with the same parameters (but different implementation) for different actions and choose the correct one for the correct case.
If that is the case, you're looking for function pointers. Try this tutorial.
You should be able to use a function pointer with an argument set and point it to the correct function based on your needs. You won't need an array of function pointers for this either - any function that matches the definition should do. From the tutorial, declare a function pointer like this:
int (TMyClass::*functptr)(classname, int, int) = NULL; // C++
Then assign it later:
this.functptr = &TMyClass::doitthisway;
While it is possible (although a pain) to have an array of arbitrary types, you pretty much never need it, since you have to know something about what is where to do anything interesting with it: for example, your 'TL;DR' example seems to look something like:
struct AIRule {
// Can only handle comparing ints, see later for more general solution.
typedef bool compare_type(AIObject*, AIObject::*int, int);
compare_type* compare;
AIObject* object;
AIObject::int* member;
int comparand;
};
So now you can do something like:
bool ai_equal(AIObject* object, AIObject::int* member, int comparand) {
return object->*member == comparand;
}
...
ai[n].compare = &ai_equal;
ai[n].object = some_object;
ai[n].member = &AIObject::some_member;
ai[n].comparand = 50;
...
if (ai[n].compare(ai[n].object, ai[n].member, ai[n].comparand)) {
...
}
This just moves the any type problem from the rules array to member though. C++ needs to know at least how many bytes a member is, and a string (for example) can be much bigger than an int. You can get around this by using pointers: which essentially is C++'s version of any, but you then need to delete it yourself (or you will leak memory!), at which point the interface method below becomes simpler.
If I was doing what you seem to want, I would use inheritance:
struct Sprite {
int frame;
double rotation;
Sprite() {
frame = 0;
rotation = 0.0;
}
virtual ~Sprite() {}
virtual void think() {
++frame;
}
virtual void draw() {
...
}
};
struct RotatingSprite : public Sprite {
int state;
MyShape() {
state = 0;
}
void think() {
Sprite::think();
if (state == 0 && frame > 64) {
state = 1;
rotation += 180.0;
}
}
};
Or a function pointer:
struct Sprite {
int frame;
double rotation;
void (*think)(Sprite*);
Sprite() {
frame = 0;
rotation = 0.0;
}
};
void rotate_think(Sprite* sprite) {
if (sprite->state == 0 && sprite->frame > 64) {
sprite->state = 1;
sprite->rotation += 180.0;
}
}
...
sprite->think = &rotate_think;
If you really need to do it dynamically I would recommend using the ++ part of C++. For the predicates (a predicate is just something that returns a boolean, like isLowerCase()) create an AIPredicate interface, and the actions an AIAction interface:
struct AIPredicate {
// "When you delete an AIPredicate, delete the full type, not just this interface."
virtual ~AIPredicate() {}
// "You can treat this as a function (operator()) but I'm not providing an implementation here ( = 0)"
virtual bool operator()(AIObject* object) = 0;
};
struct AIAction {
virtual ~AIAction() {}
virtual void operator()(AIObject* object) = 0;
};
struct AIRule {
// std::auto_ptr (or std::unique_ptr if you can use C++0x) will delete predicate for you.
// Add "#include <memory>" to your includes if it complains (most std headers will include it already)
std::auto_ptr<AIPredicate> predicate;
std::auto_ptr<AIAction> action;
};
Now you can make types like:
struct AIFrame : public AIPredicate {
// Implement the operator() member AICondition promises.
bool operator()(AIObject* object) {
return object->foo < 100;
}
};
...
// Use .reset() instead of = if you use std::unique_ptr.
ai[n].predicate = new AIFooIsLow();
If you want to have a very general predicate type, you can use the very powerful (and complicated) templates feature:
// The naming convention I'm using here is 'T'TitleCase for template parameters, TitleCase for types,
// lower_case for arguments and variables and '_'lower_case for members.
template<typename TMemberType, AIObject::TMemberType* TMember>
struct AIMemberEquals : public AIPredicate {
// Constructor: Initializes a new instance after it is created.
AIMemberEquals(TMemberType comparand) {
// Save comparand argument so we can use it in operator().
_comparand = comparand;
}
bool operator()(AIObject* object) {
return object->*TMember == comparand;
}
// Stores the value to compare.
TMemberType _comparand;
};
Unfortunately, creating templates looks a bit crazy:
ai[n].predicate = new AIMemberEquals<int, &AIObject::some_member>(100);
Read it as "create a new instance of (the type that AIMemberEquals applied to int and (the some_member member of AIObject) creates), with the argument 100".
When you have multiple predicates memory management becomes a bit more difficult without C++0x's unique_ptr or shared_ptr, types that will delete the object for you, since std::auto_ptr doesn't work in containers:
#include <vector>
struct AIData {
// vector is fairly close to AS3's Array type, it is a good default for
// arrays of changing or unknown size.
std::vector<AIPredicate*> predicates;
// Destructor: will be run before the memory for this object is freed.
~AIData() {
for (int i = 0; i != predicates.size(); ++i) {
delete predicates[i];
}
}
};
...
ai[n].predicates.push_back(new AIFooIsLow());
...
for (int i = 0; i != ai[n].predicates.size(); ++i) {
(*ai[n].predicates[i])(ai[n].object);
}
In C++0x:
struct AIData {
// unique_ptr will delete it for you, so no ~AIData() needed.
std::vector<unique_ptr<AIPredicate>> predicates;
};
Your final example could in C++ look something like:
std::auto_ptr<Shape> shape(new Shape());
...
std::auto_ptr<AIRule> rule(new AIRule());
rule->predicates.push(new AIMemberEquals<int, &Shape::state>(0));
rule->predicates.push(new AIMemberGreater<int, &Shape::frame>(64));
rule->actions.push(new AIAddMember<double, &Shape::rotation>(180.0));
rule->actions.push(new AISetMember<int, &Shape::state>(1));
shape->ai.push(rule); // .push(std::move(rule)); if you are using unique_ptr
Certainly not as pretty, but it works and is fairly flexible.