Game Development Community

A day in the C++ life of T2D

by Smaug · in Torque Game Builder · 04/02/2005 (9:09 pm) · 60 replies

So, you're not content with a TScript-driven Torque2D. You want to have code run the show. You want to have TScript as helpers, not masters.

But, of course, the design is working against you. There's no interface for it. If you want to do this, you're going to have to hack the code in order to make it possible.

So, where does this go? That's what we're going to explore; the C++ internals of T2D's initialization and game loop. Note that I have not yet attempted to create a functioning T2D using an alternate initialization sequece or from adding additional elements to the game loop. This discussion is for how the standard T2D loop works with discussion as to which steps load/run scripts and where the user might best insert code.

Obviously, it all starts in main() (found in, if you're using Windows, platformWin32/winWindow.cc). However, this function does rather important initialization (and deinitialization) stuff that you'd probably do well to not touch. The next step is an abstract class (yay!) called "GameInterface".

It is an abstract class, but it is also a class that has functionality. It provides "Journal" functionality; you can read more about this in the comments to "gameinterface.h". It is not important for our needs.

What is important are the virtual functions of GameInterface. Specifically:

virtual int main(int argc, const char **argv);
   virtual void postEvent(Event &event);
   virtual void processEvent(Event *event);
   virtual void processMouseMoveEvent(MouseMoveEvent *event);
   virtual void processInputEvent(InputEvent *event);
   virtual void processTimeEvent(TimeEvent *event);

The real C/C++ main() function does initialization and then calls the global GameInterface object's (called "Game") main() function. No scripts have been loaded; only the most basic setup has been done. There isn't even a game window yet. It is GameInterface::main() that is expected to handle all the significant initialization, like creating a game window and so forth.

The standard T2D build comes with a derived and fully implemented child class of GameInterface called DemoGame. We will be using this implementation as a basis for user-defined implementations.

DemoGame is declared in game/demogame.h, but the definitions are all in game/main.cc.

The very first thing that DemoGame::main() does is call initLibraries(), a static function that does what it says. If it returns 0, then it failed.

After this, the next big step is running the global function initGame(). It takes the same arguments as main(), and it's job, among a few other things, is to run the user-specified startup script. This is clearly a step that should be avoided/ignored if the user is wanting to build the game from code rather than script. However, initGame() does some other things as well, like calling Sim::init(). I haven't investigated this step, but from a cursory glance, I wouldn't suggest not calling this function. It also creates an action map named "GlobalActionMap". It is up to you whether or not you care about this.

Now, we enter the main game loop. As such, if you want to do some code initialization before this (perhaps something like what your TScript would do in the setupT2DScene() function), feel free. Though the initGame stuff should probably happen first, so that the simulation is ready. In later looks at initializing T2D manually, I'll be walking through TScripts to emulate what they do in C++.

Here's the main game loop code in question:

while(Game->isRunning())
   {
      PROFILE_START(MainLoop);
      Game->journalProcess();
      Net::process();      // read in all events
      Platform::process(); // keys, etc.
      TelConsole->process();
      TelDebugger->process();
      TimeManager::process(); // guaranteed to produce an event
      PROFILE_END();
   }

<Why do we have this silly character limit? Posts with sentences are good.>
Page «Previous 1 2 3 Last »
#1
04/02/2005 (9:09 pm)
Looks like the main game loop, right? However, it technically isn't. Or, at least, it isn't the place to add additional updates for your own game's purposes. From a cursory examination of the functions in it, nothing of importance happens. Game::journalProcess() is a book-keeping function for the GameInterface's Journal feature. Net::process() looks like networking stuff. Platform::process() is a mechanism to read input using platform-specific code. TelConsole::process() is for console stuff. TelDebugger::process() is for something that doesn't have to do with the scene graph (not directly, at any rate). And all TimeManager::process() does is update the time. Where does the scene graph processing happen?

Oddly enough, in TimeManager::process(). This function does one other thing besides updating the time. It calls GameInterface::postEvent with a time event, providing the frame delta in milliseconds (Note to Melv: please provide the frame delta in floating-point notation. Or, at least, in microseconds. Integer milliseconds is an error-prone number, especially for 2D games where frame deltas might be 10 or 8 milliseconds).

GameInterface::postEvent is one of the important functions of interest. The DemoGame version does little more than record it into the journal and then call GameInterface::processEvent(). While this function is virtual, it is also implemented in GameInterface. DemoGame does not override it, so let's look at what GameInterface::processEvent() does.

After checking some basic stuff, it looks at the type of event and determines which virtual function to call on it. This is an interesting place to look if you're creating new types of events. Obviously overriding this with your own implementation to check for your special event types is certainly possible.

However, since this is a TimeEvent, it will call GameInterface::processTimeEvent(), a pure-virtual function in GameInterface. Obviously, DemoGame provides this function.

DemoGame::processTimeEvent() is the actual main game loop. It calls per-tick stuff like Sim::advanceTime(), GuiCanvas::renderFrame(), and ::clientProcess() (sound stuff, mostly). If you want to have your own objects, or if you need to, say, keep two sets of objects in sync, this is the place to put this kind of stuff. Or, if you want to have an event system of your own, to take care of various things in certain places, this is where it should go. In short, this is your "hook"; it is here that you should add your own per-tick stuff.

So, that's the basic T2D frame advance in a nutshell. Any questions?
#2
04/04/2005 (2:18 pm)
This is really great. Thanks for this. I just got T2D up and building in VS and was planning to see how hard it would be to use C++ or C# as a replacement for a bunch of TorqueScript. There's a lot of stuff that every game needs that's just data structures and algorithms and that doesn't NEED to be in TorqueScript. If I use C++, at least it will be portable to all platforms, but I find C# to be a lot nicer, at least for prototyping. And contrary to some of the C# bashing I've seen on this site in the past, I'll challenge TorqueScript's performance against C#'s anyday. Plus when you add the benefits of real debugging, it's not even a question.
#3
04/04/2005 (2:22 pm)
C# for scripting would be a very strange sense of overkill. Kind of like implementing a C++ compiler and interpreter to replace TorqueScript.
#4
04/04/2005 (3:12 pm)
Great writeup! I think that the only thing I would suggest is that it's important to note two things--and they are more simply a perspective/attitude shift than anything right or wrong:

1) When it comes down to it, TAP (T2D, TGE, TSE) programs are simulations (in the developer's sense, not the game type sense). What this breaks down to is that everything is event based, and the primary events are time advancement, and user input. This is why everything runs under processTimeEvent() from the simulation perspective.

2) Ironically, while it doesn't appear this way from how you set up and develop a game, the scripts really are the "helpers", and the code is the primary "executor". buried in the processXXEvent() loops, and their re-implemented C++ classes/helper classes, is a Con::executef(...) call that hands execution to a script call. (Note: there may be some internal processing that uses the functionality of execute/executef without actually calling those two functions--TAP was built over time, and the core engine was written a -long- time ago!).

If you are absolutely serious about never wanting scripts to be called, you would want to search for every single instance of Con::executef and it's derivatives, re-implement all functionality of the scripts they call in C++ classes, and call those access methods in your classes directly. Of course, you lose the capability for non-C++ coders to ever work on your project from a developer's perspective, but that may not be a problem for 1-man projects.

If however you think about scripts as "plug-ins" for non-C++ coders to write "code", then the setup makes a lot of sense--for every time that a TAP developer thinks that a scripter may want access to control execution flow and implementation, they implement a Con::executef() at the appropriate point which hands execution over to the script indicated, and create ConsoleMethods for the scripts to access classes, and/or execute contained class methods.

Intricate? Yes. Flexible? Incredibly!
#5
04/04/2005 (3:39 pm)
Quote:C# for scripting would be a very strange sense of overkill. Kind of like implementing a C++ compiler and interpreter to replace TorqueScript.

I don't know about that. I guess it depends on your perspective, really. I've always felt that TScript, in how it is used by T2D, was too high-level for such tasks. I appreciate the value of a good scripting language, but I would never want to write even semi-complex AI or such things in it. I feel scripting languages should be at a higher level, dealing directly with finished entites, and having no direct contact with low-level details like collision volumes, image loading, and the like. It seems to me that a language like C#, with its type-safety and other C++-like constructs, would make an excellent intermediate language between the C++ engine and TScript.

Quote:If you are absolutely serious about never wanting scripts to be called

It's not so much about not wanting scripts to be called as not wanting scripts to decide what the game is. It is script functions (in the initial setup of T2D) that do things like create scene windows, create GUI's, etc. It is the script for the standard T2D that usually defines what will and will not appear on the screen. In order to let C++ hook into this, we have to find out where this chain begins and interrupt it with our own C++ setup code.
#6
04/04/2005 (3:52 pm)
Perhaps its because I think of C# in an application development setting with large source bases. I have been using Perl and Python for the scripting I've done and Java/C/C++/C# for applications, so that's where my large-scale bias comes into play. But both Perl and Python have large application bases from theri camps, even if I only use them for scripting.
#7
04/04/2005 (8:10 pm)
Quote:But both Perl and Python have large application bases from theri camps, even if I only use them for scripting.

But both Perl and Python (and T2D's use of TScript unless you hack the code) take the same outlook on scripting: script drives code. Your script creates objects and calls functions that code has (globally) registered with the interpreter. A script can be spawning windows from one interpreter interface and call database functions from another and doing something else with a third interface and so forth.

This is against the design paradigm of languages like JavaScript and Lua (and special-purpose game development languages like UnrealScript): code drives script. These are embedded scripting languages; a single application decides what is and is not exposed to the script, and it decides which scripts to run and when to run them. You can use Lua kinda like Perl, by explicitly including packages via a .dll interface, but this is almost never how it is used in practice.

Both have their place, but I personally don't see the purpose for the Perl/Python method in game development. At least, not as part of the core game engine. As part of tools, and such, perhaps. But not in the game itself.
#8
04/06/2005 (7:28 am)
What I am writing probably goes for Perl as well, but I just have to point out that Python is a programming language with capabilities way beyond what C++ can do naturally. To see it a sense as narrow as "a scripting language with the intention of driving code", to paraphrase, is ridiculous and wildly inaccurate. I have used Python for a while without ever knowing that "the Python method" is to drive code. I code in Python. I don't think you would call C++ a scripting language that drives the C++ standard library?

Still, I strongly agree with you that the way Torque 2D and TS works, the way it is simulation based where the script drives code isn't all that natural for all kinds of games. That is why I implemented Python to work with T2D and more or less only use T2D for displaying objects in my current project, ignoring it's physics and events.
#9
04/06/2005 (7:34 am)
I've used Perl and Python both for stand-alone projects, but I've also used them as script projects (as well as LUA). Creating a C/C++ script language (Quake-C, C-Script with A6, etc) is entirely possible, even for a C++ based engine. It just might lead to some headaches for new programmers if they're trying to sort out the differences between script and code that uses the same syntax and structure.
#10
04/06/2005 (1:11 pm)
Quote:I just have to point out that Python is a programming language with capabilities way beyond what C++ can do naturally.

Any and all scripting languages have capabilities "way beyond what C++ can do naturally." From typeless variables to functions-as-first-class-objects to any number of other things, the whole point of using a scripting language rather than C++ is because of this added functionality (and the simplicity that comes with it).

Quote:To see it a sense as narrow as "a scripting language with the intention of driving code", to paraphrase, is ridiculous and wildly inaccurate. I have used Python for a while without ever knowing that "the Python method" is to drive code. I code in Python.

My commentary is on the nature of who is in charge, not how much code you write in your scripting language of choice. Lua and JavaScript both are very clearly not in charge; they are called by code at the whim of that code. Python and Perl clearly are in charge; they tell code what to do, and it does it. You can write heaps of JavaScript code, but it is not in command of its own destiny. Whether it gets run or not is purely up to the code in which the script is embedded. By contrast, if you write a Python script, you can run it. Now, the C++ function "RunGame()" may not return for quite some time, or at all, but at the top of the process's call stack is the Python executable and virtual machine.

If you don't like my commentary on Python's use as an embedded language, then talk to your fellow Python programmers. I did a comprehensive investigation of Python last year, during which I discovered numerous commentaries on the language from those who use it. The unanimous consensus of these commentaries is that Python does not work easily or well as an embedded language. The correct way to use Python was to write your C++ code as a Python module and to then to have a script call functions into that module (functions like "StartGame" and so forth).

Quote:I don't think you would call C++ a scripting language that drives the C++ standard library?

Well, C++ isn't a scripting language, but it does drive the standard library. The standard library doesn't call itself; it gets called.
#11
04/08/2005 (1:25 am)
It's just absurd to say that a programming language, which python is, drives or is driven by code. It's just a programming language just like C++ is.
#12
04/08/2005 (2:20 am)
Quote:It's just absurd to say that a programming language, which python is, drives or is driven by code. It's just a programming language just like C++ is.

It is a programming language, but it's use is substantially different from C++. C++ is compiled into assembly that is directly executed by the central processing unit of the machine; Python is not.

Basically, the difference between a scripting language (Lua, Python, JavaScript, etc) and a byte-code interpreted programming language (.NET, Java) is simple: what level it operates at. .NET and Java are "mid-level" languages. They share many facilities with C++. They are strongly typed, and offer no dynamic recompiling. They are built for relative ease of use, as they strip away some of the complexity of C++, but they don't have any of the hardcore high-level features of real scripting langauges. You still have to declare variables and other such menial tasks. Tasks that are easily forgotten.

Scripting languages, by contrast, are much more grammatically freeform. How do you declare a variable in a scripting language? Use it. Do I have to say what type is stored in the variable? Why would you need to know? Can I take a string and have the compiler compile it as through that string were a section of code, thus easily creating dynamic compillation? Of course. Can I pass functions around like objects? Why not? These kinds of high-level, abstract functionality that separates scripting languages from interpreted programming languages. They are designed for ease of use (though clearly the makers of Perl didn't get that memo ;) ) and rapid implementation. They provide functionality that, basically, prevents any hope of JIT compilation, or even assembly compilation as a preprocessing step. Performance is not the most important concern for scripting languages; ease of use is.

More importantly, the classification matters to a very significant degree. In the embedded case, the C++ programmer is in charge. He decides which scripts are run, and when to run those scripts. If he decides to starve those scripts, so be it. Scripts are, in such instances, another way of writing functions or objects, except that their API can be much more limitted than what C++ allows.

The Python method doesn't allow this. If you, as a C++ programmer, allow a game to be controlled by script, the script is in charge. You don't even get to decide when to start or end. In such instances, it is the C++ executable that is the object created by the script.

The Python model has its advantages, but typically not for games. The Perl/Python model of installable packages is useful for running multiple packages and binding them together via script. Having a database feed into an .xls document which is later coverted into a powerpoint presentation or something.

That kind of connectivity is very useful, but not for games. For games, there's one application: the game. It doesn't need to connect to other things. It is self-contained; it is perfectly capable of doing its job on its own without calling out to other processes. Plus, high-performance games can't afford to wait on other processes for data or some such.

Oh, you could do something like store its data in an external database accessible via some API or some such. However, unless you've got a really good reason to do so (like you're storing 100,000+ pieces of information that you need to be able to sort, index and obtain relatively quickly), it's not a good idea.

Remember, one of the principle purposes in using scripts at all is to allow the end user to make changes to the game. If you cannot control what the user is allowed to do, then this becomes difficult/impossible. That's another reason why the embedded approach is the common case for games.

Obviously, you can use Python or whatever you wish in whatever context you wish. It is my experience, however, that an embedded language, the JavaScript/Lua method, is much more conducive to game development than the Python method.
#13
04/08/2005 (8:40 am)
I think Smaug is right.

Beyond all the Python/Lua/Javascript debate, what i don't understand is why Torque2D developpers look only for TorqueScript usage? Why not TorqueScript AND C++? The TorqueScript is good for almost everything a classic game needs, but i know a lot of things i need to do will need C++...

I think a little interface for c++ impementation (or hacking) should be done, like a "module" class with a init(),main(),terminate() that is facultative and that is launched by the Torque2D main loop, only if the programmer wants it. If i understood well, all the other funtions are already usable in c++, is'nt it? (i did'nt have time to jack in code).
I could do it myself, but it should be a standart in the Torque2D, don't you think?
#14
04/08/2005 (8:46 am)
Very well put Smaug, I like your comparisson of scripting languages to programming languages...

@Joel - I think its a matter of familiarity, T2D users haven't had enough time to fully know all the script commands, nevertheless know all that must be implemented in the C++ side... TGE has a lot of C++ resources, but its been around for a while... given time I think T2D developers will use the C++ in conjuction with the TorqueScript
#15
04/08/2005 (8:52 am)
Some info posted by Stephen Zepp

Quote:
1---

Actually, IIRC, TGEScript is byte-code compiled at startup, instead of "Just in Time". That's why you can see script errors immediately after entering the game (via console), instead of only after you attempt to use a particular scripted funtionality. It's also not really "interpreted" in an absolute sense but is in fact compiled--it's just that compilation isn't a seperate process.

There is a historical community belief (and I mean programmers in general, not just GG community) that script languages are inherently much slower than compiled languages, but that really isn't the case--hasn't been since back in the 80's/90's when byte code compilation become the standard. For example, I use TCL (another byte-code compiled "script" language) to handle real time data manipulation for hospitals--all the data you put on to an admitting form for example when you show up at a hospital goes into a computer, and is then massaged, mapped, and otherwise modified to fit into dozens of other applications. I also know of actual oil well rigs that use TCL to handle all of the real time systems monitoring and management for at-sea oil rigs. Obviously, that couldn't work if scripted applications were particularly slow!

TGEScript has a pretty fast execution speed overall. It will never be as fast as a C++ version of an implementation, but that's not because it can't be--only because the time and resources required to make the TGEScript environment completely optimized for speed wouldn't be worth the last little bit of performance that might be squeezed out.
#16
04/08/2005 (8:52 am)
Quote:
And 2--

Just to carry on a bit more about the difference between "interpreted" and "byte-code compiled".

Things are always going to be fuzzy because every application/language is going to do things slightly differently, but in general (and please, any purists out there--this is a layman's view of a layman's answer!):

A compiler takes text files and converts them into .obj files as an intermediate step, and then a linker ties all of these .obj files (and their internal forms of your text written code) into an executable application. The act of compiling to .obj files is a reduction from the human-readable language to a lower level, more efficient "machine processable" format. The level of reduction is compiler dependent, but we're not talking all the way down to assembler/machine language all of the time (note: this may not be true any longer--in fact, I may be wrong and compilers do actually produce machine code--it's been a while since my compiler theory days). The conversion from the high level language to the lower level language is basically on a "per primitive" level--the parser recognizes code structures, and basically replaces them with the lower level language versions during the compilation process.

An interpreter reads in the text formatter code character by character every time you execute a code section, even if you've already executed that code section during this application run. The interpreter pattern matches (normally with a finite state machine, although that's just one implementation) the tokens that are built character by character, and once it forms a valid token (including parameters, variables, etc.) it jumps off to carry out what the token requires, and then goes back to parsing character by character.

A byte-code compiler does a pre-parse of the script language (during the initial loading of the text file), and converts the text into a lower level representation of the code you've written (just like the compiler does), but doesn't necessarily carry it all the way down to the low level language that a compiler might. Once your code execution begins, a byte-code compiled language never actually refers to the text source again, but uses this much more highly optimized "byte-code" representation of your source code. That means that at execution time, it is much faster than a fully interpreted language, since it doesn't have to parse like an interpreter does each time a block is executed. However, since the byte-code representation most of the time isn't as "low level" as what a compiler would produce, it's not quite as fast...just much faster than interpreted languages.

Finally, when you purchase T2D, you have the source code. If you don't think TGEScript is fast enough for your particular game, then implement everything in C++!

#17
04/08/2005 (12:10 pm)
Quote:Beyond all the Python/Lua/Javascript debate, what i don't understand is why Torque2D developpers look only for TorqueScript usage? Why not TorqueScript AND C++?

I don't know, but this weekend, I plan to devote some time redoing the entire tutorial (the given one) in C++. Once I am able to accomplish that, I figure that I'll have the infrastructure necessary to begin real C++ work.

The designed/expected use of C++ in T2D is to build objects that TScript uses. I don't plan on working that way.
#18
04/08/2005 (12:25 pm)
Quote:I don't know, but this weekend, I plan to devote some time redoing the entire tutorial (the given one) in C++. Once I am able to accomplish that, I figure that I'll have the infrastructure necessary to begin real C++ work.[quote]

That would be nice ^^

[quote]
The designed/expected use of C++ in T2D is to build objects that TScript uses. I don't plan on working that way.

Neither do i.

Matthew > It's not just about speed; some things need to be in c++ like implementing Lua for example.
#19
04/08/2005 (12:39 pm)
Quote:I don't know, but this weekend, I plan to devote some time redoing the entire tutorial (the given one) in C++. Once I am able to accomplish that, I figure that I'll have the infrastructure necessary to begin real C++ work.[quote]

That would be nice ^^

[quote]
The designed/expected use of C++ in T2D is to build objects that TScript uses. I don't plan on working that way.

Neither do i.

Matthew > It's not just about speed; some things need to be in c++ like implementing Lua for example.
#20
04/08/2005 (12:46 pm)
Quote:I think a little interface for c++ impementation (or hacking) should be done, like a "module" class with a init(),main(),terminate() that is facultative and that is launched by the Torque2D main loop, only if the programmer wants it. If i understood well, all the other funtions are already usable in c++, is'nt it? (i did'nt have time to jack in code).
I could do it myself, but it should be a standart in the Torque2D, don't you think?

All of this functionality already exists. You'll need to determine which base class you want to inherit from when creating your new c++ class, and that decision will be based on the nature of the new object and it's intended purpose, but as long as it's a simulation, console, or event based object, everything is already there for what you need--simply select the base class to inherit from, implement the handles that are needed for appropriate use, and then implement your class specifics.

It's been mentioned several times, but it's a known issue that being a T2D licensed owner but not a TGE 1.3 owner cuts you off from some of the source code documentation that could be useful for those wanting to dive into the low level details and structure of TAP. GG is addressing possible scenarios to help along with this, so please be patient!
Page «Previous 1 2 3 Last »