2010年11月25日
Torque 3D, Unity
For serveral monthes, I had been writing a game prototype with a game engine called Torque 3D. Torque is a famous brand with series of game engine solutions including Torque Game Engine Advance (previous generation of Torque 3D), Torque 2D and Torque X (for .NET platform).
Just serveral weeks ago, the owner company of Torque announced they were going to close business and find buyer for Toruqe Engine and its community (link). It's totally not surprising for us, because we've suffer the awful quality of Torque 3D for past 3 mouths. It has a lot of bugs, graphics defects, and poor documentation. We spent 1 mouths to figure out how to work with Torque 3D and another 2 mouths to fix bugs of Torque 3D, and only 1 week actually for game programming. Even we tried so hard to build our game on Torque 3D, the result still made us disapointed and frustrated. The defects of the animation system made our game looks like garbage!!
Therefore, my team started to seek and evaluate other game engines immediately after hearing the news of close of business. We found Unity Engine is easy, efficent, robust, stable in comparison with Torque 3D. Then, we switched our project to it. It's very surprising and exciting that we spent only 3 days to re-build almost everything except level content. Unity Engine is indeed much more excellent than Torque 3D!!!
In this week, with Unity Engine,I've finished implementing many features that I never expect they can be done with Torque 3D. The difference of usability between these two engines are so huge that I want to write some articles to find out what makes an game engine succeed and what makes it fail. Hopefully, I can finish articles soon and post them to my blog.
Torque 3D V.S. Unity Engine
For serveral monthes, I had been writing a game prototype with a game engine called Torque 3D. Torque is a famous brand with series of game engine solutions including Torque Game Engine Advance (previous generation of Torque 3D), Torque 2D and Torque X (for .NET platform).
Just serveral weeks ago, the owner company of Torque announced they were going to close business and find buyer for Toruqe Engine and its community (link). It's totally not surprising for us, because we've suffer the awful quality of Torque 3D for past 3 mouths. It has a lot of bugs, graphics defects, and poor documentation. We spent 1 mouths to figure out how to work with Torque 3D and another 2 mouths to fix bugs of Torque 3D, and only 1 week actually for game programming. Even we tried so hard to build our game on Torque 3D, the result still made us disapointed and frustrated. The defects of the animation system made our game looks like garbage!!
Therefore, my team started to seek and evaluate other game engines immediately after hearing the news of close of business. We found Unity Engine is easy, efficent, robust, stable in comparison with Torque 3D. Then, we switched our project to it. It's very surprising and exciting that we spent only 3 days to re-build almost everything except level content. Unity Engine is indeed much more excellent than Torque 3D!!!
In this week, with Unity Engine,I've finished implementing many features that I never expect they can be done with Torque 3D. The difference of usability between these two engines are so huge that I want to write some articles to find out what makes an game engine succeed and what makes it fail. Hopefully, I can finish articles soon and post them to my blog.
2010年11月14日
Inheritance, Virtual Method Table, and Memory Layout of Class
Recently, I have encountered a quite obscure bug when implementing reference counting pattern. This pattern related no more than two classes and 100 lines C++ code. However, it took me whole day to find the root cause. To remind many other C++ programmers in the hell (and to practice my writing skill), I wrote this article to complain the design of C++ explain how inheritance and virtual methods affect the memory layout of a class.
Let's start from the bug I encountered. Following is a simple program used to illustrate this bug.
If you run the binary compiled from VC++, run-time exception (i.e. crash) will be definitely raised when deleting the object of Drived. After trying to set up break points, runing code line by line, looking up C++ standard to make sure this is not the bug of compiler, you may be surprised that you just need a virtual destructor for Base.
If you've already realized the root cause from the solution and the title, you need not to continue reading this article. For those who feel confusing, the following is the explanation with delicate illustrations.
Have a look at this diagram:
This is a diagram showing the object memory layout of class Derived. It includes a continuous sub-section (labelled as blue blocks) that holds the structure of class Base. You would notice there is compiler generated member __vptr, which is the pointer to the virtual method table, in the head of the memory layout of class Derived. Class Base doesn't have __vptr because it doesn't have any virtual table. Therefore, if you cast a pointer from Derived* to class Base* actually result in a shift of 4 bytes (for 32 bits system). That's why the program crashes if you delete the pointer base_ptr. The deleting address is not the address where object created!
Let's see what's happing when the virtual destructor is added to class Base:
Because class Base now have virtual method (it's destructor), the object memory layout of class base must also have a space for __vptr. Therefore, when you access the object of class Derived with the pointer of class Base, the computer will recognize blue blocks as the memory block of the object. Therefore, pointer casting between class Derived and class Base results in no address shift.
This is not the end of the story. The object memory layout gets more complicated when it comes to multiple inheritance. Consider the following program:
The object memory layout of class Derived becomes:
The pointer base2_ptr has 12 bytes shift in comparison with derived_ptr. You might think this program will crash when calling delete with base2_ptr due to address shifting. Unexpectedly, the derived object is correctly deleted, because class Base2 also has virtual destructor, and virtual function mechanism do help address the deletion, thus avoiding free memory in wrong address.
Let's start from the bug I encountered. Following is a simple program used to illustrate this bug.
class Base{
public:
int a;
int b;
};
class Derived : public Base{
public:
int c;
virtual void methodA(){
return;
}
};
int main(){
// create derived class
Derived* derived_ptr = new Derived();
// cast derived point to base pointer
Base* base_ptr = static_cast<base*>(derived_ptr);
// delete derived class with base pointer
delete base_ptr;
// exit
return 0;
}
If you run the binary compiled from VC++, run-time exception (i.e. crash) will be definitely raised when deleting the object of Drived. After trying to set up break points, runing code line by line, looking up C++ standard to make sure this is not the bug of compiler, you may be surprised that you just need a virtual destructor for Base.
class Base{
public:
// virtual destructor
virtual ~Base(){}
int a;
int b;
};
If you've already realized the root cause from the solution and the title, you need not to continue reading this article. For those who feel confusing, the following is the explanation with delicate illustrations.
Have a look at this diagram:
This is a diagram showing the object memory layout of class Derived. It includes a continuous sub-section (labelled as blue blocks) that holds the structure of class Base. You would notice there is compiler generated member __vptr, which is the pointer to the virtual method table, in the head of the memory layout of class Derived. Class Base doesn't have __vptr because it doesn't have any virtual table. Therefore, if you cast a pointer from Derived* to class Base* actually result in a shift of 4 bytes (for 32 bits system). That's why the program crashes if you delete the pointer base_ptr. The deleting address is not the address where object created!
Let's see what's happing when the virtual destructor is added to class Base:
Because class Base now have virtual method (it's destructor), the object memory layout of class base must also have a space for __vptr. Therefore, when you access the object of class Derived with the pointer of class Base, the computer will recognize blue blocks as the memory block of the object. Therefore, pointer casting between class Derived and class Base results in no address shift.
This is not the end of the story. The object memory layout gets more complicated when it comes to multiple inheritance. Consider the following program:
class Base{
public:
virtual ~Base(){}
int a;
int b;
};
class Base2{
public:
virtual ~Base2(){}
int a2;
int b2;
};
class Derived : public Base, public Base2{
public:
int c;
virtual void methodA(){
return;
}
};
int main(){
// create derived object
Derived* derived_ptr = new Derived();
// cast Derived pointer to Base pointer
Base* base_ptr = static_cast<base*>(derived_ptr);
// cast Derived pointer to Base2 pointer
Base2* base2_ptr = static_cast<base2*>(derived_ptr);
// delete derived object with base2 pointer
delete base2_ptr;
return 0;
}
The object memory layout of class Derived becomes:
The pointer base2_ptr has 12 bytes shift in comparison with derived_ptr. You might think this program will crash when calling delete with base2_ptr due to address shifting. Unexpectedly, the derived object is correctly deleted, because class Base2 also has virtual destructor, and virtual function mechanism do help address the deletion, thus avoiding free memory in wrong address.
Conclusion
You must declare virtual destructor for a class that is expected to be inherited by other classes. Publisher: Unknown - 星期日, 11月 14, 2010
訂閱:
文章 (Atom)