Optimizing full scriptability

Started by
6 comments, last by Hodgman 13 years ago
I have a base entity class that extends to every object used in the game. I'm using two base class functions to manage all scriptable properties in each derived class:

WSEERROR SetProperty(char * sName, wchar_t * sValue);
WSEERROR RegProperty(REF ref, char * sName, const type_info * ti);

and a macro to give me a shorthand:

#define REGENTITYPROPERTY(prop) { RegProperty((REF)&prop, #prop, &typeid(prop)); }

What I need (and this is extra easy to do using REGENTITYPROPERTY()) is to simply call RegProperty() in the constructor of all derived classes for member variables that need to be exposed to scripting. Generally these are (mostly static) variables that apply to whole object types, eg:

class Car : public Entity { ... };
class LandCruiser : public Car {
static float fTyreWidth;
bool bHasUberWeponForLandCruisers;
LandCruiser()
{
REGENTITYPROPERTY(fTyreWidth);
REGENTITYPROPERTY(bHasUberWeponForLandCruisers);
}
};


Simple and elegant. I can now use

car->SetProperty("fTyreWidth", L"2.2");
car->SetProperty("bHasUberWeponForLandCruisers", L"true");

to set the tyre width for all land cruisers or enable a very cool uber weapon for a specific land cruiser, which is extremely scripting friendly. This, however, is where I need to make a few choices: I'm using RTTI to avoid specifying the type info manually. This requires RTTI to be enabled for the entire program (there are no other pieces of code that absolutely depend on type information, although I'm using RTTI to implement reflection in C++ for debugging purposes, which I am willing to drop for the final release build). As far as I've read (I haven't actually done the required profiling myself as the setup would take a while to write, which is why I'm rather asking here) RTTI can get pretty expensive size-wise, especially when it comes to functions with (pure?) virtual calls. I have a number of base classes in my code that implement both pure and non-pure virtuals, including the Entity base class, which extends to every imaginable in-game object, usually reaching an inheritance depth of 2-3. Also, using RTTI to extract type information is inherently safer than having a programmer define it manually (on the other hand, the number of exposed/scriptable variables isn't that ludicrously large).

Internally I'm still translating the type_info struct ref into an int variable to denote the data type for faster look-up inside SetProperty(). Externally, even though I won't be calling RTTI code on a per-frame basis, I do want to enable scripting modifiers for continuous functionality: for instance a scripted modifier that affects an exposed variable depending on time, which is applied every frame. This raises two questions:

1) On the one hand, is this the best way to expose local functionality to scripting? (even though I can optimize away variable name string comparisons, I will still need to do string-to-datatype conversions whenever I'm applying a modifier)
2) On another hand, would I be better off giving up some programming shorthand for the sake of being able to drop RTTI, eg by manually identifying the data type of the scriptable variable? I mean, is it really worth it at the end of the day? I don't even care about the size impact, which I've read to be in the range of 5-15% of the executable size, but much more about potential speed issues.

In short, (1) I know this is a viable way of enabling dumb object scripting, but is there a better/faster way of going about it and (2) at this time and age, should I really be worried about having RTTI enabled?
Advertisement
You can probably make use of templates to do a lot of your auto-type generation.

e.g. here's an example of using a template to automatically choose a setter/getter class based on the variable type.struct IPropertyAccess
{
virtual bool Set(void* prop, const std::string& string) { return false; }
virtual bool Get(const void* prop, std::string& string) { return false; }
};
template<class T>struct PropertyAccess : public IPropertyAccess{};
template<> struct PropertyAccess<float> : public IPropertyAccess//one of these for each property type
{
bool Set(void* prop, const std::string& string)
{
float& out = *(float*)prop;
out = (float)atof(string.c_str());
return true;
}
bool Get(const void* prop, std::string& string)
{
const float& in = *(const float*)prop;
std::stringstream ss;
ss << in;
ss >> string;
return true;
}
};

struct Property
{
PropertyAccess* propertyType;
void* propertyData;
const char* name;
};
template<class T>
void RegProperty(T* data, char * name)
{
static PropertyAccess<T> propertyType;
Property p = { &propertyType, data, name };
m_Properties.push_back(&p);
}


On another note, you shouldn't have to register each instance of a property for each instance of a class (unless your properties are dynamic and can be added/removed). If your properties are static (i.e. like C++ members are - unable to be removed) then each class could have a static function which registers it's members by recording a pointer-to-member-variable for each of them.
I don't know how many games still put the game logic in C++ anymore. Sure the fundamental entity components like graphics, physics, animation and AI still lives in the C++ core, but game logic has migrated to a more flexible scripting language for many games. That allows for faster iteration and more flexibility. This benefits even smaller games, like indie games. The benefits are several fold, RTTI is already supported in the scripting languages (most times) so reflection is easy, complex game states can be serialized (by dumping the virtual machine state in one piece), faster iteration because designers can iterate interdependently and usually the scripting language us tailored to the task, so the programs are smaller and simpler.

Though I've used such macro schemes in the past not for scripting but for networking in exposing seralizable elements in a class and generating seralizing functions for those classes. I see nothing wrong with using them if your intent on keeping the game logic in C++, less typing and less mistakes in creating the interfaces.

Good Luck!

-ddn
Thanks for the replies, guys! I am, indeed, intent on keeping everything in C++ and this being a one man hobby project, I don't see a reason to broaden the scope to such macro schemes. I hadn't thought of using templates here since I usually don't find much use for them in what I'm doing, but the solution suggested by Hodgman works just wonderfully and will cater for all the needs I have right now! I will be implementing LUA support and scripted object creation, however, which is why I'm trying to come up with the fastest possible way of updating an instance-specific variable in realtime from a script (which is also why I will need to register all scriptable variables per object, not per class - incidentally this is where I figured I'd use static variables to distinguish between the two).

As a small sidenote I thought I'd mention, Hodgman, that you're storing a reference to a local variable in RegProperty(), but I'm guessing that's just a typo from copying together the sample code :)

In any case, you have been most helpful. Thank you.

As a small sidenote I thought I'd mention, Hodgman, that you're storing a reference to a local variable in RegProperty(), but I'm guessing that's just a typo from copying together the sample code :)
Ah but it's a static variable so it's okay, and given it's in a template class there would be one static variable for each type. Looks very much intentional to me, and not a mistake.
"In order to understand recursion, you must first understand recursion."
My website dedicated to sorting algorithms
I might question the wisdom of encouraging access to static members through type instances -- granted, IIRC, C++ allows the same but I tend to hold the opinion that, in general, static members should be considered read-only for class instances, and that modifying such a member should only be done through explicit resolution of the member (using <Type>::<Static Member> syntax, or through static members, similarly resolved).

Aside from the academic purity of this stance, it also can reduce the number of candidate members in the case that dispatch/resolution happens at run-time.

You might also consider prototypal inheritance a'la JavaScript, rather than the classical OO inheritance model -- which provides an easy solution to such issues, and fits will with scripting. This may not apply, of course, you don't say whether you are integrating an existing scripting solution or rolling your own.

throw table_exception("(? ???)? ? ???");

The fastest way? :) If your platform is the PC and your going to use Lua, the fastest way is to use LuaJit 2 in conjunction with the FFI. From my tests, you'll get a massive improvement.

Normal Lua is pretty fast, fast enough for general game logic (on the PC anyways). But when you cross the C<->Lua boundary it takes a hit. LuaJit 1.xx gives a 3-5x fold speedup and also reduces the C<->Lua overhead alittle. That's pretty good but if your doing alot of C<->Lua calls your going to eat up all that performance. Now with LuaJit 2.xx and FFI the C<->Lua overhead has all but disappeared and its 2x faster than LuaJit 1.xx which makes it about 6-10x than normal Lua in practical use case (approaching C in some cases). I'm routinely passing megabyte chunks of data back and forth between Lua and C every frame and calling C functions literally 1000s of time per frame with minimal performance hit. Essentially it allows you to program in Lua at C speeds.

Good Luck!

-ddn
[quote name='irreversible' timestamp='1302856254' post='4798700']As a small sidenote I thought I'd mention, Hodgman, that you're storing a reference to a local variable in RegProperty(), but I'm guessing that's just a typo from copying together the sample code :)
Ah but it's a static variable so it's okay, and given it's in a template class there would be one static variable for each type. Looks very much intentional to me, and not a mistake.[/quote]Oops, I'm also copying the address of the local variable '[font="Courier New"]p[/font]' into the vector instead of copying the value of '[font="Courier New"]p[/font]'. Yes that was a typo from writing code on the fly.

This topic is closed to new replies.

Advertisement