RTTI and Scripting

Started by
4 comments, last by Jez 18 years, 8 months ago
[edit: Looks like I rambled more than I intended to here. Sorry. If you even take the time to read this then I'm thankful] I'm currently working on a small engine and one of my goals is to implement scripting support later on. I feel that in order to keep that feasible, I need to bear that goal in mind as I make design decisions early on in the project. Currently I'm still at the stage of implementing the object creation/destruction framework. I'm using a combination of reference counted smart pointers and a central object factory which is used to create any type of object provided a Creator for that object is registered with the factory. This is done using a templated Create method of the Factory class: SmartPointer<SomeObject> obj = factory->Create<SomeObject>(); So far, this all works beautifully as far as I can tell. But there are problems later on. The factory holds a map with values which are CreatorBase pointers, and in reality the objects being pointed to are of the Creator class, which derives from CreatorBase and is templated to allow creation of any object provided it derives from my Object base class. Half of the point of me using this approach is that I'll be able to create other derivitives of CreatorBase if I want. For example I could derive a special creator which keeps a 'pool' of old objects and recycles them when a new one is requested. The 'list of CreatorBase pointers' is actually a map. This is where my problems appear. Initially I thought it would be really neat, and really useful later on when implementing scripting support, to map class names (as strings) to Creator objects, so that my Factory is capable of creating an instance of a class given the name of the desired class as a string. That way the name of a class to be created could be given in a script, passed to the Factory's CreateByName method, and the desired object would be produced... Seemed a nice idea anyway, but now I've hit trouble. I'm using the standard C++ RTTI system to achieve what I've got so far, but unfortunately it seems I wasn't as well informed about the workings of RTTI as I had hoped. This is the first time I've experimented with it really. Anyway, it turns out that, despite what I had believed, the type_info::name method returns a decorated string, rather than an undecorated one. I think the misunderstanding here might have occured due to differences between RTTI implementations. I'm using gcc, and the texts I have read were most likely written with MSVC in mind. Either way, it seems my idea will not work quite as nicely as I hoped. I'm sure at least some people would suggest I stay clear of RTTI completely, but unless the performance hit I'm likely to receive from using it is far beyond what I'm expecting, I don't really see much of a reason not to use it, as I'm not interested in creating the next Quake. I reason that I can afford to spare some resources to a feature like this which could potentially make my life a whole lot easier in the long run. So what I'm hoping to gather here is opinions on whether or not I should continue to try and find a way to implement my existing plan, perhaps through the use of a simple custom RTTI system. Or perhaps there are better ways of coping with the creation of new objects through scripts which I have not yet encountered? If so, please enlighten me. If you think you can offer any useful advice here, be my guest. Thanks! :)
Advertisement
std::type_info::name() yields an implementation specific null terminated string. A standards compliant implementation could simply return "" for every type. Needless to say, no compiler actually does this.

Personally, I prefer manual registration of classes exposed to scripting. And since in most cases it proves extremely difficult to automate the registration of class members to the scripting language, you'll end up doing manual registration of members anyways.
Thanks SiCrane.

I'm already registering Creators to the Factory manually, yes. However, ideally I'll only have to do this once for each class, and then these registered classes can be used by the scripting system directly using the same strings the user types into the script.

The alternative, as far as I can see, would be to register Creators the the Factory for use in the engine/game (C++) code, and then make some sort of lookup table for the scripting system which translates human-friendly string names such as 'goblin', to the key mapped to the creator in the Factory, which may or may not be anything to do with the class' name. Perhaps just an integer would be suitable.

However, if I do this then how do I make it easy to keep track of which object will be produced by which integer? If the integer is randomly generated (possibly a bad idea?) then it will change from execution to execution, so the integer cannot be hard coded into the table in the scripting system.

Idea: Could I perhaps use the address of a class's type_info as the unique identifier? As I understand it, this should be unique and constant throughout execution of the program. Also I could populate the table translating object names to these IDs in the scripting system like so:

objects["goblin"] = &typeid(MonsterGoblin)

Or something like that.

I love how ideas like this come flowing as soon as I try asking for help :). So, uh... I haven't tried any of this, and will soon do so unless I get any immediate responses here telling me why it won't work after all.

Any comments on this?
Quote:
If the integer is randomly generated (possibly a bad idea?) then it will change from execution to execution


It's called Run Time Type Info for a reason. If you knew at compile time, it'd be easy :D

Anyways, I inadvertantly created my own RTTI sort of system a few months back. It simply has a type manager, which has hash tables for type lookup. The types store an id [int], a name [human readable string], and 4 functors [copy, delete, serialize, unserialize].

The ints are generated upon registration with the manager. The names are unique; non-unique names are refused upon registration.

Anyways, it mostly works. The setup I've found just doesn't have many advantages outside of serialization.
I'm 99% certain that you can't rely on the address of type_info objects from different calls of typeid on the same class to be the same. std::type_info provides an operator==() overload for that reason. It is possible to create a std::map indexed by type_info objects or proxies since type_info is ordered by the type_info::before() member function.

Personally, I think you're making life too hard for yourself. Unless I misunderstood something, I don't see any reason why you don't just specify the class name during registration.
Oops, looks like the lack of sleep is getting to me. I'm forgetting my own plan here. I gave some incorrect information in my previous post. So far I am NOT registering Creators manually. That's what I quite like about my system so far (though it has a little problem which I'll mention in a bit).

You see, the templated Create method of my Factory looks like this:

template <class Type>Type *Create() {  const char *tname = typeid(Type).name(); if (creators.find(tname) == creators.end()) {  // No creator exists for this type yet. Make one.  creators[tname] = new Creator<Type>; }    return static_cast<Type *>(creators[tname]->Create());   }


The static_cast is present there because the Create method of CreatorBase returns Object*. So anyway, as you can see, I simply give a type in the template of the method, and if a Creator for that type doesn't currently exist in the Factory's map, one is created and added.

Also, as you can see, I'm mapping the return value of type_info::name to the Creators there. As I said, the idea was to have another method of Factory, CreateByName, which is not templated but instead takes a string. It looks like this:

Object *CreateByName(const char *tname) {  if (creators.find(tname) != creators.end()) {  return creators[tname]->Create(); } else {  return NULL; } }


This is where the problems are (problems other than the fact that the whole thing doesn't work due to type_info::name not returning the type of string I was expecting). First is only a minor issue, and that is that CreateByName has to return an Object*, not a pointer to the specific type created. More importantly is that CreateByName cannot create an object if there is not already a Creator for it. It cannot create a Creator if one does not already exist like Create can, because it doesn't actually know the type being requested. Templating the CreateByName method would completely defeat its purpose. To work around this I added a quick templated AddCreator method which adds a Creator for a type but doesn't create an object of that type. This could be used in initialisation of the scripting system for example, to ensure that all types to be used in scripts are available for creation by name.

However, there would be nothing to stop a script trying to create a different type of object which the scripting system initialisation code did not anticipate being used in a script. Besides, that approach smells somewhat of bad design to me.

I'm still playing with ideas at the moment... I like how at the moment I don't need to manually register my Creators. If it weren't for this, I'd happily do as you have suggested. As far as I can tell, the only way for me to achieve what I'm after would be through a custom RTTI system which would be guaranteed to return the actual name of a class, not some mangled version.

This topic is closed to new replies.

Advertisement