397
25
IntrospectionforC++GameEngines
Jon Watte
IMVU, Inc.
25.1Introduction
This gem describes a mechanism for adding general-purpose introspection of
data types to a C++ program, using a minimum of macros, mark-up, or repetition.
Introspection is the capability of a computer program to look at its own data and
make modifications to it. Any high-level language of note has a rich introspec-
tion API, generally as part of an even larger reflection API, but users of C++
have had to make do with the built-in class
type_info ever since the early days.
A conversation with the introspection system of a language such as C#, Java, or
Python might look something like this:
“Hello, introspection system, I’d like you to tell me a bit about this here
piece of data I have!”
“Why, certainly, game program! It’s a data structure you’d like to call a
TreasureChest.”
“That’s useful to know, but what I really want to know is whether it has a
property called ‘
Position’?”
“Yes, it does! It’s of type
float3, containing the position in the world.”
“That’s great! Actually, now that I think about it, the designer just
clicked on it, and wants to edit all the properties. What are they?”
“Well, we’ve got ‘
Name’, which is a string, and ‘Contents’, which is a
list of references to
Object templates, and ‘Model’, which is a string
used to reference the 3D mesh used to render the object, and …”
In code, it looks something more like Listing 25.1 (using C#/.NET).
398 25.IntrospectionforC++GameEngines
string typeName = theObject.GetType().Name;
PropertyInfo info = theObject.GetType().GetProperty("Position");
/* ... */
foreach (PropertyInfo pi in theObject.GetType().GetProperties())
{
theEditor.AddProperty(theObject, pi.Name,
new GetterSetter(pi.ReflectedType));
}
Listing 25.1. Properties in a dynamic language.
So, what can we do in C++? Using only the standard language and library,
we can’t do much. You can find out the name of a type (using
typeid), and the
size of a type (using
sizeof), and that’s about it. You can also find out whether a
given object instance derives from a given base class (using the horribly expen-
sive
dynamic_cast<>), but only if the type has a virtual table, and you can’t iter-
ate over the set of base classes that it derives from, except perhaps by testing
against all possible base classes. Despite these draw-backs, C++ game program-
mers still need to deal with data, display it in editors, save it to files, send it over
networks, wire compatible properties together in object/component systems, and
do all the other data-driven game development magic that a modern game engine
must provide.
Trying to solve this problem, many engines end up with a number of differ-
ent mechanisms to describe the data in a given object, entity, or data structure.
For example, you may end up having to write code that’s something like what is
shown in Listing 25.2.
class TreasureChest : public GameObject
{
private:
/* properties */
string name;
float3 position;
list<ObjectRef> contents;
string model;
public:
25.1Introduction 399
/* saving */
void WriteOut(Archive &ar)
{
ar.beginObject("TreasureChest");
GameObject::WriteOut(ar);
ar.write(name);
ar.write(position);
ar.write(contents.size());
for (list<ObjectRef>::iterator ptr(contents.begin()),
end(contents.end()); ptr != end; ++ptr)
{
ar.write(*ptr);
}
ar.write(model);
ar.endObject();
}
/* loading */
void ReadIn(Archive &ar)
{
ar.beginObject("TreasureChest");
GameObject::ReadIn(ar);
ar.read(name);
ar.read(position);
size_t size;
ar.read(size);
while (size-- > 0)
{
contents.push_back(ObjectRef());
ar.read(contents.back());
}
ar.read(model);
ar.endObject();
}
#if EDITOR
400 25.IntrospectionforC++GameEngines
/* editing */
void AddToEditor(Editor &ed)
{
ed.beginGroup("TreasureChest");
GameObject::AddToEditor(ed);
ed.addString(name, "name", "The name of the object");
ed.addPosition(position, "position",
"Where the object is in the world");
ed.addObjectRefCollection(contents, "contents",
"What's in this chest");
ed.addFilename(model, "model",
"What the chest looks like", "*.mdl");
ed.endGroup();
}
#endif
}
Listing 25.2. TreasureChest introspection, the verbose way.
This approach has several problems, however. For example, each addition of
a new property requires adding it in several parts of the code. You have to re-
member the order in which properties are serialized, and you generally have to
write even more support code to support cloning, templating, and other common
operations used by most game engines and editors. More than one game has
shipped with bugs caused by getting one of these many manual details wrong. If
you apply the technique in this gem, you will be able to avoid this whole class of
bugs—as well as avoid a lot of boring, error-prone typing. Next to having the
language run-time library do it for you, this is the best you can get!
25.2TheDemo
The code that comes with this gem implements a simple network protocol for a
text-based chat server. A user can join a chat, get a list of other users in the chat,
and send and receive chat text. The chat server itself stores and edits some infor-
mation about each user, such as an email address and a password. While simple,
this is enough to show how to apply the mechanism of this gem to solve a variety
of problems in a real program.
25.3TheGem 401
There are four related programs:
A test program, which exercises the API mechanisms one at a time to let you
easily verify the functionality of the different pieces.
A server program, which lets you host a simple chat server (from the com-
mand line).
A client program, which lets you connect to a running chat server and ex-
change chat messages with other users (again, from the command line).
An editor program, which lets you edit the list of users used by the server
program, using the introspection mechanisms outlined in this chapter.
These are baked into two executables: the
introspection test program, to verify
that the API works as intended, and the
simplechat program, to act as a client,
server, or user list editor for the simple chat system.
To build the programs, either use the included solution and project files for
Microsoft Visual Studio 2010 (tested on Windows 7) or use the included GNU
make file for GCC (tested on Ubuntu Linux 10.04). Run the sample programs
from the command line.
25.3TheGem
The implementation of this gem lives in the files introspection.h, intro-
spection.cpp
, and protocol.cpp. While the implementation makes use of type
inference and other template metaprogramming tricks, the API from a user’s per-
spective is designed to be simple.
Decorate each data structure you want to introspect using an
INTROSPEC-
TION()
macro, as illustrated by the example shown in Listing 25.3. This macro
takes two arguments: the name of the data structure itself and a list of members
that you want to introspect. Each introspection member is described using a sepa-
rate macro that lists its name and a description of the role of the member. This
can be as simple as a plain C string (to display in an editor interface, for exam-
ple) or as complex as a rules-enforcing and network-encoding object that makes
sure floating-point values are within a given range and transmitted using a speci-
fied number of bits and other variant behavior.
While you still have to list each member twice, once for declaring it as an
actual member and once for declaring it introspectable, this mechanism cuts
down on the amount of boilerplate code you need to write. It also keeps the dec-
laration of introspection in the header, right near the declaration of the members,
to reduce the likelihood that someone adds a member without adding the appro-
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.35.247