What have I learned using Ogre?

What it says on the tin: a place to discuss proposed new features.
Post Reply
User avatar
Faranwath
Halfling
Posts: 93
Joined: Mon Jul 09, 2012 2:19 pm
Location: Cuba
x 7

What have I learned using Ogre?

Post by Faranwath »

For the last month and a half, I've been (seriously) playing with Ogre in order to gain insight into the engine's philosophy. This has been one of the most productive "month and a half"s of my entire life. And the most important thing is, even though I learned a lot about Ogre as I originally intended, the mere act of reading Ogre's source put me in the track of discovering so many things I was ignorant about.

To sumarize:
  • Ogre's use of distinct allocation policies through template class AllocatedObject made me delve deeper into the subject of policy-based class design, and once you enter that world you have to read Andrei's "Modern C++ Design: Generic Programming and Design Patterns Applied". This book has been the source of many of my "Wow!" moments so far.
  • While I was reading Andrei's book, I stumbled upon his technique for achieving static dispatch on an integer constant, which I later discovered is used by Ogre::ExceptionFactory::create by means of Ogre::ExceptionCodeType. Kudos...
  • The principle behind template class Ogre::Singleton is so simple, yet so powerful, that it literally knocked my socks off when I understood how it works. Even though it cannot assert the singleton's unicity from its interface, it is the best way I've seen so far to retain full control over its lifetime -- a must in systems like games. (There's one issue that I don't yet fully understand, though. Is the cast found in the constructor always safe? Isn't this an object of type Singleton<SomeType> at this point, even though its layout in memory may coincide with the one of an object of type SomeType? What if SomeType has a virtual function, and the vptr is (as many compilers do it) at the beginning of the SomeType objects, or SomeType inherits from other class besides Singleton<SomeType> and the latter is declared after the former?)
If at this point I haven't made myself clear, then allow me to: Thanks to all the developers (and specially to Steve Streeting) for sharing their wisdom so willingly. It has helped me a lot, and I think I'm not alone here.

Still, there were some moments in which I wanted to poke someone's head because of design decisions that made my life harder. Yes, Ogre is a well-thought system, but I (modestly, let me not pretend otherwise) think there are things that can be done in order to help us (the users):
  • I have to look inside the code to know what functions throw exceptions most of the time. That is annoying to me. Exception specifications should be part of the function's documentation. Note that I'm not referring to exception specifications as part of the function's declaration; those are useless most of the time. I can realize that would be a ton of work, but it would make Ogre more enjoyable (and secure) to use. It would also ease the challenge that is getting a piece of code to be exception-safe. Yes, Ogre's documentation does indicate the exceptions that a bunch of functions may throw, but some functions are documented as not throwing exceptions and are implemented in terms of some that may.
    This all comes from the debugging I had to do in order to find the source of a crash that, according to my program's design, shouldn't occur, yet happened when the application was being shut down. It was only fifteen minutes later that I discovered that Ogre::ResourceGroupManager::clearResourceGroup throws an exception if it cannot find a group with the supplied name. I'm not questioning this decision at all, in fact I strongly believe that is The Right Thing To Do. The issue is that this happened after another exception had been thrown and the system was being correctly shut down (and yes, it was complex enough as to need clearing the group before final shutdown), and, as we know, two active exceptions is something C++ doesn't handle very well :)
  • To my (unexpected) surprise, Ogre's "destroyX"-like functions have undefined behavior if the instances of X handled to them are equal to nullptr. My initial guess was that these functions would emulate C++'s operator delete behavior, which does nothing when presented with a nullptr pointer (pun intended). I know, it is only a matter of me checking the pointers before passing them to Ogre's "destroyers", but I think that's something the library could do. And when you wrap these objects with std::unique_ptr's, well... it doesn't feel right to insert the test inside every functor. (And again, the system is complex enough as to need destroying these objects long before Ogre::Root.)
  • Build times are just huge, which basically shocked me when I first did a full build of a not-so-simple Ogre-based program, because the library does a terrific job at using abstract classes and the "program-to-interfaces-not-to-implementations" principle. From what I've seen, the main cause of this is almost every file including the header OgrePrerequisites.
  • Once the different compilers catch up, consider eliminating Boost as a dependency, in favor of C++11's <chrono> and threading API.
I have to point out that I'm using Cthugha, not Byatis.

Long preamble, final thoughts: Ogre is enjoyable to use, you can learn a big deal just by trying to figure out how all of its components fit together, and thank God for its active community.

You are doing a great job, guys.
bstone
OGRE Expert User
OGRE Expert User
Posts: 1920
Joined: Sun Feb 19, 2012 9:24 pm
Location: Russia
x 201

Re: What have I learned using Ogre?

Post by bstone »

Faranwath wrote:There's one issue that I don't yet fully understand, though. Is the cast found in the constructor always safe? Isn't this an object of type Singleton<SomeType> at this point, even though its layout in memory may coincide with the one of an object of type SomeType? What if SomeType has a virtual function, and the vptr is (as many compilers do it) at the beginning of the SomeType objects, or SomeType inherits from other class besides Singleton<SomeType> and the latter is declared after the former?
That's not "many compilers" - that's C++ standard. If you follow the common-sense convention you will be safe: don't use the singleton accessor methods until it's fully constructed. That's already hard to do unless you have a class in the middle of the inheritance chain that for some reason decided to use getSingleton() instead of "this" in it's own constructor and thinks he will get access to virtual methods of the following derived classes. If that's the case then you should re-think your approach to software development anyway :)
Faranwath wrote:I have to look inside the code to know what functions throw exceptions most of the time. That is annoying to me. Exception specifications should be part of the function's documentation.
Why? And I don't think so. Just use the right tools for the job. Get a debugger that can break on an exception being thrown if you are so keen to know where exactly it's generated. MSVC debuggers could do that since version 6.0 if not earlier. Can't remember if GDB has the option but I tend to think it has. If you use an outdated debugger (e.g. printf) then consider redefining OGRE_EXCEPT to trigger a soft breakpoint and dump the exception origin, optionally with a stack trace.
Faranwath wrote:I can realize that would be a ton of work, but it would make Ogre more enjoyable (and secure) to use.
And the docs will be out of sync with the code most of the time unless carefully maintained. How would it make it more secure? Also make sure you have OGRE_ASSERT_MODE defined either as STANDARD or RELEASE_EXCEPTIONS. That will make asserts in Ogre use the available debug mechanism that usually points the origin of an issue right into your face. And yes, debug builds are for a purpose :wink:
Faranwath wrote:but some functions are documented as not throwing exceptions and are implemented in terms of some that may.
Here you go. Just what I said.
Faranwath wrote:and yes, it was complex enough as to need clearing the group before final shutdown), and, as we know, two active exceptions is something C++ doesn't handle very well :)
Well, you ask for so much yet you obviously didn't care to handle the first exception :wink:
Faranwath wrote:Ogre's "destroyX"-like functions have undefined behavior if the instances of X handled to them are equal to nullptr. My initial guess was that these functions would emulate C++'s operator delete behavior, which does nothing when presented with a nullptr pointer (pun intended).
Sounds like wishful thinking to me.
Faranwath wrote:I know, it is only a matter of me checking the pointers before passing them to Ogre's "destroyers", but I think that's something the library could do.
Well, just don't destroy something that shouldn't be destroyed. It's a valid idiom to me.
Faranwath wrote:And when you wrap these objects with std::unique_ptr's, well... it doesn't feel right to insert the test inside every functor. (And again, the system is complex enough as to need destroying these objects long before Ogre::Root.)
Just make another template functor that would check the pointers before invoking your final functor, similar to std::bind. Then use it as a deleter with std::unique_ptr.
Faranwath wrote:Build times are just huge, which basically shocked me when I first did a full build of a not-so-simple Ogre-based program, because the library does a terrific job at using abstract classes and the "program-to-interfaces-not-to-implementations" principle. From what I've seen, the main cause of this is almost every file including the header OgrePrerequisites.
Precompiled headers and proper abstractions to the rescue.

I could be harsh but I love Ogre and I'm drunk. :mrgreen:
User avatar
Faranwath
Halfling
Posts: 93
Joined: Mon Jul 09, 2012 2:19 pm
Location: Cuba
x 7

Re: What have I learned using Ogre?

Post by Faranwath »

It seems to me that you missed the point I tried to make. I'm not bashing Ogre, I simply wanted to share my experiences (however contrived) with the community.
That's not "many compilers" - that's C++ standard.
Is it? Because N3242 (the latest draft of ISO C++11) doesn't contain a single mention of neither virtual pointers nor virtual tables.
If that's the case then you should re-think your approach to software development anyway
Is this the kind of advice I can expect from Ogre's community? Perhaps I should then find another place to learn.
And the docs will be out of sync with the code most of the time unless carefully maintained.
Not if this is automated.
How would it make it more secure?
Read again the issue I had with two active exceptions because I didn't know Ogre::ResourceGroupManager::clearResourceGroup could throw.
Well, you ask for so much yet you obviously didn't care to handle the first exception
The first exception would be handled after clearing the group. Easily redesignable once I knew the function could throw.
Sounds like wishful thinking to me.
Fine. I like libraries that make my life easier, though.
I could be harsh but I love Ogre and I'm drunk.
As well you can, but then perhaps you should take a shower and coffee before addressing someone else.
User avatar
Klaim
Old One
Posts: 2565
Joined: Sun Sep 11, 2005 1:04 am
Location: Paris, France
x 56
Contact:

Re: What have I learned using Ogre?

Post by Klaim »

Hi Faranwath, you're feedback is an interesting read.
Faranwath wrote:It seems to me that you missed the point I tried to make. I'm not bashing Ogre, I simply wanted to share my experiences (however contrived) with the community.
That's not "many compilers" - that's C++ standard.
Is it? Because N3242 (the latest draft of ISO C++11) doesn't contain a single mention of neither virtual pointers nor virtual tables.
I'm not sure on this point, but doesn't the standard give enough type data layout guarantees for this case?

Faranwath wrote:
If that's the case then you should re-think your approach to software development anyway
Is this the kind of advice I can expect from Ogre's community? Perhaps I should then find another place to learn.
Wow, that's quite a generalization...
Please keep in mind different people have different tone of talking around here, but we are all gentlemen. 8)
Also, Ogre team members have the final work on what modifications get in, with some discussions before most on the time.
And the docs will be out of sync with the code most of the time unless carefully maintained.
Not if this is automated.
As far as I know, it is impossible to automate this.
I remember few months ago Stroustrup was asked about tools to have guarantees about exceptions, there is none because in practice there is no way to make such tools without getting into NP complete complexity of problem.
I'm not a specialist on this point though.

Even assuming there was such a tool, you should always assume that any Ogre function can throw.
Any debugger will point you the exception type and data at throw point (gdb does it too by default).
User avatar
Faranwath
Halfling
Posts: 93
Joined: Mon Jul 09, 2012 2:19 pm
Location: Cuba
x 7

Re: What have I learned using Ogre?

Post by Faranwath »

I'm not sure on this point, but doesn't the standard give enough type data layout guarantees for this case?
I'm far from being a language lawyer, so I can't assert that. What I referred to was that Ogre's Singleton (at least as I can see it) assumes that this' memory location is that of an object of type T, which need not always be true if there is a base class other than Singleton.
Wow, that's quite a generalization...
Please keep in mind different people have different tone of talking around here, but we are all gentlemen.
Also, Ogre team members have the final work on what modifications get in, with some discussions before most on the time.
I don't intend to leave the community because of some guy's response, rest assure. I was only a little offended, that's all. My original intention was to thank the developers for the library and everything I'm learning from it.
I remember few months ago Stroustrup was asked about tools to have guarantees about exceptions, there is none because in practice there is no way to make such tools without getting into NP complete complexity of problem.
I believe the question was actually about an upper bound on the time it would take to catch an exception, but I see you point. I'm not very versed on NP-completeness theory, but I thought a tool could be written that parsed the code and determined whether a function could or not throw, and then simply include this fact in the documentation.
Even assuming there was such a tool, you should always assume that any Ogre function can throw.
That's exactly what I did after the issue I described in my first post.
bstone
OGRE Expert User
OGRE Expert User
Posts: 1920
Joined: Sun Feb 19, 2012 9:24 pm
Location: Russia
x 201

Re: What have I learned using Ogre?

Post by bstone »

You should take it easy. I wasn't bashing you but was answering your questions without any offense intended. Sometimes a bad answer is better than no answer at all.
Faranwath wrote:Is it? Because N3242 (the latest draft of ISO C++11) doesn't contain a single mention of neither virtual pointers nor virtual tables.
All right. So I look into N3337 (no, I don't have a time machine but you should double check your "latest" claim :)) and I still see the important bit even through my fogged eye:
N3337 - 12.7.3 Construction and destruction wrote: To form a pointer to (or access the value of) a direct non-static member of an object obj, the construction of obj shall have started and its destruction shall not have completed, otherwise the computation of the pointer value (or accessing the member value) results in undefined behavior.
Look ma - no virtual tables!
Faranwath wrote:Is this the kind of advice I can expect from Ogre's community? Perhaps I should then find another place to learn.
Oh, should I quote the latest draft of the ISO Humor standard now? Or do you fit the nearly impossible condition of trying to reference a derived class from the still constructing base? Like on regular basis? Otherwise the ironic statement you took so close to the heart wasn't aimed at you.
Faranwath wrote:Not if this is automated.
Show me! :D
Faranwath wrote:Read again the issue I had with two active exceptions because I didn't know Ogre::ResourceGroupManager::clearResourceGroup could throw.
It's already secure - the C++ standard dictates the strict outcome of the second unhandled exception.
Faranwath wrote:Fine. I like libraries that make my life easier, though.
I've heard Unity3D does exactly that :D
Faranwath wrote:As well you can, but then perhaps you should take a shower and coffee before addressing someone else.
Nah. It's too late here for coffee. I'll consider your advice about the shower though. :mrgreen:
bstone
OGRE Expert User
OGRE Expert User
Posts: 1920
Joined: Sun Feb 19, 2012 9:24 pm
Location: Russia
x 201

Re: What have I learned using Ogre?

Post by bstone »

And yeah - I'm still new here and can be a pain in the ass so don't reflect me on the whole community. You will find lots of nice and helpful people around. Being trigger happy to stamp them with a negative attitude based on my single answer won't serve you any good. Don't offend them or I'll chase you until you start indenting the braces my way :lol:
bstone
OGRE Expert User
OGRE Expert User
Posts: 1920
Joined: Sun Feb 19, 2012 9:24 pm
Location: Russia
x 201

Re: What have I learned using Ogre?

Post by bstone »

Oh, and the last note. I used Google Translate to post on this thread. If all of the above sounds dark and grim then please know this: the original in Russian sounded fun, cheerful, and very inspirational (this post included). Blame Google not me! :mrgreen:
User avatar
Faranwath
Halfling
Posts: 93
Joined: Mon Jul 09, 2012 2:19 pm
Location: Cuba
x 7

Re: What have I learned using Ogre?

Post by Faranwath »

Oh my, this turned out to be completely different from the way I thought it would be. So, given that I intend to stay around here for a while, let me put an end to this issue.

The thing related to Ogre's Singleton class template is now clear, my concern was motivated by nothing else than ignorance on static_assert. Given two classes B and D, where the former is a base of the latter, the standard dictates that it is completely safe to cast between pointers or references to two objects of those types, as long as the casts "make sense", as occurs within Singleton's constructor -- i.e. the compiler is required to point to the right address, even it needs to "go back" because B is a contained within D. So, long story short, yet another thing I learned because of Ogre.

Reading through your posts, it seems to me that you thought I was trying to do something perverse with this, such as invoking a virtual function inside a constructor or some other nonsense. This was never the intention, I don't like to play hacker and have been using C++ for some time and know two or three things. Were you always aware of the mistake I was making? Perhaps that's the case, although I failed to saw it that way. So, in order to maintain politeness, allow me to say I should had expressed myself better.

Thanks to your suggestion, I took a deeper look at Visual Studio's debugging tools, and you were right: the ~15 minutes I spent trying to find the origin of the crash in my program was yet another product of my ignorance. I now have a much better understanding of these tools and have begun to use them much more often. So, thanks for your advice.

I thought N3242 was the latest draft, so I apologize. I don't keep track of the whole standardization process, even though I do read sections from the document often. When I said this was the latest draft I sincerely thought it was.

I need to show you no tool, I just thought it would be nice to know the set of exceptions that a function may throw without looking at the code. It turns out this is quite difficult to achieve, if not impossible (again, I'm far from being Turing or Stephen Cook). I now know this.
Don't offend them or I'll chase you until you start indenting the braces my way
You're far from being needed to play as someone's bodyguard. It was never my intention to generalize, even though this is indeed the obvious thing to infer from the words I said (or rather, wrote). Nothing of what I said could be qualified as offensive. I always praised the work of Ogre's developers, and I was being sincere.
If all of the above sounds dark and grim then please know this: the original in Russian sounded fun, cheerful, and very inspirational (this post included). Blame Google not me!
There's no need to blame anybody. And I don't speak Russian, only Spanish and hardly English.
Sometimes a bad answer is better than no answer at all.
I strongly disagree.

So, to set an end to all of this, I'm sorry if at some point you felt offended or whatever because of something I may have said. Peace...

PS: I find it necessary (however you might take it) to point out that I don't believe I should excuse myself, but what I definitively don't want is to continue from this as if nothing had happened and say, "Oh s**t, here's that annoying Russian guy again", every time I see a post of yours.
User avatar
Zonder
Ogre Magi
Posts: 1168
Joined: Mon Aug 04, 2008 7:51 pm
Location: Manchester - England
x 73

Re: What have I learned using Ogre?

Post by Zonder »

Faranwath wrote:
If all of the above sounds dark and grim then please know this: the original in Russian sounded fun, cheerful, and very inspirational (this post included). Blame Google not me!
There's no need to blame anybody. And I don't speak Russian, only Spanish and hardly English.
Common reason for tone misunderstandings is translation alot of people don't speak english on this forum.

Don't forget there are native forums as well for spanish and russian :D
There are 10 types of people in the world: Those who understand binary, and those who don't...
CABAListic
OGRE Retired Team Member
OGRE Retired Team Member
Posts: 2903
Joined: Thu Jan 18, 2007 2:48 pm
x 58
Contact:

Re: What have I learned using Ogre?

Post by CABAListic »

Faranwath wrote: I have to look inside the code to know what functions throw exceptions most of the time. That is annoying to me. Exception specifications should be part of the function's documentation. Note that I'm not referring to exception specifications as part of the function's declaration; those are useless most of the time. I can realize that would be a ton of work, but it would make Ogre more enjoyable (and secure) to use. It would also ease the challenge that is getting a piece of code to be exception-safe. Yes, Ogre's documentation does indicate the exceptions that a bunch of functions may throw, but some functions are documented as not throwing exceptions and are implemented in terms of some that may.
This all comes from the debugging I had to do in order to find the source of a crash that, according to my program's design, shouldn't occur, yet happened when the application was being shut down. It was only fifteen minutes later that I discovered that Ogre::ResourceGroupManager::clearResourceGroup throws an exception if it cannot find a group with the supplied name. I'm not questioning this decision at all, in fact I strongly believe that is The Right Thing To Do. The issue is that this happened after another exception had been thrown and the system was being correctly shut down (and yes, it was complex enough as to need clearing the group before final shutdown), and, as we know, two active exceptions is something C++ doesn't handle very well :)
I don't think it's manageable to document every single exception that specific operations might throw. After all, some operations can be fairly complex, e.g. loading a mesh will at some point also load referenced textures and possibly materials. Any number of things could go wrong. I do believe, however, that functions should generally document how they react to "incorrect" input, which would apply to your case. As always, this is a matter of time and effort, but do feel free to raise any such incidence you encounter as a papercut on our bugtracker.
To my (unexpected) surprise, Ogre's "destroyX"-like functions have undefined behavior if the instances of X handled to them are equal to nullptr. My initial guess was that these functions would emulate C++'s operator delete behavior, which does nothing when presented with a nullptr pointer (pun intended). I know, it is only a matter of me checking the pointers before passing them to Ogre's "destroyers", but I think that's something the library could do. And when you wrap these objects with std::unique_ptr's, well... it doesn't feel right to insert the test inside every functor. (And again, the system is complex enough as to need destroying these objects long before Ogre::Root.)
I'd consider this a bug as it violates developer expectations. Does this apply to every create/destroy pairs, or only specific ones? Again, please report any such problem to our bugtracker.
Build times are just huge, which basically shocked me when I first did a full build of a not-so-simple Ogre-based program, because the library does a terrific job at using abstract classes and the "program-to-interfaces-not-to-implementations" principle. From what I've seen, the main cause of this is almost every file including the header OgrePrerequisites.
You might try a build without Boost (e.g. by disabling threading support altogether, if you don't strictly need it), that might help. Other than that, I tried reducing header dependencies at some point a little, but it didn't really help. We could probably do better, but I feel this is a general C++ problem due to the lack of proper module support.

Once the different compilers catch up, consider eliminating Boost as a dependency, in favor of C++11's <chrono> and threading API.
At this point it's more likely that we will switch to our own wrappers around the Windows / POSIX thread APIs in the foreseeable future.
User avatar
Faranwath
Halfling
Posts: 93
Joined: Mon Jul 09, 2012 2:19 pm
Location: Cuba
x 7

Re: What have I learned using Ogre?

Post by Faranwath »

CABAListic wrote:I do believe, however, that functions should generally document how they react to "incorrect" input, which would apply to your case. As always, this is a matter of time and effort, but do feel free to raise any such incidence you encounter as a papercut on our bugtracker.
That would be extremely helpful, but I do agree it would also mean a ton of work for whoever chooses to carry out with the task -- which led me to (naively) assume the process could be automated.
CABAListic wrote:I'd consider this a bug as it violates developer expectations. Does this apply to every create/destroy pairs, or only specific ones? Again, please report any such problem to our bugtracker.
Hmm, my concern was motivated because of some SceneManager's functions such as destroySceneNode(SceneNode*) and destroyMovableObject(MovableObject*) (invoked as part of destroyLight(Light*), destroyEntity(Entity*), ...), so I cannot account for every other create/destroy pair in the library. I could, however, take the time to find all such pairs and report them (i.e. file name and line), if the development team is interested in modifying the behavior of the functions. However, such a "safe and user-friendly modification" could negatively impact on the library's performance, and perhaps not everyone would appreciate it -- what about some sort of checking policy established at compile time through CMake's options?

Of course, this could raise the question "But why are you passing null pointers to these functions?". I think it's a matter of convenience then: I could do the checking myself, or trying to come with an architecture in which this situation would be impossible (which seems rather hard). Am I putting too much thought into this?
CABAListic wrote:We could probably do better, but I feel this is a general C++ problem due to the lack of proper module support.
You're right. Besides, I started playing with precompiled headers (thanks to bstone's suggestion) and they do help in mitigating this burden!
CABAListic
OGRE Retired Team Member
OGRE Retired Team Member
Posts: 2903
Joined: Thu Jan 18, 2007 2:48 pm
x 58
Contact:

Re: What have I learned using Ogre?

Post by CABAListic »

If you're constantly creating and destroying objects at such a rate that performance is negatively impacted by a simple null pointer check, then chances are you're doing something seriously wrong. And should probably look at pooling or something similar. But it is a well established standard that resource destruction is safe on a null pointer, so breaking that convention without a better reason should be avoided.
User avatar
Faranwath
Halfling
Posts: 93
Joined: Mon Jul 09, 2012 2:19 pm
Location: Cuba
x 7

Re: What have I learned using Ogre?

Post by Faranwath »

Oh well, performance isn't an issue here. Actually the whole point I was trying to make is that I would like Ogre's functions to follow that behavior, but I'm sure this can't be the source of any degradation in performance.

This has lasted more than I wished. Forget about the null pointer...
CABAListic
OGRE Retired Team Member
OGRE Retired Team Member
Posts: 2903
Joined: Thu Jan 18, 2007 2:48 pm
x 58
Contact:

Re: What have I learned using Ogre?

Post by CABAListic »

You might have been missing the fact that I was agreeing with you...? ;) Destroying null pointers should be a no-op, simply doing nothing. Or maybe we are just missing each other. Anyway, report any occurence where destroying a null pointer isn't safe in our bugtracker, and we'll fix it.
User avatar
Faranwath
Halfling
Posts: 93
Joined: Mon Jul 09, 2012 2:19 pm
Location: Cuba
x 7

Re: What have I learned using Ogre?

Post by Faranwath »

CABAListic wrote:You might have been missing the fact that I was agreeing with you...?
Not at all! I have understood everything since you first replied. I simply didn't want to report anything that might cause you guys to sort of :roll: . But I see now this isn't the case -- oh my, I need to polish my social interaction software module :mrgreen: .

I'll take a look at those functions right away.
Post Reply